解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框

标题没想好叫啥,为了更多人可以看到,所以多加了点关键字~

解决全屏Activity的键盘遮挡输入框

Fullscreen遮挡Edittext

全屏输入框bug

AndroidBug5497Workaround

沉浸式输入框

关于AndroidBug5497Workaround的兼容性问题

华为全屏输入框(PS:为什么有人说华为有这个问题?有虚拟导航栏的都有这个问题好伐。。。)

全屏无法弹出输入法

全屏adjustResize不执行

SoftInputMode无效

安卓键盘挡住输入框

 

好了,上面是一堆废话,只是为了让更多人可以看到,毕竟老夫找解决方案的时候找了半天也没完美的解决。

最近搞个项目,设计比较奇葩,沉浸式也就罢了,状态栏和Toolbar的背景不是纯色的,是渐变色的,如图:

这尼玛就有点费劲了,自从接手这个方案,然后各种坑扑面而来应接不暇。

沉浸式实现倒是不复杂,但是后续有哪些坑,得慢慢发现。

第一个就是安卓版本问题,我的项目最低支持4.4,所以往下的兼容性就没考虑。5.x-6.x(忘了几点几了)状态栏是可以完全透明的,就是上图的样式,但是再高的,就会是半透明,我勒个去,这哪行啊,作为强迫症患者,显然不允许这种情况发生啊。于是乎,尝试了几个库,最后整合了几个开源库的代码,总算处理好了半透明的问题,但是据说会引发其他bug,诸如页面动画失效之类的,由于没遇到,等遇到再填坑吧。

背景介绍完了,然后接下来就是今天的大bug了,为此差点放弃了沉浸式设计。

聊天界面,如下:

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第1张图片

点击输入框之后却发现只有键盘弹上来了,而输入框被键盘挡住了,我去,显然不行啊。看起来就像是adjustResize没有发挥他的效果。

咋整捏,解决吧,到处找方案,才发现此问题由来已久,而且貌似谷歌官方不当回事,一直没有官方的解决办法(如果有,欢迎告知)后来才了解到AndroidBug5497Workaround,但是看到有人说在华为的机器上会有个黑条,这种不完美的方案就被我否定了,当时没仔细研究他的代码,不想重复造轮子,于是找到这篇博文:https://blog.csdn.net/smileiam/article/details/69055963  里面的方法5,据说比较完美,于是直接拿来用,跑起来发现,键盘虽然推起来了,但是键盘跟输入框之间有个明显的距离啊,目测等于状态栏高度。

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第2张图片

想到其他人评论说的,华为机器上有黑条,莫非就是这个条?要是这个条真是状态栏高度,倒也好解决,我的担心的是另一个bug,三星Note8的虚拟导航栏,可以双击这个点来隐藏,正常情况下,隐藏之后,activity里的高度会变,内容会填充到虚拟导航栏的位置:

然后界面应该变成这样子:

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第3张图片

测试一下,果然是不如老夫所愿,虚拟按键隐藏了,而输入框稳稳的停在了那个位置,不卑不亢:

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第4张图片

开始排查吧,到底为什么会这样,首先确定是不是沉浸式UI造成这个恶果,不调用这个修复输入法弹出的帮助类,app是正常的,跟随虚拟导航栏隐藏与显示而填充界面或者缩短,调用之后,界面就像是固定了。诶?固定了?莫非是大小被固定了?虽然不想重复造轮子,但是问题得解决啊,于是乎开始学习前辈们的源码:

/**
 * 解决键盘档住输入框
 * Created by SmileXie on 2017/4/3.
 */

public class SoftHideKeyBoardUtil {
    public static void assistActivity (Activity activity) {
        new SoftHideKeyBoardUtil(activity);
    }
    private View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;
    //为适应华为小米等手机键盘上方出现黑条或不适配
    private int contentHeight;//获取setContentView本来view的高度
    private boolean isfirst = true;//只用获取一次
    private  int statusBarHeight;//状态栏高度
    private SoftHideKeyBoardUtil(Activity activity) {
   //1、找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        //2、获取到setContentView放进去的View
        mChildOfContent = content.getChildAt(0);
        //3、给Activity的xml布局设置View树监听,当布局有变化,如键盘弹出或收起时,都会回调此监听  
          mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        //4、软键盘弹起会使GlobalLayout发生变化
            public void onGlobalLayout() {
            if (isfirst) {
                    contentHeight = mChildOfContent.getHeight();//兼容华为等机型
                    isfirst = false;
                }
                //5、当前布局发生变化时,对Activity的xml布局进行重绘
                possiblyResizeChildOfContent();
            }
        });
        //6、获取到Activity的xml布局的放置参数
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
    }

    // 获取界面可用高度,如果软键盘弹起后,Activity的xml布局可用高度需要减去键盘高度  
    private void possiblyResizeChildOfContent() {
        //1、获取当前界面可用高度,键盘弹起后,当前界面可用布局会减少键盘的高度
        int usableHeightNow = computeUsableHeight();
        //2、如果当前可用高度和原始值不一样
        if (usableHeightNow != usableHeightPrevious) {
            //3、获取Activity中xml中布局在当前界面显示的高度
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
            //4、Activity中xml布局的高度-当前可用高度
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            //5、高度差大于屏幕1/4时,说明键盘弹出
            if (heightDifference > (usableHeightSansKeyboard/4)) {
                // 6、键盘弹出了,Activity的xml布局高度应当减去键盘高度
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
                    frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
                } else {
                    frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
                }
            } else {
                frameLayoutParams.height = contentHeight;
            }
            //7、 重绘Activity的xml布局
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }
    private int computeUsableHeight() {
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度
        return (r.bottom - r.top);
    }
}

大佬的注释很清楚。

首先发现 statusBarHeight 没有赋值啊,搞什么哇?注释了半天把个变量给拉下了??严重怀疑那个白块就是这个的问题。

然后继续看,搞明白了原理,原来就是当高度改变的时候,超过阈值就认为是键盘弹出,阈值有设置为四分之一的,有设置为200px的,不管多少,总感觉不靠谱啊,可是又没其他办法,仔细想想也想不出来还有什么情况会导致高度改变(比如手机突然换了块屏幕?)。最终无可奈何的认同了这种做法。

 

也不知道是不是大家都不爱动脑,恢复高度的时候,这里竟然传入了第一次获取到的高度,那问题就来了,当我隐藏虚拟导航栏的时候,高度明明变了,为啥还要传第一次获取到的高度呢?不是应该交给系统控制的么?所以这里:

    //5、高度差大于屏幕1/4时,说明键盘弹出
            if (heightDifference > (usableHeightSansKeyboard/4)) {
                xxxxxxx......
            } else {
                frameLayoutParams.height = contentHeight;
            }

明显是罪魁祸首,改成:

     } else {
           mFrameLayoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT;
     }

那些啥第一获取的值之类的变量,不想关的都去掉。跑一下试试,果然,再次隐藏显示虚拟导航栏,bug就都解决了。

当然,上面的还不够优雅,打log会发现,onGlobalLayout被多次调用,所以去掉不需要的调用:

if (mFrameLayoutParams.height != height) {//不必要的更新就不要了
    mFrameLayoutParams.height = height;
    mContentView.requestLayout();//触发布局更新
}

到这里,基本上遇到的问题就解决了。

但是,我的Note8是安卓8.0,支持分屏的,虽然没咋用过,但是正好可以试试,会不会导致异常情况呢?

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第5张图片

果然,又出问题了,遮挡了一半,目测又是个状态栏高度(这个目测真厉害...),先观察了一下,其他app,注意这个位置:

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第6张图片

比正常情况要高(比其他app要高),说明已经有了个状态栏高度了,其他app在分屏情况下,没有这么高,于是乎心里大概有谱了,我的app不知道系统为什么把状态栏也给我算在内了(难道是fitsSystemWindows的锅?),而我又多加了个状态栏高度,所以导致这种情况。因此首先想到,在分屏的情况下,就不加这个状态栏高度,代码改成:

frameLayoutParams.height = usableHeightSansKeyboard - heightDifference ;// + statusBarHeight;

跑一下子试试呗~嗯,效果8错。

交换一下分屏窗口的上下位置,拉长一下app窗口长度,另一个窗口自动缩短,然后再试一下,哎哟我去,又tm不行了??

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第7张图片

解决全屏Activity的键盘遮挡输入框,Fullscreen遮挡Edittext,全屏输入框bug,AndroidBug5497Workaround,沉浸式输入框_第8张图片

可以看到,输入法遮挡住下面小一点的APP窗口之后又遮挡住了我的app的下边一部分,这部分刚好包括我的输入框。。。。

分析一下,就到了之前说的,变动距离超过阈值就认为是键盘弹出,显然,这种情况移动的距离太小太小,达不到阈值,所以回调里就给忽略了!没办法,只好改成当处于分屏的情况下,移动距离只要大于0就认为是键盘弹出,暂时没想到会不会有其他情况。

修改完成之后,一切顺利,横竖屏都没有问题。

最开始的时候,我只在竖屏里处理这个情况,但是发现,分屏的时候,屏幕方向orientation竟然等于ORIENTATION_LANDSCAPE,而不是ORIENTATION_PORTRAIT,不知道这是什么情况。。。于是不再用orientation作为依据。好在横屏时,一般输入法都会满屏,也就看不到遮挡不遮挡输入框了。只是不知道会不会有横屏输入法不是满屏的情况~~~

代码都修改完了,才发现,原来大佬这段代码的前身就是AndroidBug5497Workaround的代码,只不过是大佬处理华为等手机的时候多添加了一段代码。所以本次就算是对他的一个增强完善吧。

 

原理都已经讲完了,我遇到的实际问题也解决了,但是总感觉这样并不是完美的方案。如果你有更好的经过验证的想法,可以告诉我哟~

 

如果下面的源码对你有帮助或者是没有解决你的问题,可以留言分享一下,并不是我会帮你解决,只是为了给更多人参考,也是为了他人不误入歧途,吼吼~

 

以下是工具类的源码:

一些不用的方法是备用的,怕遇到其他情况可以快速修改。自己随意删~

/**
 * 解决全屏Activity的键盘档住输入框
 * 来自:https://blog.csdn.net/passerby_b/article/details/82686662
 * 注意:
 * 1.要在setContentView之后调用 assistActivity(activity)!
 * 2.要是横屏输入法不是满屏的,就需要自己适配了!
 * 3.自测没有发现问题,但无法100%保证兼容性~
 * 4.分屏模式下的处理,不知道会不会有其他问题,如果不是刚需,建议还是通过setSoftInputMode尝试调整~~~
 * 

* 更新 Cooper 2018-9-13 13:27:59 * 1.解决虚拟导航栏隐藏显示布局不自动适配的问题(三星Note8 8.0实测横屏竖屏都没问题,Vivo没有虚拟按键的机器6.0测试没有问题) * 2.解决分屏模式下不适配的问题(三星Note8 8.0实测横屏竖屏都没问题) * 3.优化代码 *

* 参考:https://blog.csdn.net/smileiam/article/details/69055963 * 参考:https://blog.csdn.net/auccy/article/details/80632429 * 参考:https://github.com/yy1300326388/AndroidBarUtils/blob/master/app/src/main/java/cn/zsl/androidbarutils/utils/AndroidBarUtils.java * 其实最初的原版就是 AndroidBug5497Workaround ,但是原版考虑的不够全面,尤其是虚拟导航栏的问题,没有考虑进去 * 参考:https://www.jianshu.com/p/a95a1b84da11 */ public class SoftKeyboardFixerForFullscreen { public static void assistActivity(Activity activity) { new SoftKeyboardFixerForFullscreen(activity); } private View mContentView;//我们设置的contentView private FrameLayout.LayoutParams mFrameLayoutParams;//我们设置的contentView的layoutParams // private boolean isNavigationShowing = false;//没有用到这个 // private boolean isFullscreenMode = false;//没有用到这个 // private int barNavigationHeight = 0;//虚拟导航栏高度,没用 // private int barNavigationWidth = 0;//虚拟导航栏宽度,没用 private int barStatusHeight = 0;//状态栏高度 private int lastUsableHeight = 0;//上一次的可用高度 private int lastUsableWidth = 0;//上一次的可用宽度 private SoftKeyboardFixerForFullscreen(final Activity activity) { //region 本来是想通过这个监听虚拟按键,结果发现这个回调比布局回调要晚,所以不用了。放在这里是为了给以后提供一些思路。 // //1.为DecorView添加系统组件的可见变更事件 // View decorView = activity.getWindow().getDecorView(); // isNavigationShowing = ((decorView.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0); // isFullscreenMode = ((decorView.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0);//api 16以上 // decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {//参考:https://blog.csdn.net/auccy/article/details/80632429 // @Override // public void onSystemUiVisibilityChange(int visibility) { // if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { // isNavigationShowing = true; // } else { // isNavigationShowing = false; // } // if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { // isFullscreenMode = true; // } else { // isFullscreenMode = true; // } // } // }); //endregion //1.获取 状态栏 高度,获取 导航栏 高度、宽度(横屏用到的,可是横屏在手机上输入法会满屏,不知道不满屏的情况,所以不处理了,要是你遇到了,自行按照横屏的方式解决吧) barStatusHeight = getStatusBarHeight(activity); //barNavigationHeight = getNavigationBarHeight(activity); //barNavigationWidth = getNavigationBarWidth(activity); //2.找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout final FrameLayout content = activity.findViewById(android.R.id.content); //3.获取到setContentView放进去的View mContentView = content.getChildAt(0); //4.拿到我们设置的View的布局参数,主要是调整该参数来实现软键盘弹出上移 mFrameLayoutParams = (FrameLayout.LayoutParams) mContentView.getLayoutParams(); //5.给我们设置的View添加布局变动的监听,来实现布局动作(虚拟导航栏的弹出收起也会触发该监听!) mContentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() {//软键盘弹出、系统导航栏隐藏显示均会触发这里 int heightRoot = content.getRootView().getHeight();//包含虚拟按键的高度(如果有的话) int heightDecor = content.getHeight();//不含虚拟按键的高度,貌似不包含状态栏高度 int usableHeight = computeUsableHeight();//我们setContentView设置的view的可用高度 if (usableHeight != lastUsableHeight) { lastUsableHeight = usableHeight;//防止重复变动 int heightDifference = heightDecor - usableHeight; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode()) {//如果是分屏模式 if (heightDifference > 0) {//分屏模式,只要变动了就人为弹出键盘,因为分屏可能该Activity是在手机屏幕的上方,弹出输入法只是遮盖了一丁点~如果不合适,需要你自己适配了! setHeight(heightDecor - heightDifference); //这里不能加状态栏高度哟~ } else { setHeight(FrameLayout.LayoutParams.MATCH_PARENT);//还原默认高度,不能用计算的值,因为虚拟导航栏显示或者隐藏的时候也会改变高度 } } else { if (heightDifference > (heightDecor / 4)) {//高度变动超过decor的四分之一则认为是软键盘弹出事件,为什么不用屏幕高度呢?开始以为这样在分屏模式下也可以监听,但是实测不行。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { setHeight(heightDecor - heightDifference + barStatusHeight);//这里为什么要添加状态栏高度? } else { setHeight(heightDecor - heightDifference);//这里不添加状态栏高度?不懂为什么,原版如此,就先这样吧。遇到再说~ } } else { setHeight(FrameLayout.LayoutParams.MATCH_PARENT);//还原默认高度,不能用计算的值,因为虚拟导航栏显示或者隐藏的时候也会改变高度 } } } } }); } private void setHeight(int height) { if (mFrameLayoutParams.height != height) {//不必要的更新就不要了 mFrameLayoutParams.height = height; mContentView.requestLayout();//触发布局更新 } } private int computeUsableHeight() { Rect r = new Rect(); mContentView.getWindowVisibleDisplayFrame(r); // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度 return (r.bottom - r.top); } private int computeUsableWidth() { Rect r = new Rect(); mContentView.getWindowVisibleDisplayFrame(r); // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度//横屏就是宽度 return (r.right - r.left); } //下面相关代码来自:https://github.com/yy1300326388/AndroidBarUtils/blob/master/app/src/main/java/cn/zsl/androidbarutils/utils/AndroidBarUtils.java //完整代码,全屏时有问题。 private static final String STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height"; private static final String NAV_BAR_HEIGHT_RES_NAME = "navigation_bar_height"; private static final String NAV_BAR_WIDTH_RES_NAME = "navigation_bar_width"; /** * 获取状态栏高度 * * @param context context * @return 状态栏高度 */ private static int getStatusBarHeight(Activity context) { // 获得状态栏高度 return getBarHeight(context, STATUS_BAR_HEIGHT_RES_NAME); } /** * 获取导航栏高度 * * @param activity activity * @return 导航栏高度 */ private static int getNavigationBarHeight(Activity activity) { if (hasNavBar(activity)) { // 获得导航栏高度 return getBarHeight(activity, NAV_BAR_HEIGHT_RES_NAME); } else { return 0; } } /** * 获取横屏状态下导航栏的宽度 * * @param activity activity * @return 导航栏的宽度 */ private static int getNavigationBarWidth(Activity activity) { if (hasNavBar(activity)) { // 获得导航栏高度 return getBarHeight(activity, NAV_BAR_WIDTH_RES_NAME); } else { return 0; } } /** * 获取Bar高度 * * @param context context * @param barName 名称 * @return Bar高度 */ private static int getBarHeight(Context context, String barName) { // 获得状态栏高度 int resourceId = context.getResources().getIdentifier(barName, "dimen", "android"); return context.getResources().getDimensionPixelSize(resourceId); } /** * 是否有NavigationBar * * @param activity 上下文 * @return 是否有NavigationBar */ private static boolean hasNavBar(Activity activity) { WindowManager windowManager = activity.getWindowManager(); Display d = windowManager.getDefaultDisplay(); DisplayMetrics realDisplayMetrics = new DisplayMetrics(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { d.getRealMetrics(realDisplayMetrics); } int realHeight = realDisplayMetrics.heightPixels; int realWidth = realDisplayMetrics.widthPixels; DisplayMetrics displayMetrics = new DisplayMetrics(); d.getMetrics(displayMetrics); int displayHeight = displayMetrics.heightPixels; int displayWidth = displayMetrics.widthPixels; return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0; } }

 

写文章很麻烦,转载注明出处哟。https://blog.csdn.net/passerby_b/article/details/82686662

 

你可能感兴趣的:(开发相关)