Github Demo:https://github.com/imflyn/InputManagerHelper



我们在做登录界面时经常会遇到上图的情况,输入法不但遮住了登录按钮而且第二个输入框也挡住了一部分。
网上很多文章把 android:windowSoftInputMode="adjustPan"android:windowSoftInputMode="adjustResize"这两行代码一帖,很多同学把代码复制过来,再到manifest文件里黏贴上去,试了一遍又一遍。发现完全没有效果啊!!!
所以一定要先搞清楚windowSoftInputMode中各个属性的作用。各个属性的作用 官方文档定义的很清楚,不再做具体解释,不懂的同学一定仔细看明白。

Android原生的效果无法满足,我们只能自己写实现来解决问题。

“adjustResize” 始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间。

这一句话说当android:windowSoftInputMode="adjustResize"时,window会调整尺寸,也就是说布局大小会改变并且window中的视图树会重新绘制,那么根据这个思路就诞生了两种解决方案。
1.自定义Layout,监听布局的大小改变,动态调整布局的位置
2.布局重绘时监听View树的变化,知道软键盘弹出的时机,依此来动态调整布局


有了初步的思路,那么我们再来看看该如何知道布局需要调整的距离。

Android输入法遮挡问题的解决思路_第1张图片
从图中我们可以看到登录按钮被输入法遮挡以后大概的位置,那么为了让登录按钮能够完全显示,就需要布局整体向上位移,移动的高度就是登录按钮底部到键盘最顶端的位置。
确定了思路我们就可以具体着手实现功能了。

1.自定义Layout,监听布局的大小改变

首先需要继承RelativeLayout自定义Layout,监听onSizeChanged方法:

public class KeyboardListenLayout extends RelativeLayout {    private onSizeChangedListener mChangedListener;    public KeyboardListenLayout(Context context) {        super(context);    }    public KeyboardListenLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public KeyboardListenLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        if (null != mChangedListener && 0 != oldw && 0 != oldh) {            boolean showKeyboard = h < oldh;            mChangedListener.onChanged(showKeyboard, h, oldh);        }    }    public void setOnSizeChangedListener(onSizeChangedListener listener) {        mChangedListener = listener;    }    public interface onSizeChangedListener {        void onChanged(boolean showKeyboard, int h, int oldh);    }}

在XML中引用自定义的layout,这里需要注意的是,如果你的界面中使用了Toolbar,一定不能把KeyboardListenLayout放在最外层 ,因为如果放最外层,调整布局时布局向上移动会把Toolbar挤出整个界面的可见范围内。

                                                                                                                                                                    

为布局设置OnSizeChanged监听,并在layout大小发生变化时判断软键盘是弹出还是隐藏。

private void bindKeyboardListenLayout(final KeyboardListenLayout keyboardListenLayout, final View lastVisibleView) {        keyboardListenLayout.setOnSizeChangedListener(new KeyboardListenLayout.onSizeChangedListener() {            @Override            public void onChanged(final boolean showKeyboard, final int h, final int oldh) {                new Handler().postDelayed(new Runnable() {                    @Override                    public void run() {                        if (showKeyboard) {                            //oldh代表输入法未弹出前最外层布局高度,h代表当前最外层布局高度,oldh-h可以计算出布局大小改变后输入法的高度                            //整个布局的高度-输入法高度=键盘最顶端处在布局中的位置,其实直接用h计算就可以,代码这么写为了便于理解                            int keyboardTop = oldh - (oldh - h);                            int[] location = new int[2];                            lastVisibleView.getLocationOnScreen(location);                            //登录按钮顶部在屏幕中的位置+登录按钮的高度=登录按钮底部在屏幕中的位置                            int lastVisibleViewBottom = location[1] + lastVisibleView.getHeight();                            //登录按钮底部在布局中的位置-输入法顶部的位置=需要将布局弹起多少高度                            int reSizeLayoutHeight = lastVisibleViewBottom - keyboardTop;                            //因为keyboardListenLayout的高度不包括外层的statusbar的高度和actionbar的高度                            //所以需要减去status bar的高度                            reSizeLayoutHeight -= getStatusBarHeight();                            //如果界面里有actionbar并且处于显示状态则需要少减去actionbar的高度                            if (null != (((AppCompatActivity) activity).getSupportActionBar()) && (((AppCompatActivity) activity).getSupportActionBar()).isShowing()) {                                reSizeLayoutHeight -= getActionBarHeight();                            }                            //设置登录按钮与输入法之间间距                            reSizeLayoutHeight += offset;                            if (reSizeLayoutHeight > 0)                                keyboardListenLayout.setPadding(0, -reSizeLayoutHeight, 0, 0);                        } else {                            //还原布局                            keyboardListenLayout.setPadding(0, 0, 0, 0);                        }                    }                }, 50);            }        });    }
2.监听View树的变化

添加ViewTreeObserver的监听,并通过计算键盘高度可以知道键盘是弹出还是关闭状态

 private void bindLayout(final ViewGroup viewGroup, final View lastVisibleView ) {        viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                new Handler().postDelayed(new Runnable() {                    @Override                    public void run() {                        //获得屏幕高度                        int screenHeight = viewGroup.getRootView().getHeight();                        //r.bottom - r.top计算出输入法弹起后viewGroup的高度,屏幕高度-viewGroup高度=键盘高度                        Rect r = new Rect();                        viewGroup.getWindowVisibleDisplayFrame(r);                        int keyboardHeight = screenHeight - (r.bottom - r.top);                        //当设置layout_keyboard设置完padding以后会重绘布局再次执行onGlobalLayout()                        //所以判断如果键盘高度未改变就不执行下去                        if (keyboardHeight == lastKeyBoardHeight) {                            return;                        }                        lastKeyBoardHeight = keyboardHeight;                        if (keyboardHeight < 300) {                            //键盘关闭后恢复布局                            viewGroup.setPadding(0, 0, 0, 0);                        } else {                            //计算出键盘最顶端在布局中的位置                            int keyboardTop = screenHeight - keyboardHeight;                            int[] location = new int[2];                            lastVisibleView.getLocationOnScreen(location);                            //获取登录按钮底部在屏幕中的位置                            int lastVisibleViewBottom = location[1] + lastVisibleView.getHeight();                            //登录按钮底部在布局中的位置-输入法顶部的位置=需要将布局弹起多少高度                            int reSizeLayoutHeight = lastVisibleViewBottom - keyboardTop;                            //需要多弹起一个StatusBar的高度                            reSizeLayoutHeight -= getStatusBarHeight();                            //设置登录按钮与输入法之间间距                            reSizeLayoutHeight += offset;                            if (reSizeLayoutHeight > 0)                                viewGroup.setPadding(0, -reSizeLayoutHeight, 0, 0);                        }                    }                }, 50);            }        });    }
3.在ScrollView和RecycleView中处理监听View树的变化

很多时候我们做填写表单的界面时用到ScrollView,甚至在RecycleView中也有输入框时,同样可以运用监听ViewTreeObserver的思路来实现ScrollView或RecycleView位置的调整。

private void bindViewGroup(final ViewGroup viewGroup) {        viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                new Handler().postDelayed(new Runnable() {                    @Override                    public void run() {                           //获得屏幕高度                        int screenHeight = viewGroup.getRootView().getHeight();                        //r.bottom - r.top计算出输入法弹起后viewGroup的高度,屏幕高度-viewGroup高度即为键盘高度                        Rect r = new Rect();                        viewGroup.getWindowVisibleDisplayFrame(r);                        int keyboardHeight = screenHeight - (r.bottom - r.top);                        //当设置layout_keyboard设置完padding以后会重绘布局再次执行onGlobalLayout()                        //所以判断如果键盘高度未改变就不执行下去                        if (keyboardHeight == lastKeyBoardHeight) {                            return;                        }                        lastKeyBoardHeight = keyboardHeight;                        View view = activity.getWindow().getCurrentFocus();                        if (keyboardHeight > 300 && null != view) {                            if (view instanceof TextView) {                                //计算出键盘最顶端在布局中的位置                                int keyboardTop = screenHeight - keyboardHeight;                                int[] location = new int[2];                                view.getLocationOnScreen(location);                                //获取登录按钮底部在屏幕中的位置                                int viewBottom = location[1] + view.getHeight();                                //比较输入框与键盘的位置关系,如果输入框在键盘之上的位置就不做处理                                if (viewBottom <= keyboardTop)                                    return;                                //需要滚动的距离即为输入框底部到键盘的距离                                int reSizeLayoutHeight = viewBottom - keyboardTop;                                reSizeLayoutHeight -= getStatusBarHeight();                                reSizeLayoutHeight += offset;                                if (viewGroup instanceof ScrollView) {                                    ((ScrollView) viewGroup).smoothScrollBy(0, reSizeLayoutHeight);                                } else if (viewGroup instanceof RecyclerView) {                                    ((RecyclerView) viewGroup).smoothScrollBy(0, reSizeLayoutHeight);                                }                            }                        }                    }                }, 50);            }        });    }

最后:

更详细的参考Demo在github中,我把调整高度的方法封装在一个类中,在Activity中只需要添加一行代码即可。

InputManagerHelper.attachToActivity(this).bind(viewGroup, view).offset(1);

如果有错误也希望大家能够指出,如果觉得能帮到你的话给个Star吧。

更多相关文章

  1. Android的唤醒锁和键盘锁
  2. android 常见布局及控件的属性详解
  3. Android布局动画之animateLayoutChanges与LayoutTransition
  4. Android布局详解
  5. Android一些关于分辨率和布局的设置
  6. android的五大布局详解
  7. android 使用Activity类布局时怎样让图片居中

随机推荐

  1. 关于如何使用Mumu模拟器连接电脑adb
  2. Android 自定义布局对话框避免全屏的设置
  3. Android设备内存和SD卡操作工具类
  4. 【Android-Activity】EditText的基本属性
  5. Android各个版本对应的源代码
  6. Android(安卓)Widget开发系列(二)
  7. android N 编译环境搭建
  8. 鸿蒙开发TV软件环境搭建以及简单教程
  9. android程序执行adb shell命令
  10. 详解Android(安卓)App AllowBackup配置带