全面屏适配方案,终极版,华为隐藏导航栏解决方案
近期发现华为手机隐藏导航栏的问题,个别页面可能存在有问题:我在这里说一下我的开发过程与总结
首先是在适配这种的有几种方法:其中官网中给了几个:
http://developer.huawei.com/consumer/cn/devservice/doc/50111
这里给出的解决方案可能存在有达不到自己理想的状态
华为给出的解决方案如下:
方案1:
AndroidManifest.xml 文件添加属性: <meta-data android:name="android.max_aspect" android:value="2.4" />
应用适配建议采用meta-data的方式,具体可以参考:https://developer.android.com/guide/practices/screens-distribution.html#MaxAspectRatio
方案2:
添加 android:resizeableActivity =“true”
此设置只针对Activity生效,且增加了此属性该activity也会支持分屏显示。
方案3:
修改AndroidManifest.xml文件,设置targetSdkVersion>=26,就是应用升级到O版本,不需要设置其他任何属性,默认在任何纵横比的屏幕都能全屏显示。(备注:有一种例外情况需要注意,应用如果已经适配到O版本,并且通过meta-data属性android.max_aspect或者是android:MaxAspectRatio属性设置了页面支持的最大纵横比,同时又通过android:resizeableActivity=“false”设置了页面不支持分屏,这个时候系统会按照应用自己设置的最大纵横比决定该页面是否能全屏显示,如果应用设置的最大纵横比比手机屏幕比例小,那应用还是无法全屏显示。)
这里华为推荐方案3,但是在实际中,我们想要支持分屏,可能存在有一定的工作量问题,比如说自己的页面支持分屏的支持性问题,可能会很耗时
方案2是我比较推荐的,但是方案2中可能存在有问题,今天主要就是解决这个问题的
方案1没试过,原理也不清楚,这里是google的方案,参考链接是google的,需要科学上网,具体不评判
下面我就来说下方案2
如果一般性页面:可能不涉及全屏等问题,推荐使用方案2,原因就是简单快捷,但是这里存在有问题
,如果自己的页面很长,但是需要输入法弹起,那么这里可能就会存在有一系列的问题(例如下图):
1. NestedScrollView 中EditText位于底部被输入法遮盖
2.如果解决问题1使用的是清单文件设置 strongwindowSoftInputMode="stateVisible|adjustResize
但是布局文件中fitsSystemWindows 为false或者默认是false ,导致 windowSoftInputMode 不能生效问题
(另外补充一点全屏模式也会失效,也有人说fitsSystemWindows=false 也是一种全屏)
3.但是因为某些原因fitsSystemWindows不能直接为true ,例如显现问题:(toolbar向下平移了statusbar的高度,也就是说statusbar是全白的,4.4低版本oppo存在这种情况) 因此问题冲突了
4.解决上述问题: 经过搜索找到了靠谱的解决方案以及描述:(具体位置忘了自己百度吧,代码大致如下)
public class StrongWindowSoftInputModeLayout extends RelativeLayout {
private int[] mInsets = new int[4];
public StrongWindowSoftInputModeLayout (Context context) {
super(context);
}
public StrongWindowSoftInputModeLayout (Context context, AttributeSet attrs) {
super(context, attrs);
}
public StrongWindowSoftInputModeLayout (Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public StrongWindowSoftInputModeLayout (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected final boolean fitSystemWindows(Rect insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mInsets[0] = insets.left;
mInsets[1] = insets.top;
mInsets[2] = insets.right;
insets.left = 0;
insets.top = 0;
insets.right = 0;
}
return super.fitSystemWindows(insets);
}
@Override
public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
mInsets[0] = insets.getSystemWindowInsetLeft();
Log.e("mInsets[0]", "" + mInsets[0]);
mInsets[1] = insets.getSystemWindowInsetTop();
Log.e("mInsets[1]", "" + mInsets[1]);
mInsets[2] = insets.getSystemWindowInsetRight();
Log.e("mInsets[2]", "" + mInsets[2]);
return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
insets.getSystemWindowInsetBottom()));
} else {
return insets;
}
}
}
5.经过上边的解决过后,测试发现华为手机的隐藏导航栏的问题不能有效的得到解决,导航栏隐藏后会留白以及按钮被遮挡(如下图)
![](https://images2018.cnblogs.com/blog/981541/201807/981541-20180719175519177-1534693921.png)
6.下面就是解决方案:
1.监听view变化,通过getViewTreeObserver().addOnGlobalLayoutListener 进行监听,哪怕是全屏模式都有效
2.重写fitSystemWindows 动态修改bottom
在解决的过程中发现,输入法弹起后会导致页面不对,缺少一部分或者高出一部分,这里原因就是fitSystemWindows 的bottom导致的
因为导航栏的出现和输入法的出现隐藏都会导致重新fitSystemWindows以及getViewTreeObserver的变化
核心代码如下:
boolean currentShowBar = checkNavigationBarShow(getContext(), ((Activity) getContext()).getWindow()); if (currentShowBar) { L.d("1导航" + mNowh); if (mNowh == 0 || mNowh == -1) {//还原 L.d("导航还原" ); insets.bottom = mNowh + ImmersionBar.getNavigationBarHeight((Activity) getContext()) ; } else if (mNowh > 0 && mNowh <= 150) {//从无到有 L.d("导航无到有" ); insets.bottom = mNowh; } else if (mNowh > 150) {//输入法出来了 L.d("导航输入法" ); insets.bottom = defaultNavIsShow ? mNowh + ImmersionBar.getNavigationBarHeight((Activity) getContext()) : mNowh; } else {//从有到无 L.d("导航有到无"); insets.bottom = mNowh; } } else { L.d("导航无的时候还原"); //如果改变的高度大于150则认为是输入法出现,按照输入法高度设定即可 //但是如果小于则是导航栏的隐藏,既然是隐藏则可以吧高度设定为0即可 insets.bottom = mNowh > 150 ? mNowh : 0; }
上述核心代码:主要体现了几种情况:
1.导航栏不显示进入后输入法弹窗
2.导航不显示进入后输入法弹窗然后输入法关闭
3.导航不显示进入切换为显示输入法弹窗
4.导航不显示进入切换为显示后输入法弹窗然后关闭
5.导航栏显示进入后输入法弹窗
6.导航显示进入后输入法弹窗然后输入法关闭
7.导航显示进入切换为不显示输入法弹窗
8.导航显示进入切换为不显示后输入法弹窗然后关闭
自己如果没有面屏手机,或者华为的可隐藏导航栏的手机可以通过如下命令进行模拟:
测试模式隐藏导航栏命令:adb shell settings put global policy_control immersive.navigation=*
恢复到正常(导航栏出现)adb shell settings put global policy_control null
测试解决最终结果:
下面代码中的 ImmersionBar.getNavigationBarHeight是一个获取导航栏高度的工具方法,这里我就不给了,可以百度获取
import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.Build; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.View; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.widget.RelativeLayout; import com.gyf.barlibrary.ImmersionBar; import com.suxuantech.erpsys.utils.L; /** * ......................我佛慈悲.................... * ......................_oo0oo_..................... * .....................o8888888o.................... * .....................88" . "88.................... * .....................(| -_- |).................... * .....................0\ = /0.................... * ...................___/`---'\___.................. * ..................' \\| |// '................. * ................./ \\||| : |||// \.............. * .............../ _||||| -卍-|||||- \.............. * ..............| | \\\ - /// | |............. * ..............| \_| ''\---/'' |_/ |............. * ..............\ .-\__ '-' ___/-. /............. * ............___'. .' /--.--\ `. .'___........... * .........."" '< `.___\_<|>_/___.' >' "".......... * ........| | : `- \`.;`\ _ /`;.`/ - ` : | |....... * ........\ \ `_. \_ __\ /__ _/ .-` / /....... * ....=====`-.____`.___ \_____/___.-`___.-'=====.... * ......................`=---='..................... * ..................佛祖开光 ,永无BUG................ * * @author Created by 李站旗 on 2018/4/19 0019 20:09 . * QQ:1032992210 * E-mail:lizhanqihd@163.com * @Description: NestedScrollView 中EditText位于底部被输入法遮盖,需要在 * 清单文件设置 strongwindowSoftInputMode="stateVisible|adjustResize * 但是布局文件中fitsSystemWindows 为false或者默认是false ,导致 windowSoftInputMode 不能生效 * (另外补充一点全屏模式也会失效, 也有人说fitsSystemWindows=false 也是一种全屏 ) * 但是因为某些原因fitsSystemWindows不能直接为true ,例如显现问题:(toolbar向下平移了statusbar的高度,也就是说statusbar是全白的)
*本类工具就是修复上述问题以及全面屏的适配 */ public class StrongWindowSoftInputModeLayout extends RelativeLayout { private int[] mInsets = new int[4]; private int mOldh = -1; private int mNowh = -1; /** * 进入当前页面,DecorView的高度 */ protected int mScreenHeight = 0; /** * 用来记录进入到当前页面的的时候是否在显示导航栏 */ private boolean defaultNavIsShow; public StrongWindowSoftInputModeLayout(Context context) { super(context); } public StrongWindowSoftInputModeLayout(Context context, AttributeSet attrs) { super(context, attrs); getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Rect r = new Rect(); ((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(r); if (mScreenHeight == 0) { mScreenHeight = r.bottom; defaultNavIsShow = checkNavigationBarShow(getContext(), ((Activity) getContext()).getWindow()); } L.d(defaultNavIsShow + "屏幕高度" + mScreenHeight); mNowh = mScreenHeight - r.bottom; if (mOldh != -1 && mNowh != mOldh) { fitSystemWindows(new Rect()); if (mNowh > 0) { L.d("导航出现" + mNowh); } else { L.d("导航消失" + mNowh); } } mOldh = mNowh; } }); } public StrongWindowSoftInputModeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public StrongWindowSoftInputModeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected final boolean fitSystemWindows(Rect insets) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mInsets[0] = insets.left; mInsets[1] = insets.top; mInsets[2] = insets.right; insets.left = 0; insets.top = 0; insets.right = 0; /** * 这里会出现几种情况: * 1.输入法会把导航栏带出来 * 2.无论导航栏在当前页面从无到有,还是从有到无 DecorView是固定的, * 这里说的固定指的是不退出当前页面的DecorView大小不会改变,但是如果首次进入本页面的时候自带导航栏, * 或者不带导航栏,这种情况高度是不一样的 *3.受到2的影响会导致输入法弹窗,弹窗高度会不一致,上下差异会差出来一个导航栏的高度 */ /** * 是否显示导航栏 * (这里用于显示或者隐藏导航栏时候)情况判断,如果当前没有显示导航栏,但是当前页面出现150以内的 * 页面差异,则就是当前页面导航栏从无到有再到无的情况 * 显示导航栏,的情况可能就会比较多了: * 例如:输入法弹出,无到有,或在有到无等等 * 下面核心代码:主要体现了几种情况: * 1.导航栏不显示进入后输入法弹窗 * 2.导航不显示进入后输入法弹窗然后输入法关闭 * 3.导航不显示进入切换为显示输入法弹窗 * 4.导航不显示进入切换为显示后输入法弹窗然后关闭 * 5.导航栏显示进入后输入法弹窗 * 6.导航显示进入后输入法弹窗然后输入法关闭 * 7.导航显示进入切换为不显示输入法弹窗 * 8.导航显示进入切换为不显示后输入法弹窗然后关闭 */ boolean currentShowBar = checkNavigationBarShow(getContext(), ((Activity) getContext()).getWindow()); if (currentShowBar) { // boolean actvityHave = hasSoftKeys(((Activity) getContext()).getWindowManager()); L.d("1导航" + mNowh); if (mNowh == 0 || mNowh == -1) {//还原 L.d("导航还原" ); insets.bottom = mNowh + ImmersionBar.getNavigationBarHeight((Activity) getContext()) ; } else if (mNowh > 0 && mNowh <= 150) {//从无到有 L.d("导航无到有" ); insets.bottom = mNowh; } else if (mNowh > 150) {//输入法出来了 L.d("导航输入法" ); insets.bottom = defaultNavIsShow ? mNowh + ImmersionBar.getNavigationBarHeight((Activity) getContext()) : mNowh; } else {//从有到无 L.d("导航有到无"); insets.bottom = mNowh; } } else { L.d("导航无的时候还原"); //如果改变的高度大于150则认为是输入法出现,按照输入法高度设定即可 //但是如果小于则是导航栏的隐藏,既然是隐藏则可以吧高度设定为0即可 insets.bottom = mNowh > 150 ? mNowh : 0; } } return super.fitSystemWindows(insets); } private boolean hasNavigationBar(WindowManager windowManager) { Display d = windowManager.getDefaultDisplay(); DisplayMetrics realDisplayMetrics = new DisplayMetrics(); 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; } /** * 判断虚拟导航栏是否显示 * * @param context 上下文对象 * @param window 当前窗口 * @return true(显示虚拟导航栏),false(不显示或不支持虚拟导航栏) */ public static boolean checkNavigationBarShow(@NonNull Context context, @NonNull Window window) { boolean show; Display display = window.getWindowManager().getDefaultDisplay(); Point point = new Point(); display.getRealSize(point); View decorView = window.getDecorView(); Configuration conf = context.getResources().getConfiguration(); if (Configuration.ORIENTATION_LANDSCAPE == conf.orientation) { View contentView = decorView.findViewById(android.R.id.content); show = (point.x != contentView.getWidth()); } else { Rect rect = new Rect(); decorView.getWindowVisibleDisplayFrame(rect); show = (rect.bottom != point.y); } return show; } @TargetApi(14) 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.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; } @Override public final WindowInsets onApplyWindowInsets(WindowInsets insets) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { mInsets[0] = insets.getSystemWindowInsetLeft(); Log.e("mInsets[0]", "" + mInsets[0]); mInsets[1] = insets.getSystemWindowInsetTop(); Log.e("mInsets[1]", "" + mInsets[1]); mInsets[2] = insets.getSystemWindowInsetRight(); Log.e("mInsets[2]", "" + mInsets[2]); return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0, insets.getSystemWindowInsetBottom())); } else { return insets; } } }