继续创造,加快成长!这是我参加「日新计划 10 月更文挑战」的第2天,点击查看活动详情

前言

首先在文章开始之前先抛出几个问题,让咱们带着疑问往下走:

什么是窗口操控?

在Android手机状态栏,导航栏,输入法等这些与app无关,可是需求合作app一同运用的窗口部件。

之前咱们都是怎么办理窗口的?

在window上增加各种flag,有些flag只适应于指定的版别,而某些flag在高版别不能生效,清除flag也相对费事。

WindowInsetsController 又能处理什么问题?

WindowInsetsController 的推出是来取代之前杂乱费事的窗口操控,之前增加各种Flag不容易了解,而运用Api的办法来办理窗口,更加的语义化,更加的便利了解,能够说看到Api办法就知道是什么意思,运用起来却是很便利。

WindowInsetsController 就真的没有兼容性问题吗?

尽管flag这欠好那欠好,那咱们直接用 WindowInsetsController 就能够了吗?可是 WindowInsetsController 需求Android 11 (R) API 30 才干运用。尽管谷歌又推出了 ViewCompat 的Api 向下兼容到5.0版别,可是5.0以下的版别怎么办?

可能现在的一些新运用都是5.0以上了,可是这个兼容到哪一个版别也并不是咱们开发者说了算,万一要兼容5.0一下怎么办?

就算咱们的运用是支撑5.0以上,那么咱们运用 WindowInsetsController 与 windowInsets 就能够了吗?并不是!

就算是 WindowInsetsController 或它的兼容包 WindowInsetsControllerCompat 也并不是悉数就能用的,也会有兼容性问题。部分设备不能用,部分版别不能用等等。

说了这么多,到底怎么运用?下面一同来看看吧!

一、WindowInsetsController 与 windowInsets 的运用

WindowInsetsController 能办理的东西不少,可是咱们常用的便是状态栏,导航栏,软键盘的一些办理,下面咱们就基于这几点来看看到底怎么操控

1.1 状态栏

榜首种办法:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        window.decorView.setOnApplyWindowInsetsListener { view: View, windowInsets: WindowInsets ->
            //状态栏
            val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
            //状态栏高度
            val statusBarHeight = Math.abs(statusBars.bottom - statusBars.top)
            windowInsets
        }
    }

第二种办法

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        val windowInsets = window.decorView.rootWindowInsets
        //状态栏
        val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
        //状态栏高度
        val statusBarHeight = Math.abs(statusBars.bottom - statusBars.top)
        YYLogUtils.w("statusBarHeight2:$statusBarHeight")
    }

第三种办法

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
         window.decorView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(view: View?) {
                val windowInsets = window.decorView.rootWindowInsets
                 //状态栏
                val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
                //状态栏高度
                val statusBarHeight = Math.abs(statusBars.bottom - statusBars.top)
                YYLogUtils.w("statusBarHeight2:$statusBarHeight")
            }
            override fun onViewDetachedFromWindow(view: View?) {
            }
        })
    }

榜首种办法和第三种办法是运用监听回调的办法获取到状态栏高度,第二种办法是运用同步的办法获取状态栏高度,可是第二种办法有坑,它无法在 onCreate 中运用,直接运用会空指针的。

为什么?其实也能了解,onCreate 办法其实便是解析布局增加布局,并没有展现出来,所以咱们第三种办法运用了监听,当View现已 OnAttach 之后咱们再调用办法才干运用。

1.2 导航栏

榜首种办法:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        window.decorView.setOnApplyWindowInsetsListener { view: View, windowInsets: WindowInsets ->
                //导航栏
                val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
                //导航栏高度
                val navigationHeight = Math.abs(statusBars.bottom - statusBars.top)
                YYLogUtils.w("navigationHeight:$navigationHeight")
            windowInsets
        }
    }

第二种办法

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        val windowInsets = window.decorView.rootWindowInsets
               //导航栏
                val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
                //导航栏高度
                val navigationHeight = Math.abs(statusBars.bottom - statusBars.top)
                YYLogUtils.w("navigationHeight:$navigationHeight")
        YYLogUtils.w("statusBarHeight2:$statusBarHeight")
    }

第三种办法

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
         window.decorView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(view: View?) {
                val windowInsets = window.decorView.rootWindowInsets
                //导航栏
                val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
                //导航栏高度
                val navigationHeight = Math.abs(statusBars.bottom - statusBars.top)
                YYLogUtils.w("navigationHeight:$navigationHeight")
            }
            override fun onViewDetachedFromWindow(view: View?) {
            }
        })
    }

其实导航栏和状态栏是一样样的,这儿打印Log如下:

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

能够看到其实也更推荐咱们运用第三种办法,因为它是在 onAttach 中调用,而其他的办法需求在 onResume 之后调用,相对来说第三种办法更快一些。

1.3 软键盘

相同的咱们能够操作软键盘的翻开,收起,还能监听软键盘弹起的动画的Value,获取当时的值,这个也是巨便利

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        //翻开键盘
        window?.insetsController?.show(WindowInsets.Type.ime())
//      mBinding.llRoot.windowInsetsController?.show(WindowInsets.Type.ime())
        window.decorView.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
            override fun onProgress(insets: WindowInsets, runningAnimations: MutableList<WindowInsetsAnimation>): WindowInsets {
                val isVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
                val keyboardHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
                //当时是否展现
                YYLogUtils.w("isVisible = $isVisible")
                //当时的高度进展回调
                YYLogUtils.w("keyboardHeight = $keyboardHeight")
                return insets
            }
        })
    }

咱们能够经过 window?.insetsController 或者 window.decorView.windowInsetsController? 来获取 WindowInsetsController 对象,经过 Controller 对象咱们就能操作软键盘了。

打印Log如下:

封闭软键盘:

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

翻开软键盘:

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

1.4 其他

除了软键盘的操作,咱们还能进行其他的操作

    window?.insetsController?.apply {
        show(WindowInsetsCompat.Type.ime())
        show(WindowInsetsCompat.Type.statusBars())
        show(WindowInsetsCompat.Type.navigationBars())
        show(WindowInsetsCompat.Type.systemBars())
    }

不过都不是太常用。

除此之外咱们还能设置状态栏与导航栏的文本图标色彩

    window?.insetsController?.apply {
       setAppearanceLightNavigationBars(true)
       setAppearanceLightStatusBars(false)
    }

不过也并欠好用,内部有兼容性问题。

二、兼容库 WindowInsetsControllerCompat 的运用

为了兼容低版别的Android,咱们能够运用 implementation 'androidx.core:core:1.5.0' 以上的版别,内部即可运用 WindowInsetsControllerCompat 兼容库,最多能够支撑到5.0以上版别。

这儿我运用的是 implementation 'androidx.core:core:1.6.0'版别作为示例。

2.1 状态栏

咱们关于前面的版别,相同的咱们运用三种办法来获取

办法一:

    ViewCompat.setOnApplyWindowInsetsListener(view, new OnApplyWindowInsetsListener() {
        @Override
        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
            Insets statusInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars());
            int top = statusInsets.top;
            int bottom = statusInsets.bottom;
            int height = Math.abs(bottom - top);
            return insets;
        }
    });

办法二:

        WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(view);
            assert windowInsets != null;
            int top = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
            int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).bottom;
            int height = Math.abs(bottom - top);

办法三:


    view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);
                 assert windowInsets != null;
                int top = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
                int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).bottom;
                int height = Math.abs(bottom - top);
                }
            @Override
            public void onViewDetachedFromWindow(View v) {
            }
        });

和R的版别共同,我更推荐运用第三种办法,当View现已 OnAttach 之后咱们再调用办法,更方便一点。

2.2 导航栏

办法一:

    ViewCompat.setOnApplyWindowInsetsListener(view, new OnApplyWindowInsetsListener() {
        @Override
        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
            Insets navInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
            int top = navInsets.top;
            int bottom = navInsets.bottom;
            int height = Math.abs(bottom - top);
            return insets;
        }
    });

办法二:

        WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(view);
            assert windowInsets != null;
            int top = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).top;
            int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
            int height = Math.abs(bottom - top);

办法三:


    view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);
                 assert windowInsets != null;
                int top = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).top;
                int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
                int height = Math.abs(bottom - top);
                }
            @Override
            public void onViewDetachedFromWindow(View v) {
            }
        });

和R版别的共同,这样即可正确的获取到底部导航栏的高度

2.3 软键盘

操作软键盘的办法和R的版别差不多,仅仅调用的类变成了兼容类。

    ViewCompat.setWindowInsetsAnimationCallback(window.decorView, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
         override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat {
                val isVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
                val keyboardHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
                //当时是否展现
                YYLogUtils.w("isVisible = $isVisible")
                //当时的高度进展回调
                YYLogUtils.w("keyboardHeight = $keyboardHeight")
                return insets
            }
        })
        ViewCompat.getWindowInsetsController(findViewById(android.R.id.content))?.apply {
            show(WindowInsetsCompat.Type.ime())
        }

这样的兼容类,其实并没有彻底兼容,低版别的部分手机还是拿不到进展。

那么咱们能够在兼容类上再做一个版别的兼容


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    activity.window.decorView.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
        override fun onProgress(insets: WindowInsets, animations: MutableList<WindowInsetsAnimation>): WindowInsets {
            val imeHeight = insets.getInsets(WindowInsets.Type.ime()).bottom
             listener.onKeyboardHeightChanged(imeHeight)
            return insets
        }
    })
} else {
    ViewCompat.setOnApplyWindowInsetsListener(activity.window.decorView) { _, insets ->
        val posBottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
        listener.onKeyboardHeightChanged(posBottom)
        insets
    }
}

无赖,兼容类的软键盘监听作用并欠好,只能运用以前的办法。

打印的Log如下:

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

2.4 其他

相同的咱们能够运用兼容类来操作状态栏,导航栏,软键盘等

    View decorView = activity.findViewById(android.R.id.content);
    WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(decorView);
    if (controller != null) {
        controller.show(WindowInsetsCompat.Type.navigationBars());
        controller.show(WindowInsetsCompat.Type.statusBars());
        controller.show(WindowInsetsCompat.Type.ime());    
    }

留意坑点,假如运用的是Activity对象,这儿推荐运用 findViewById(android.R.id.content) 的办法来获取View来操作,假如是经过window.decorView 来获取 Controller 有可能为null。

操控导航栏,状态栏的文本图标色彩

    WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(activity.findViewById(android.R.id.content));
    if (controller != null) {
      controller.setAppearanceLightNavigationBars(false);
      controller.setAppearanceLightStatusBars(false);    
    }

留意坑点,看起来很夸姣,其实底部导航栏只要版别R以上才干操控,而顶部状态栏的色彩操控则有很大的兼容性问题,简直不可用,我目前测试过的机型只要一款能生效。

三、实战中兼容库的兼容问题

在运用的开发中咱们能够用 WindowInsetsControllerCompat 吗?它能处理咱们那些痛点呢?

当然能够用,在状态栏高度,导航栏高度,判别状态栏导航栏是否显现,监听软键盘的高度等一系列场景中的确能起到很好的作用。

为什么要用 WindowInsetsControllerCompat ?

看之前的状态栏高度,导航栏高度获取,都是监听的办法获取啊,假如想运用我还需求加个回调才行,这儿就引进一个问题,一定要异步运用吗?运用同步行不行?

博主,你这个太杂乱了,咱们之前的办法都是直接一个静态办法就行了。


    /**
     * 老的办法获取状态栏高度
     */
    private static int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
    /**
     * 老的办法获取导航栏的高度
     */
    private static int getNavigationBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

相信咱们包括我都是这么用的,的确简单好用有快速又便捷,搞得这些监听啊回调啊有个diao用?

可是可是,这些值仅仅预设的值,部分手机厂商会修正不运用这些值,而咱们运用 WindowInsets 的办法来获取的话,是其真实展现的值。

例如状态栏的高度,早前一些刘海屏的手机,假如刘海做的比较大,比较高,状态栏的高度都显现不下,那么就会加大状态栏高度,那么运用预设值就会有问题,显得比较小。

再比方现在盛行的全面屏手机,全面屏手势,因为要兼容各种操作形式,底部的导航栏高度就彻底不是预设值,假如还是用老办法就会踩大坑了。

如下图,非常典型的比方,真实的导航栏是黑色,运用老办法获取到的导航栏高度为深灰色。

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

再比方判别导航栏是否存在,因为部分手机能够手动躲藏导航栏,还能在设置中动态改变交互形式,全面屏手势,底部三大金刚键等。

咱们运用的老的办法,大约都是这样判别:

 /**
     * 老办法,并欠好用
     */
    public static boolean isNavBarVisible(Context context) {
        boolean isVisible = false;
        if (!(context instanceof Activity)) {
            return false;
        }
        Activity activity = (Activity) context;
        Window window = activity.getWindow();
        ViewGroup decorView = (ViewGroup) window.getDecorView();
        for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
            final View child = decorView.getChildAt(i);
            final int id = child.getId();
            if (id != View.NO_ID) {
                String resourceEntryName = context.getResources().getResourceEntryName(id);
                if ("navigationBarBackground".equals(resourceEntryName) && child.getVisibility() == View.VISIBLE) {
                    isVisible = true;
                    break;
                }
            }
        }
        if (isVisible) {
            // 关于三星手机,android10以下做单独的判别
            if (isSamsung()
                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
                    && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
                try {
                    return Settings.Global.getInt(activity.getContentResolver(), "navigationbar_hide_bar_enabled") == 0;
                } catch (Exception ignore) {
                }
            }
            int visibility = decorView.getSystemUiVisibility();
            isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
        }
        return isVisible;
    }
    private static final String[] ROM_SAMSUNG = {"samsung"};
    private static boolean isSamsung() {
        final String brand = getBrand();
        final String manufacturer = getManufacturer();
        return isRightRom(brand, manufacturer, ROM_SAMSUNG);
    }
    private static String getBrand() {
        try {
            String brand = Build.BRAND;
            if (!TextUtils.isEmpty(brand)) {
                return brand.toLowerCase();
            }
        } catch (Throwable ignore) {/**/}
        return "UNKNOWN";
    }
    private static String getManufacturer() {
        try {
            String manufacturer = Build.MANUFACTURER;
            if (!TextUtils.isEmpty(manufacturer)) {
                return manufacturer.toLowerCase();
            }
        } catch (Throwable ignore) {/**/}
        return "UNKNOWN";
    }
    private static boolean isRightRom(final String brand, final String manufacturer, final String... names) {
        for (String name : names) {
            if (brand.contains(name) || manufacturer.contains(name)) {
                return true;
            }
        }
        return false;
    }

中心思路是直接遍历 decorView 找到导航栏的控件,去判别它是否躲藏还是显现。。。

其实不说全面屏手机了,便是我的老华为 7.0系统的手机都判别的不准确,巨坑!

比方,全面屏手机的导航栏判别:

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

看到我全面屏手势的小横杠杠的了吗?我明明没有底部导航栏了,居然判别我存在导航栏,还给一个彻底不合理的状态栏高度。

我醉了,真的是够了!

而以上办法都是能够经过 WindowInsets 来处理的,也便是为什么推荐部分场景下的一些作用还是运用 WindowInsets 来做为好。

那么咱们真的在实战中运用了 WindowInsetsControllerCompat 就完美了吗?就没坑了吗?

no no no, 答案是否定的。你根本不知道会发生什么兼容性的问题。(兼容性可用说是咱们安卓人的终身之敌)

WindowInsetsController 的兼容性问题

咱们知道 WindowInsetsController 是安卓11以上用的,而 WindowInsetsControllerCompat 是安卓5以上可用的兼容包,那么 WindowInsetsControllerCompat 的兼容包就没有兼容性问题了吗?一样有!

例如一些 WindowInsetsControllerCompat 的获取办法,设置状态栏文本图标的色彩办法,设置导航栏的图标色彩办法。设置状态栏导航栏的背景色彩等。

假如 WindowInsetsController / WindowInsets的办法在某些作用上并没有那么好用,那么咱们是不是还是要用flag的办法来完成这些作用,在一些兼容性好的办法上,那么咱们就能够用 WindowInsetsController / WindowInsets的办法的办法来完成,这样是不是就能相对完美的完成咱们想要的作用了。

所以我封装了这样的东西类。

四、推荐的东西类

此东西类5.0以上可用,记录了一些状态栏与导航栏操作的常用的办法。


public class StatusBarHostUtils {
    // =======================  StatusBar begin ↓ =========================
    /**
     * 5.0以上设置沉溺式状态
     */
    public static void immersiveStatusBar(Activity activity) {
        //办法一
        //false 表明沉溺,true表明不沉溺
//        WindowCompat.setDecorFitsSystemWindows(activity.getWindow(), false);
        //办法二:增加Flag,两种办法都能够,都是5.0以上运用
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            View decorView = window.getDecorView();
            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility()
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.setStatusBarColor(Color.TRANSPARENT);
        }
    }
    /**
     * 设置当时页面的状态栏色彩,运用宿主计划一般不用这个修正色彩,仅仅用于沉溺式之后修正状态栏色彩为通明
     */
    public static void setStatusBarColor(Activity activity, int statusBarColor) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(statusBarColor);
        }
    }
    /**
     * 6.0版别及以上能够设置黑色的状态栏文本
     *
     * @param activity
     * @param dark     是否需求黑色文本
     */
    public static void setStatusBarDarkFont(Activity activity, boolean dark) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Window window = activity.getWindow();
            View decorView = window.getDecorView();
            if (dark) {
                decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            } else {
                decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            }
        }
    }
    /**
     * 老的办法获取状态栏高度
     */
    private static int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
    /**
     * 新办法获取状态栏高度
     */
    public static void getStatusBarHeight(Activity activity, HeightValueCallback callback) {
        getStatusBarHeight(activity.findViewById(android.R.id.content), callback);
    }
    /**
     * 新办法获取状态栏高度
     */
    public static void getStatusBarHeight(View view, HeightValueCallback callback) {
        boolean attachedToWindow = view.isAttachedToWindow();
        if (attachedToWindow) {
            WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(view);
            assert windowInsets != null;
            int top = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
            int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).bottom;
            int height = Math.abs(bottom - top);
            if (height > 0) {
                callback.onHeight(height);
            } else {
                callback.onHeight(getStatusBarHeight(view.getContext()));
            }
        } else {
            view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);
                    assert windowInsets != null;
                    int top = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top;
                    int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).bottom;
                    int height = Math.abs(bottom - top);
                    if (height > 0) {
                        callback.onHeight(height);
                    } else {
                        callback.onHeight(getStatusBarHeight(view.getContext()));
                    }
                }
                @Override
                public void onViewDetachedFromWindow(View v) {
                }
            });
        }
    }
    // =======================  NavigationBar begin ↓ =========================
    /**
     * 5.0以上-设置NavigationBar底部导航栏的沉溺式
     */
    public static void immersiveNavigationBar(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            View decorView = window.getDecorView();
            decorView.setSystemUiVisibility(decorView.getSystemUiVisibility()
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.setNavigationBarColor(Color.TRANSPARENT);
        }
    }
    /**
     * 设置底部导航栏的色彩
     */
    public static void setNavigationBarColor(Activity activity, int navigationBarColor) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setNavigationBarColor(navigationBarColor);
        }
    }
    /**
     * 底部导航栏的Icon色彩白色和灰色切换,高版别系统才会生效
     */
    public static void setNavigationBarDrak(Activity activity, boolean isDarkFont) {
        WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(activity.findViewById(android.R.id.content));
        if (controller != null) {
            if (!isDarkFont) {
                controller.setAppearanceLightNavigationBars(false);
            } else {
                controller.setAppearanceLightNavigationBars(true);
            }
        }
    }
    /**
     * 老的办法获取导航栏的高度
     */
    private static int getNavigationBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
    /**
     * 获取底部导航栏的高度
     */
    public static void getNavigationBarHeight(Activity activity, HeightValueCallback callback) {
        getNavigationBarHeight(activity.findViewById(android.R.id.content), callback);
    }
    /**
     * 获取底部导航栏的高度
     */
    public static void getNavigationBarHeight(View view, HeightValueCallback callback) {
        boolean attachedToWindow = view.isAttachedToWindow();
        if (attachedToWindow) {
            WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(view);
            assert windowInsets != null;
            int top = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).top;
            int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
            int height = Math.abs(bottom - top);
            if (height > 0) {
                callback.onHeight(height);
            } else {
                callback.onHeight(getNavigationBarHeight(view.getContext()));
            }
        } else {
            view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);
                    assert windowInsets != null;
                    int top = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).top;
                    int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
                    int height = Math.abs(bottom - top);
                    if (height > 0) {
                        callback.onHeight(height);
                    } else {
                        callback.onHeight(getNavigationBarHeight(view.getContext()));
                    }
                }
                @Override
                public void onViewDetachedFromWindow(View v) {
                }
            });
        }
    }
    // =======================  NavigationBar StatusBar Hide Show begin ↓ =========================
    /**
     * 显现躲藏底部导航栏(留意不是沉溺式作用)
     */
    public static void showHideNavigationBar(Activity activity, boolean isShow) {
        View decorView = activity.findViewById(android.R.id.content);
        WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(decorView);
        if (controller != null) {
            if (isShow) {
                controller.show(WindowInsetsCompat.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH);
            } else {
                controller.hide(WindowInsetsCompat.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
            }
        }
    }
    /**
     * 显现躲藏顶部的状态栏(留意不是沉溺式作用)
     */
    public static void showHideStatusBar(Activity activity, boolean isShow) {
        View decorView = activity.findViewById(android.R.id.content);
        WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(decorView);
        if (controller != null) {
            if (isShow) {
                controller.show(WindowInsetsCompat.Type.statusBars());
            } else {
                controller.hide(WindowInsetsCompat.Type.statusBars());
            }
        }
    }
    /**
     * 当时是否显现了底部导航栏
     */
    public static void hasNavigationBars(Activity activity, BooleanValueCallback callback) {
        View decorView = activity.findViewById(android.R.id.content);
        boolean attachedToWindow = decorView.isAttachedToWindow();
        if (attachedToWindow) {
            WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(decorView);
            if (windowInsets != null) {
                boolean hasNavigationBar = windowInsets.isVisible(WindowInsetsCompat.Type.navigationBars()) &&
                        windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0;
                callback.onBoolean(hasNavigationBar);
            }
        } else {
            decorView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);
                    if (windowInsets != null) {
                        boolean hasNavigationBar = windowInsets.isVisible(WindowInsetsCompat.Type.navigationBars()) &&
                                windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0;
                        callback.onBoolean(hasNavigationBar);
                    }
                }
                @Override
                public void onViewDetachedFromWindow(View v) {
                }
            });
        }
    }
}

关于状态栏的沉溺式两种办法都能够,而导航栏的沉溺式运用的Flag,修正状态栏与导航栏的背景色彩运用flag,修正状态栏文本色彩运用flag,修正导航栏的图片色彩运用的 controller,获取导航栏状态栏的高度运用的 controller ,判别导航栏是否存在运用的 controller。

一些作用如图:

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑

总结

因为运用了 WindowInsetsController与其兼容库,所以咱们界说的东西类在5.0版别以上。

假如运用flag的办法,那么咱们能够兼容到更低的版别,这一点还请知悉。

在5.0版别以上运用东西类,咱们有些兼容性欠好的运用的是flag计划,而有些作用比较好的咱们运用的是 indowInsetsController 计划。

此计划并非什么威望计划,仅仅我个人在开发过程中踩坑踩出来的,对我个人来说相对完善的一个计划,在实战开发中我个人觉得还算能用。

当然因为各种原因受限,个人水平也有限,不免有凭空捏造的情况,假如你有更好的计划或者觉得有错漏的地方,还望指出来咱们一同交流学习进步。

后期我也会针对本文进行一些扩展,会出一些相关的细节文章与一些作用的完成。

好了,本文的悉数代码与Demo都现已开源。有爱好能够看这儿。项目会继续更新,咱们能够关注一下。

假如感觉本文对你有一点点的启发,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

操作Android窗口的几种方式?WindowInsets与其兼容库的使用与踩坑