Android输入法遮挡问题的解决思路
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树的变化,知道软键盘弹出的时机,依此来动态调整布局
有了初步的思路,那么我们再来看看该如何知道布局需要调整的距离。
从图中我们可以看到登录按钮被输入法遮挡以后大概的位置,那么为了让登录按钮能够完全显示,就需要布局整体向上位移,移动的高度就是登录按钮底部到键盘最顶端的位置。
确定了思路我们就可以具体着手实现功能了。
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吧。
更多相关文章
- Android的唤醒锁和键盘锁
- android 常见布局及控件的属性详解
- Android布局动画之animateLayoutChanges与LayoutTransition
- Android布局详解
- Android一些关于分辨率和布局的设置
- android的五大布局详解
- android 使用Activity类布局时怎样让图片居中