近期發現華為手機隱藏導航欄的問題,個別頁面可能存在有問題:我在這里說一下我的開發過程與總結
首先是在適配這種的有幾種方法:其中官網中給了幾個:
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.經過上邊的解決過后,測試發現華為手機的隱藏導航欄的問題不能有效的得到解決,導航欄隱藏后會留白以及按鈕被遮擋(如下圖)

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; } } }

