Android: 记一个关于获取输入法弹起高度的新方式

本文介绍了一种新的实现方式,使EditText在输入法弹出时能够自动上移,避免了传统方法中的布局滚动问题和高度计算错误,通过使用ViewCompat.setOnApplyWindowInsetsListener方法,获取WindowInsets并应用底部系统窗口嵌入物遮挡距离。

很久没写博客,最近项目遇到需要获取输入法高度将EditText上移的效果,实现方式本来有几种

1.设置softInputMode WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
但是这样布局中如果有RecyclerView,或者ScrollView的时候,整个布局会向上滚动,但是我不想让他滚动,
执行编辑框上移

2.网上有一个SoftSoftInputKeyBoardUtil类,原理是通过获取可见高度来计算键盘高度,但是测试发现有些手机获取到的高度是不正确的,
有的高一些,有的低一些,(普遍是状态栏和导航栏的高度影响),特别是一些手机有导航栏一键隐藏功能,可以随时显示或隐藏导航栏,
设置隐藏时候,如果弹出输入法,又会自动加上导航栏,导致高度计算错误

下面介绍一种新的实现方式,从fitSystemWindow的灵感发现。(softInputMode需要是adjustResize
设置fitSystemWindow后,会发现我们即使不用上面的监听方法,编辑框也能自己被输入法推上去,但是缺点是View会被添加一段白色的长条,这是由于适应适应系统窗口,系统会为我们给布局view加上pading,留出系统窗口占据的大小,但是有一些浸入式的实现方式可能会被破坏,需要我们手动修改状态栏颜色,从而有可能影响到其他页面的状态栏颜色(如果未及时重置颜色)

1.相关类

从源码中发现这样一个方法,在View和ViewCompat类中

View.java

/**
     * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
     * window insets to this view. The listener's
     * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
     * method will be called instead of the view's
     * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
     *
     * @param listener Listener to set
     *
     * @see #onApplyWindowInsets(WindowInsets)
     */
    public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
        getListenerInfo().mOnApplyWindowInsetsListener = listener;
    }

ViewCompat.java

/**
     * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
     * window insets to this view. This will only take effect on devices with API 21 or above.
     */
    public static void setOnApplyWindowInsetsListener(@NonNull View v,
            final OnApplyWindowInsetsListener listener) {
        if (Build.VERSION.SDK_INT >= 21) {
            if (listener == null) {
                v.setOnApplyWindowInsetsListener(null);
                return;
            }

            v.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
                @Override
                public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
                    WindowInsetsCompat compatInsets = WindowInsetsCompat.wrap(insets);
                    compatInsets = listener.onApplyWindowInsets(view, compatInsets);
                    return (WindowInsets) WindowInsetsCompat.unwrap(compatInsets);
                }
            });
        }
    }

两者没有太大差别,ViewCompat帮我们对View中的一些方法做了版本兼容

api介绍是这个listener会去接管view中对于窗口嵌入物的处理,

2.窗口嵌入物都指什么

嵌入物的数据通过WindowInsets表示

稍微看下WindowInsets

重点看下mSystemWindowInsets

public final class WindowInsets {

/**
     * Returns the left system window inset in pixels.
     *
     * <p>The system window inset represents the area of a full-screen window that is
     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
     * </p>
     *
     * @return The left system window inset
     * 
	 * 这个表示全屏模式下,部分或者全部被状态栏,导航栏,输入法或其他系统窗口遮挡的那部分区域
	 * 这些代码是我从这个类下两个地方拼接的,方便说明这个变量
     */
	private Rect mSystemWindowInsets;


    private Rect mWindowDecorInsets;
    private Rect mStableInsets;
    private Rect mTempRect;
    private boolean mIsRound;
    private DisplayCutout mDisplayCutout;
}

systemWindowInset就是我们要用的东西

/**
     * Returns the left system window inset in pixels.
     * @return The left system window inset
     */
    public int getSystemWindowInsetLeft() {
        return mSystemWindowInsets.left;
    }

    /**
     * Returns the top system window inset in pixels.
     * @return The top system window inset
     */
    public int getSystemWindowInsetTop() {
        return mSystemWindowInsets.top;
    }

    /**
     * Returns the right system window inset in pixels.
     * @return The right system window inset
     */
    public int getSystemWindowInsetRight() {
        return mSystemWindowInsets.right;
    }

    /**
     * Returns the bottom system window inset in pixels.
     * @return The bottom system window inset
     */
    public int getSystemWindowInsetBottom() {
        return mSystemWindowInsets.bottom;
    }

这几个方法,分别获取左,上,右,下位置的遮挡

在这里插入图片描述
这2个值就是从手机边缘算起,嵌入物遮挡的距离

结论:我们可以给View设置这个Listener,获取WindowInsets,获取 getSystemWindowInsetBottom 得到底部被系统窗口嵌入物遮挡的距离,这样就由系统去计算移动的距离(不管你是导航栏还是输入法还是其他的,或者一起出现,遮挡窗口),然后将需要上移的view移动这个距离,通过设置padding或者marin即可。

ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
            setPadding(0, 0, 0, insets.systemWindowInsetBottom)
            insets
        }

这样就可以实现输入法弹起,编辑框上移,关闭输入法,编辑框复原位置

ViewCompat.setOnApplyWindowInsetsListener() 兼容到api 21 也就是5.0

View.setOnApplyWindowInsetsListener() 要求最低版本 20

更低版本可以通过覆写View onApplyWindowInsets来获取WindowInsets

再次提醒: windowSoftInputMode 设置未adjustResize

如果有错误,欢迎讨论,如果能解决你的问题,那就太高兴了

欢迎留言,让我知道这篇文章是否对大家有帮助

<think>好的,用户现在问的是关于Android 13适配输入法弹出时如何获取输入法高度的问题。首先,我需要确认用户的具体需求。他们提到“输入法的高德”,可能是指输入法高度,或者是和高德地图相关的适配问题?但根据上下文,之前的对话是关于Fragment实现沉浸式,所以更可能是输入法高度的问题,可能“高德”是打错了,应该是“高度”。 接下来,我需要回顾Android 13中关于输入法变化。Android 13引入了WindowInsets的改进,特别是关于输入法(IME)的处理。以前可能需要通过ViewTreeObserver监听布局变化,或者使用WindowInsetsCompat来获取IME的高度,但现在可能有更推荐的方法。 用户可能已经尝试过旧的方法,但在Android 13上遇到了问题,比如无法正确获取高度,或者布局调整不正确。需要检查Android 13的API变更,比如是否废弃了某些方法,或者是否有的API替代。 此外,沉浸式模式下,输入法弹出可能会影响布局,比如底部导航栏被顶起,或者内容区域被挤压。用户可能希望输入法弹出时,界面能正确调整,避免遮挡输入框,同时保持沉浸式效果。 还要考虑兼容性问题,用户可能希望解决方案能在不同版本的Android上工作,特别是从较旧的API到最Android 13。需要提供兼容性处理的方法,比如使用AndroidX的WindowInsetsCompat库,它可以在不同版本中保持一致的行为。 然后,需要组织回答的结构,分步骤说明如何在Android 13中正确获取输入法高度,并调整布局。可能包括使用ViewCompat.setOnApplyWindowInsetsListener,处理软键盘的显示和隐藏,动态调整视图的margin或padding,以及如何处理全屏模式下的情况。 另外,用户可能遇到的问题包括:如何检测输入法是否显示,如何实时获取高度变化,如何处理横竖屏切换时的适配,以及如何避免布局闪烁或跳动。需要涵盖这些场景,并提供相应的代码示例和解决方案。 还需要注意,在沉浸式模式下,系统UI的可见性可能会影响输入法弹出的布局,比如全屏模式下,默认可能不会调整布局,导致输入框被遮挡。这时候需要设置正确的WindowManager.LayoutParams.softInputMode,比如使用SOFT_INPUT_ADJUST_RESIZE或SOFT_INPUT_ADJUST_PAN,并解释两者的区别。 最后,总结最佳实践,推荐使用AndroidX库中的工具类,测试不同场景下的表现,并处理可能的边缘情况,如折叠屏、分屏模式等。确保提供的解决方案全面且稳定,帮助用户顺利完成适配工作。</think>在 Android 13 中适配输入法高度需要结合最的 WindowInsets 机制,以下是针对输入法高度获取和适配的完整解决方案: --- ### 一、核心原理 Android 13 强化了 `WindowInsets` 的语义化控制,输入法高度可通过 `ime()` 类型直接获取: $$ \text{输入法高度} = \text{WindowInsetsCompat.getInsets(ime()).bottom} $$ --- ### 二、基础适配方案 #### 1. 动态监听输入法高度 ```kotlin ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom // 调整底部控件位置 binding.bottomView.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = imeHeight + 16.dp // 保留安全间距 } // 滚动容器适配(如 ScrollView) binding.scrollView.post { binding.scrollView.smoothScrollTo(0, targetView.bottom + imeHeight) } insets } ``` #### 2. 软键盘模式配置 ```xml <!-- AndroidManifest.xml --> <activity android:name=".MainActivity" android:windowSoftInputMode="adjustResize|stateHidden" /> ``` --- ### 三、Android 13 特性适配 #### 1. 预测性返回手势兼容 ```kotlin // 处理返回手势与输入法的冲突 if (Build.VERSION.SDK_INT >= 33) { window.setDecorFitsSystemWindows(false) findViewById<View>(android.R.id.content).setOnApplyWindowInsetsListener { v, insets -> val ime = insets.getInsets(Type.ime() or Type.systemGestures()) v.updatePadding(bottom = ime.bottom) insets } } ``` #### 2. 分屏模式适配 ```kotlin // 检测窗口模式变化 class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { view.viewTreeObserver.addOnGlobalLayoutListener { val rect = Rect() view.getWindowVisibleDisplayFrame(rect) val screenHeight = resources.displayMetrics.heightPixels val keypadHeight = screenHeight - rect.bottom if (keypadHeight > screenHeight * 0.15) { // 输入法可见 adjustForIME(keypadHeight) } } } } ``` --- ### 四、沉浸式模式特殊处理 #### 1. 全屏模式兼容方案 ```kotlin window.apply { addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // 关键配置:允许内容调整 setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) } ``` #### 2. 底部导航栏联动处理 ```kotlin WindowCompat.setDecorFitsSystemWindows(window, false) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) view.updatePadding( bottom = systemBars.bottom + ime.bottom ) insets } ``` --- ### 五、兼容性封装工具类 ```kotlin object IMEUtils { // 获取当前输入法高度 fun getIMEHeight(activity: Activity): Int { val rect = Rect() activity.window.decorView.getWindowVisibleDisplayFrame(rect) val screenHeight = activity.resources.displayMetrics.heightPixels return screenHeight - rect.bottom } // 输入法状态监听 fun registerImeListener(view: View, callback: (isShowing: Boolean, height: Int) -> Unit) { val listener = object : View.OnLayoutChangeListener { private var lastHeight = 0 override fun onLayoutChange( v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int ) { val newHeight = bottom - top if (lastHeight != newHeight) { lastHeight = newHeight val isImeVisible = newHeight < oldBottom - oldTop callback(isImeVisible, getIMEHeight(view.context as Activity)) } } } view.addOnLayoutChangeListener(listener) } } ``` --- ### 六、常见问题解决方案 #### 1. 输入法遮挡输入框 ```kotlin fun scrollToView(target: View) { target.post { val location = IntArray(2) target.getLocationOnScreen(location) val scrollY = location[1] - (screenHeight - imeHeight - target.height) if (scrollY > 0) { binding.scrollView.smoothScrollBy(0, scrollY) } } } ``` #### 2. 动态改变布局结构 ```xml <!-- 使用 CoordinatorLayout + Behavior --> <androidx.coordinatorlayout.widget.CoordinatorLayout> <EditText app:layout_behavior=".ImeAvoidingBehavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> ``` --- ### 七、适配检查清单 | 测试场景 | 验证要点 | |----------------------|----------------------------| | 普通模式 | 输入法高度正确响应 | | 全屏模式 | 内容区域自动调整 | | 分屏/折叠屏 | 布局动态适应不同高度 | | 横竖屏切换 | 高度计算保持准确 | | 第三方输入法 | 兼容不同厂商实现 | | 连续快速弹出/收起 | 无布局闪烁或跳动 | --- 通过上述方案可实现: - 精确获取输入法实时高度 - 智能调整界面布局 - 兼容 Android 13 特性 - 完美适配沉浸式模式 - 处理复杂窗口场景 建议配合 [Android 13 窗口边距官方文档](https://developer.android.com/develop/ui/views/layout/window-insets) 进行深度适配优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值