android 沉浸状态栏下fitSystemWindow与键盘挡住输入框问题
在将状态栏改为沉浸时遇到了如下一个问题:fitsSystemWindows设置为true后,界面就无法全屏,因为顶部有一个状态栏高度的padding;不设置fitsSystemWindows,adjustResize模式无法用于沉浸全屏界面,导至输入框无法跟随键盘。
沉浸状态栏使用的工具:ImmersionBar
问题分析
-
fitSystemWindow
如果多个View设置了
fitsSystemWindows=”true”
,只有最外层view起作用,从最外层设置了fitsSystemWindows
的view开始计算padding,如果在布局中不是最外层控件设置fitsSystemWindows=”true”
, 那么设置的那个控件高度会多出一个状态栏高度。若有多个view设置了,因第一个view已经消耗掉insect,其他view设置了也会被系统忽略。 -
键盘挡住输入框问题
方法一、在AndroidManifest.xml对应的Activity里添加
windowSoftInputMode
属性adjustResize:调整activity主窗口的尺寸来为屏幕上的软键盘腾出空间
adjustPan:自动平移窗口的内容,使当前焦点永远不被键盘遮盖,让用户始终都能看到其输入的内容。只有关闭软键盘 才能看到因平移被遮盖的内容。
方法二、在布局最外层使用ScrollView
解决方案
去除fitSystemWindow,然后使用adjustResize以保证输入框跟随软键盘,所以现在只要解决一个问题:adjustResize在全屏时失效。
最后通过监听addOnGlobalLayoutListener
在输入法弹出时改变rootView或DecorView的高度,使view高度=屏幕高度-输入法高度。代码如下:
import android.app.Activity;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
public class FullScreenInputWorkaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
private static final String TAG = "AndroidBug5497Workaround";
public static FullScreenInputWorkaround assistActivity(Activity activity, View contentView, InputShowListener inputShowListener) {
return new FullScreenInputWorkaround(activity, contentView, inputShowListener);
}
private Activity activity;
private View mChildOfContent;
private int usableHeightPrevious;
private ViewGroup.LayoutParams layoutParams;
private ViewTreeObserver.OnGlobalLayoutListener listener;
private FullScreenInputWorkaround(Activity activity, View contentView, InputShowListener inputShowListener) {
this.activity = activity;
this.inputShowListener = inputShowListener;
mChildOfContent = contentView;
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(listener = new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
layoutParams = mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
layoutParams.height = usableHeightSansKeyboard - heightDifference;
if (inputShowListener != null) {
inputShowListener.inputShow(true);
}
} else {
// keyboard probably just became hidden
layoutParams.height = usableHeightSansKeyboard;
if (inputShowListener != null) {
inputShowListener.inputShow(false);
}
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
//这个判断是为了解决19之后的版本在弹出软键盘时,键盘和推上去的布局(adjustResize)之间有黑色区域的问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return (r.bottom - r.top) + statusBarHeight;
}
return (r.bottom - r.top);
}
public void finish() {
if(mChildOfContent != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mChildOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
private InputShowListener inputShowListener;
public interface InputShowListener {
void inputShow(boolean show);
}
}
详细内容见Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南
问题:
-
因addOnGlobalLayoutListener监听每次都会改变根布局高度,会导致布局与虚拟导航栏重叠。
解决方案:在修改布局高度时判断是否要减去导航栏高度。
修改后的代码如下:
public class FullScreenInputWorkaround { // For more information, see https://code.google.com/p/android/issues/detail?id=5497 // To use this class, simply invoke assistActivity() on an Activity that already has its content view set. private static final String TAG = "AndroidBug5497Workaround"; public static FullScreenInputWorkaround assistActivity(Activity activity, View contentView, InputShowListener inputShowListener) { return new FullScreenInputWorkaround(activity, contentView, inputShowListener); } private Activity activity; private View mChildOfContent; private int usableHeightPrevious; private ViewGroup.LayoutParams layoutParams; private ViewTreeObserver.OnGlobalLayoutListener listener; private FullScreenInputWorkaround(Activity activity, View contentView, InputShowListener inputShowListener) { this.activity = activity; this.inputShowListener = inputShowListener; mChildOfContent = contentView; mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(listener = new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { possiblyResizeChildOfContent(); } }); layoutParams = mChildOfContent.getLayoutParams(); } private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != usableHeightPrevious) { int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); int heightDifference = usableHeightSansKeyboard - usableHeightNow; if (heightDifference > (usableHeightSansKeyboard / 4)) { // keyboard probably just became visible layoutParams.height = usableHeightSansKeyboard - heightDifference; if (inputShowListener != null) { inputShowListener.inputShow(true); } } else { // keyboard probably just became hidden layoutParams.height = usableHeightSansKeyboard - (NavigationBoomUtils.navigationBarExist(activity) && heightDifference != 0 ? NavigationBoomUtils.getNavigationBarHeight(activity) : 0); if (inputShowListener != null) { inputShowListener.inputShow(false); } } if (mChildOfContent != null) { mChildOfContent.requestLayout(); } usableHeightPrevious = usableHeightNow; } } private int computeUsableHeight() { Rect frame = new Rect(); if (activity != null) { activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); } int statusBarHeight = frame.top; Rect r = new Rect(); if (mChildOfContent != null) { mChildOfContent.getWindowVisibleDisplayFrame(r); } //这个判断是为了解决19之后的版本在弹出软键盘时,键盘和推上去的布局(adjustResize)之间有黑色区域的问题 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { return (r.bottom - r.top) + statusBarHeight; } return (r.bottom - r.top); } public void finish() { if (mChildOfContent != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { //计算完成,要移除监听 mChildOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } } private InputShowListener inputShowListener; public interface InputShowListener { void inputShow(boolean show); } }
NavigationBoomUtils代码如下:
public class NavigationBoomUtils { private static final String TAG = NavigationBoomUtils.class.getSimpleName(); private NavigationBoomUtils() { throw new RuntimeException("NavUtils cannot be initialized!"); } /** * 获取底部虚拟导航栏高度 * * @param activity * @return */ public static int getNavigationBarHeight(Context activity) { try { boolean hasNavigationBar = navigationBarExist(scanForActivity(activity)) && !vivoNavigationGestureEnabled(activity); if (!hasNavigationBar) {//如果不含有虚拟导航栏,则返回高度值0 return 0; } Resources resources = activity.getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); //获取NavigationBar的高度 return resources.getDimensionPixelSize(resourceId); }catch (Throwable e){ } return 0; } /** * 通过获取不同状态的屏幕高度对比判断是否有NavigationBar * https://blog.csdn.net/u010042660/article/details/51491572 * https://blog.csdn.net/android_zhengyongbo/article/details/68941464 */ public static boolean navigationBarExist(Activity activity) { WindowManager windowManager = activity.getWindowManager(); Display d = windowManager.getDefaultDisplay(); DisplayMetrics realDisplayMetrics = new DisplayMetrics(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 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; } /** * 解决java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity问题 * https://blog.csdn.net/yaphetzhao/article/details/49639097 */ public static Activity scanForActivity(Context cont) { if (cont == null) return null; else if (cont instanceof Activity) return (Activity) cont; else if (cont instanceof ContextWrapper) return scanForActivity(((ContextWrapper) cont).getBaseContext()); return null; } /** * 获取vivo手机设置中的"navigation_gesture_on"值,判断当前系统是使用导航键还是手势导航操作 * * @param context app Context * @return false 表示使用的是虚拟导航键(NavigationBar), true 表示使用的是手势, 默认是false * https://blog.csdn.net/weelyy/article/details/79284332#更换部分被拉伸的图片资源文件 */ public static boolean vivoNavigationGestureEnabled(Context context) { int val = Settings.Secure.getInt(context.getContentResolver(), "navigation_gesture_on", 0); return val != 0; }
nativationBottomUtils参考 :https://www.cnblogs.com/whycxb/p/7635745.html
参考:https://www.jianshu.com/p/a62351cea3ed