我敢说,这就是 github 上最好用的 Android 状态栏导航栏库,没有之一

转自:掘金-Zackratos 

https://juejin.cn/post/6903165109485436935

从 Android 4.4 开始,Android 支持了状态栏和导航栏的透明效果,并在 Android 5.0 上加强了这种效果,但是实现方法却和 Android 4.4 完全不同,之后在 Android 6.0、Android 8.0 以及 Android 10.0 上都增加了一些新的特性,使得在不同 Android 版本上,要实现状态栏和导航栏同样的效果异常困难,为此,我很久以前写了一个库 UltimateBar,但是随着时间的推移以及本人的成长,我越发觉得这个库设计的不好,存在太多不合理的地方,有较多的 bug 无法解决,后来我决定设计一个更完美更强大更好用的库,于是便有了今天的主角。

UltimateBarX

这是个全新的库,采用了全新的实现方案,在设计的过程中,我考虑了各种业务场景,适配各个 Android 版本和各家手机厂商,经过几个非正式版本的迭代,现在我终于敢拍胸脯自信地说,这就是 github 上最好用的 Android 状态栏导航栏的库,没有之一

关于命名

Ultimate 翻译过来是「终极」的意思,在设计第一个库的时候,我写了一篇文章叫透明状态栏和导航栏的终极解决方案(虽然后来觉得并不终极),于是就命名为了「UltimateBar」,现在命名为「UltimateBarX」,是借鉴了 Google 爸爸的「AndroidX」,Google 也是嫌弃 support 库太乱,弄了 AndroidX 来统一,这个命名倒是有异曲同工之妙。

基本实现方案

首先说说什么是「沉浸式状态栏」,什么是「透明状态栏」,关于这一点,郭神在很久以前有一篇文章已经说的很清楚了

Android状态栏微技巧,带你真正理解沉浸式模式

我简单总结一下,如下图所示,很多人称这种效果为「沉浸式」,其实这并不是真正的沉浸式,而只能叫「透明」,真正的沉浸式是状态栏完全不可见,这个暂不讨论,先说说这里的「透明」,可以看到,它效果就是状态栏和导航栏本身是透明的,然后布局内容侵入到状态栏和导航栏内部

那么这种效果要怎么实现呢,在 Android 4.4 上是这样的

private fun test() {
    window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
}
复制代码

Android 5.0 以上则是

private fun test() {
    val flag = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
    window?.decorView?.systemUiVisibility = flag
    window?.statusBarColor = Color.TRANSPARENT
    window?.navigationBarColor = Color.TRANSPARENT
}

再看一下另外一种常见的效果

用语言描述一下就是,状态栏和导航栏的颜色都是红色,状态栏下面的 Toolbar 的颜色也是红色,并且布局内容没有侵入到状态栏和导航栏内部。

这种效果在 Android 5.0 以上非常好实现,两行代码就可以

private fun test() {
    window?.statusBarColor = Color.RED
    window?.navigationBarColor = Color.RED
}

但是在 Android 4.4 上就比较麻烦了,因为 Android 4.4 是无法直接给状态栏和导航栏设置颜色的,要实现这种效果,比较常见的解决方案就是在 Activity 的 DecroView 中,在状态栏和导航栏的位置分别添加一个 View,根据需要给 View 设置背景色,然后让布局内容不侵入到状态栏和导航栏内部,就可以造成这种视觉效果了,代码如下

private fun test() {
    window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
    val decorView = window?.decorView as FrameLayout?
    val contentView = decorView?.findViewById<ViewGroup>(android.R.id.content)?.getChildAt(0)
    contentView?.fitsSystemWindows = true
    val statusBarView = View(this)
    statusBarView.setBackgroundColor(Color.RED)
    val statusBarLP = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight())
    statusBarLP.gravity = Gravity.TOP
    decorView?.addView(statusBarView, statusBarLP)
    val navigationBarView = View(this)
    navigationBarView.setBackgroundColor(Color.RED)
    val navigationBarLP = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getNavigationBarHeight())
    navigationBarLP.gravity = Gravity.BOTTOM
    decorView?.addView(navigationBarView, navigationBarLP)
}

上面一直有提到布局内容侵入到状态栏和导航栏,那么怎么设置让布局内容侵入或者不侵入呢,其实很简单,调用布局根 View 的 setFitsSystemWindows 方法即可(true 表示不侵入,默认 false),但是这个方法有个缺陷,它对状态栏和导航栏是同时生效的,也就是说,要么都侵入,要么都不侵入,那如果现在的需求是状态栏侵入,但导航栏不侵入该怎么办呢,显然就实现不了了, 为了解决这个问题,我在设计 UltimateBarX 的时候,就用了很极端的方法,先让状态栏和导航栏都侵入,当遇到不需要侵入的情况时,给 DecroView 增加 paddingTop 和 paddingBottom 就可以了。

到这里,思路已经很明显了,不管是 Android 4.4 还是 Android 5.0 以上,都给状态栏和导航栏设置透明效果并侵入,获取状态栏的高度为 statusBarHeight, 然后如果需要状态栏不透明,就在状态栏的位置给 DecroView 增加一个有背景色高度为 statusBarHeight 的 View,姑且称它为 StatusBarView,如果状态栏需要不侵入,就设置 DecroView 的 paddingTop 为 statusBarHeight,另外需要提一点的是,由于 StatusBarView 也是 Decorview 的子 View,而 DecorView 设置了 paddingTop,这时候 StatusBarView 的实际位置会跑到状态栏的下方,所以需要给它设置 marginTop 为 -statusBarHeight,同时需要调用 DecroView 的 setClipToPadding(false) 方法,保证 StatusBarView 可见,导航栏的设置方法也是类似,这样就可以实现状态栏和导航栏完全分开设置,不再耦合了,最终代码如下

private fun test(
    statusBarFitWindow: Boolean,
    @ColorInt statusBarColor: Int,
    navigationBarFitWindow: Boolean,
    @ColorInt navigationBarColor: Int
) {
    transparentBar()
    setStatusBarView(statusBarFitWindow, statusBarColor)
    setNavigationBarView(navigationBarFitWindow, navigationBarColor)
}

private fun transparentBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val flag = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
        window?.decorView?.systemUiVisibility = flag
        window?.statusBarColor = Color.TRANSPARENT
        window?.navigationBarColor = Color.TRANSPARENT
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
    }
    val decorView = window?.decorView as FrameLayout?
    val contentView = decorView?.findViewById<ViewGroup>(android.R.id.content)?.getChildAt(0)
    contentView?.fitsSystemWindows = false
    decorView?.clipToPadding = false
} 

private fun setStatusBarView(statusBarFitWindow: Boolean, @ColorInt statusBarColor: Int) {
    val decorView = window?.decorView as FrameLayout?
    var statusBarView = decorView?.findViewWithTag<View>("status_bar")
    if (statusBarView == null) {
        statusBarView = View(this)
        statusBarView.tag = "status_bar"
    }
    statusBarView.setBackgroundColor(statusBarColor)
    val statusBarLP =
        FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight())
    if (statusBarFitWindow) {
        statusBarLP.topMargin = -getStatusBarHeight()
        decorView?.setPadding(0, getStatusBarHeight(), 0, decorView.paddingBottom)
    } else {
        statusBarLP.topMargin = 0
        decorView?.setPadding(0, 0, 0, decorView.paddingBottom)
    }
    statusBarLP.gravity = Gravity.TOP
    decorView?.addView(statusBarView, statusBarLP)
}

private fun setNavigationBarView(navigationBarFitWindow: Boolean, @ColorInt navigationBarColor: Int) {
    val decorView = window?.decorView as FrameLayout?
    var navigationBarView = decorView?.findViewWithTag<View>("navigation_bar")
    if (navigationBarView == null) {
        navigationBarView = View(this)
        navigationBarView.tag = "navigation_bar"
    }
    navigationBarView.setBackgroundColor(navigationBarColor)
    val navigationBarLP =
        FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getNavigationBarHeight())
    if (navigationBarFitWindow) {
        navigationBarLP.bottomMargin = -getNavigationBarHeight()
        decorView?.setPadding(0, decorView.paddingTop, 0, getNavigationBarHeight())
    } else {
        navigationBarLP.bottomMargin = 0
        decorView?.setPadding(0, decorView.paddingTop, 0, 0)
    }
    navigationBarLP.gravity = Gravity.BOTTOM
    decorView?.addView(navigationBarView, navigationBarLP)
}

到此,UltimateBarX 的最基本的功能已经实现了。

light 模式

在 Android 6.0 的以上,状态栏支持字体变灰色,Android 8.0 以上,导航栏支持导航按钮变灰色,效果如下所示

我们可以称它为「light 模式」,调用 DecroView 的 setSystemUiVisibility(int visibility) 方法给它设置一些 flag 即可实现,代码如下

private fun test() {
    val flag = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
    window?.decorView?.systemUiVisibility = flag
    window?.statusBarColor = Color.TRANSPARENT
    window?.navigationBarColor = Color.TRANSPARENT
}

而前面的状态栏导航栏透明效果也是依赖这些 flag,并且状态栏和导航栏的 light 模式也是耦合在一起的,假设一种场景,如果一开始使用 SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 给状态栏设置了 light 模式,然后需要设置导航栏的 light 模式,需要重新调用 setSystemUiVisibility(int visibility) 方法并设置 SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,这时候按理说状态栏的灰色字体应该保持不变,所以要同时加上 SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,设置 flag 的代码如下

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun systemUiFlag(statusBarLight: Boolean, navigationBarLight: Boolean): Int {

    var flag = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
    when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
            if (statusBarLight) flag = flag or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            if (navigationBarLight) flag = flag or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
        }
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
            if (statusBarLight) flag = flag or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
        }
    }
    return flag
}

但是在 Activity 的外部,并不知道上次设置了状态栏的 light 模式,因此,在每一次设置完状态栏或者导航栏的 light 模式时,都需要把它们的 light 状态记下来。

那么这个状态记在哪里呢?UltimateBarX 中使用了单例类来保存,为了可以记录多个 Activity 的状态,在单例类中创建了两个 Map,分别用于保存状态栏和导航栏的 light 模式状态,Map 的 key 就是 Activity 对象,那么问题来了,我们知道,单例的生命周期是贯穿整个应用的生命周期的,在单例中持有 Activity 对象会导致 Activity 不能被回收,造成内存泄漏,所以必须要 Activity 关闭的时候把 Map 中的对应数据移除掉,那么怎么监听 Activity 的关闭呢?

比较常规的一个方法就是在 Activity 中添加一个看不见的 Fragment,只要监听 Fragment 的 onDestroy 方法即可,大名鼎鼎的图片加载库「Glide」就是用的这种套路,不过 Google 在「JetPack」中增加了很多好用的组件,Lifecycle 就是其中一种,通过Lifecycle 就可以非常方便的监听 Activity 的各个生命周期方法,而不需要繁琐的添加 Fragment 了,UltimateBarX 就是采用 Lifecycle 来监听的,代码如下

internal class UltimateBarXObserver: LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy(owner: LifecycleOwner) {
        UltimateBarXManager.getInstance().removeAllData(owner)
    }
}

private fun test() {
    addObserver(UltimateBarXObserver())
}

适配全面屏导航栏

在非全面屏手机上,如果不对导航栏做任何设置,那么它的背景就是一个黑条,上面有三个白色的导航按钮,如下图所示

前面提到,UltimateBarX 的基本原理就是先让状态栏和导航栏全部透明并侵入,然后再添加 View,设置 padding 和 margin

如果现在只需要设置状态栏而不设置导航栏,那么根据前面讲的原理,这时候导航栏也要透明并且侵入的,但是要让视觉效果上没有被设置过,怎么办?方法就是增加一种默认设置效果,在导航栏的位置上增加一个黑色背景的 View,并设置它的 marginBottom 和 DecroView 的 paddingBottom,但是全面屏的手机,它的导航栏默认是白色的,并且导航按钮是灰色的,如下图所示

显然,对于全面屏的手机,就不能用这种默认的方法了,否则就会在设置状态栏的时候导致导航栏变黑色

这个问题该如何解决呢?首先,全面屏手机不好判断,另外,即使可以判断,也不见得所有的全面屏手机默认导航栏都是白色的,所以直接在设置默认效果的地方判断是不是全面屏并设置不同的效果显然不合理,UltimateBarX 使用的方法是在第一次给 Activity 设置透明效果之前,先调用 Window 的 getNavigationBarColor 方法拿到当前 Activity 的导航栏颜色,并根据导航栏颜色判断是否是 light 模式,然后把导航栏的初始颜色和 light 模式状态也保存在单例中,后面如果需要给导航栏设置默认效果时,直接从单例里面取数据设置即可

private fun putOriginColor() {
    val navigationBarColor = window?.navigationBarColor ?: Color.TRANSPARENT
    originColorMap[this] = navigationBarColor
    val navConfig = getNavigationBarConfig(this)
    navConfig.light = calculateLight(navigationBarColor)
    putNavigationBarConfig(this, navConfig)
}

private fun calculateLight(@ColorInt color: Int) = color > (Color.BLACK + Color.WHITE / 2)

这样在设置状态栏的时候,在视觉效果上就不会对导航栏造成影响了

到此为止,UltimateBarX 在 Activity 中的功能已经基本实现了,接下来看看它在 Fragment 中的实现

在 Frgment 中使用

其实我一开始是拒绝适配 Fragment 的,因为状态栏和导航栏本来就是 Activity 自带的属性,Fragment 中并没有状态栏和导航栏的概念,所谓的在 Fragment 中使用,其实只是让 Fragment 看起来有相同的视觉效果,要实现这种效果,可以先让其所在的 Activity 的透明状态栏和导航栏透明,然后在 Fragment 的根 View 的顶部和底部分别添加子 View,其实前面提到,UltimateBarX 的 Activity 的实现也是采用了同样的方法,虽然原理相同,但是 Fragment 中实现起来有更多的坑。

View 的父布局

第一个坑就是 StatsBarView 和 NavigationBarView 的父布局,由于 Fragment 不像 Activity 那样布局的最外层有个 DecorView,所以把 StatsBarView 和 NavigationBarView 添加到哪个 View 中就成了一个难题,一开始我的想法是添加到 Activity 中的 Fragment 所在的容器布局中,但是这显然不合理,首先,我们并不能确定 Fragment 的布局容器的 ViewGroup 的具体类型,所以无法用统一的方法把 StatsBarView 和 NavigationBarView 固定在布局的顶部和底部,另外,如果使用这种方法,那就无法在在 google 新出的 ViewPager2 中使用,因为 ViewPager2 中的Fragment 是没有父布局的,最后,也是最重要的一点,就是这种方法本质还是在 Activity 中添加 StatsBarView 和 NavigationBarView,在切换 Fragment 会有状态栏延迟改变的情况,如下图所示

看起来非常奇怪,我们需要的是下图这种效果

所以要把 StatsBarView 和 NavigationBarView 添加到 Fragment 本身的布局中,假设他们添加到 Fragment 的根布局中,这时候就要考虑根布局的类型了,如果它是一个 FrameLayout,那就跟在 Activity 中起来是一样的,没有难度,如果是 RelativeLayout 也很简单,分别设置跟父布局的顶部和底部对齐即可,代码如下:

private fun setStatusBarView(statusBarFitWindow: Boolean, @ColorInt statusBarColor: Int) {
    val rootView = requireView() as RelativeLayout
    var statusBarView = rootView.findViewWithTag<View>("status_bar")
    if (statusBarView == null) {
        statusBarView = View(requireContext())
        statusBarView.tag = "status_bar"
    }
    statusBarView.setBackgroundColor(statusBarColor)
    val statusBarLP =
        RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight())
    if (statusBarFitWindow) {
        statusBarLP.topMargin = -getStatusBarHeight()
        rootView.setPadding(0, getStatusBarHeight(), 0, rootView.paddingBottom)
    } else {
        statusBarLP.topMargin = 0
        rootView.setPadding(0, 0, 0, rootView.paddingBottom)
    }
    statusBarLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
    rootView.addView(statusBarView, statusBarLP)
}

private fun setNavigationBarView(navigationBarFitWindow: Boolean, @ColorInt navigationBarColor: Int) {
    val rootView = requireView() as RelativeLayout
    var navigationBarView = rootView.findViewWithTag<View>("navigation_bar")
    if (navigationBarView == null) {
        navigationBarView = View(requireContext())
        navigationBarView.tag = "navigation_bar"
    }
    navigationBarView.setBackgroundColor(navigationBarColor)
    val navigationBarLP =
        RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getNavigationBarHeight())
    if (navigationBarFitWindow) {
        navigationBarLP.bottomMargin = -getNavigationBarHeight()
        rootView.setPadding(0, rootView.paddingTop, 0, getNavigationBarHeight())
    } else {
        navigationBarLP.bottomMargin = 0
        rootView.setPadding(0, rootView.paddingTop, 0, 0)
    }
    navigationBarLP.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
    rootView.addView(navigationBarView, navigationBarLP)
}

如果是 LinearLayout 或者其他的 ViewGroup 呢,有点难以实现了,但也还有办法,可以根据其与父布局顶部以及底部的距离,使用 setTranslationY 方法对 StatsBarView 和 NavigationBarView 进行平移,代码如下

private fun setStatusBarView(statusBarFitWindow: Boolean, @ColorInt statusBarColor: Int) {
    val rootView = requireView() as ViewGroup
    val paddingTop = if (statusBarFitWindow) getStatusBarHeight() else 0
    rootView.setPadding(0, paddingTop, 0, rootView.paddingBottom)
    var statusBarView = rootView.findViewWithTag<View>("status_bar")
    if (statusBarView == null) {
        statusBarView = View(requireContext())
        statusBarView.tag = "status_bar"
    }
    statusBarView.setBackgroundColor(statusBarColor)
    rootView.addView(statusBarView, ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight())
    statusBarView.post { statusBarView.translationY = -statusBarView.top.toFloat() }
}

private fun setNavigationBarView(navigationBarFitWindow: Boolean, @ColorInt navigationBarColor: Int) {
    val rootView = requireView() as ViewGroup
    val paddingBottom = if (navigationBarFitWindow) getNavigationBarHeight() else 0
    rootView.setPadding(0, rootView.paddingTop, 0, paddingBottom)
    var navigationBarView = rootView.findViewWithTag<View>("navigation_bar")
    if (navigationBarView == null) {
        navigationBarView = View(requireContext())
        navigationBarView.tag = "navigation_bar"
    }
    navigationBarView.setBackgroundColor(navigationBarColor)
    rootView.addView(navigationBarView, ViewGroup.LayoutParams.MATCH_PARENT, getNavigationBarHeight())
    navigationBarView.post { navigationBarView.translationY = (rootView.height - navigationBarView.bottom).toFloat() }
}

但是如果根布局是 RecyclerView 呢,直接在 RecyclerView 中 addView 会 crash,更极端的,如果根布局只是一个普通的 View 而不是一个 ViewGroup 呢,那就没办法直接给其添加子 View 了,所以这种方法也是不完美的。

那最完美的解决方案是什么呢,我最后采用的是直接给 Fragment 的根布局再套一层 FrameLayout,但是这样一来,Fragment 的真实根布局就变成了外面套的那个 FrameLayout 了,然后需要解决三个问题,第一,把原本的根布局从它的父布局中移除掉,并添加到新的 FrameLayout 中,第二,把 FrameLayout 添加到根布局原本的父布局中的相同位置,第三,把 Fragment 的根布局设为新的 FrameLayout,前两个问题都很好解决,比较麻烦的是第三个问题,通过查看源码发现,Fragment 的根布局是 mView,它不是 public 的,因此在 Fragment 外部是无法访问的,最后,我采用反射的方法,强行给 mView 赋了新的值 FrameLayout,问题完美解决,代码如下

private fun addFrameLayoutWrapper(): ViewGroup {
    val view = requireView()
    if (view is FrameLayout || view is RelativeLayout) return view as ViewGroup

    val flWrapper = FrameLayout(requireContext())
    flWrapper.setTag(androidx.fragment.R.id.fragment_container_view_tag, this)
    val parent = view.parent
    if (parent is ViewGroup) {
        val index = parent.indexOfChild(view)
        parent.removeViewAt(index)
        parent.addView(flWrapper, index)
    }
    flWrapper.addView(view)
    val fragmentViewFiled = Fragment::class.java.getDeclaredField("mView")
    fragmentViewFiled.isAccessible = true
    fragmentViewFiled.set(this, flWrapper)
    return flWrapper
}

light 模式的状态恢复

前面提到,在 Fragment 中使用,其实只是让 Fragment 看起来有相同的视觉效果,通过前面的一系列操作,完美的解决了把 StatsBarView 和 NavigationBarView 添加到 Fragment 内部,但是 light 模式必须通过 Activity 来实现了,这就会造成一个问题,如果一个 Activity 中包含多个 Fragment,每个 Fragment 设置了不同的 light 模式,在切换 Fragment 过程中,必须实时的恢复当前 Fragment 的 light 模式,非常遗憾的是传统的 hide 和 show 方法并不能被监听到,这种情况只能在 Activity 中手动切换 light 模式,但是 ViewPager 的 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 模式和 ViewPager2 中,在切换 Fragment 过程中,Fragment 的 onResume 方法都会被回调,因此可以通过 Lifecycle 来监听,从而恢复当前 Fragment 的 light 模式的状态,最终改造 LifecycleObserver 的代码如下

internal class UltimateBarXObserver: LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy(owner: LifecycleOwner) {
        UltimateBarXManager.getInstance().removeAllData(owner)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume(owner: LifecycleOwner) {
        if (owner is Fragment) {
            val staDefault = UltimateBarXManager.getInstance().getStatusBarDefault(owner)
            val navDefault = UltimateBarXManager.getInstance().getNavigationBarDefault(owner)
            if (staDefault) {
                UltimateBarX.get(owner).applyStatusBar()
            }
            if (navDefault) {
                UltimateBarX.get(owner).applyNavigationBar()
            }
        }
    }
}

效果如图

为什么说是 github 上最好用的

github 上有很多关于状态栏和导航栏的库,为什么我敢自称最好用呢,主要是因为「UltimateBarX」 做到了以下两点:

  1. 状态栏和导航栏彻底解藕,单独设置,互不影响

  2. 同一个 Activity 或 Fragment 可以多次设置不同的效果

纵观 github 上的其他的库,能做到这两点的,目前来说还没有,只此一家,绝无分店,使用起来也很简单,直接一行代码链式调用即可

private fun test() {
    UltimateBarX.with(this)                   // 在当前 Activity/Fragment 生效
        .fitWindow(true)                      // 是否侵入状态栏 (true: 不侵入)
        .color(Color.BLACK)                   // 状态栏颜色(色值)
        .colorRes(R.color.deepSkyBlue)        // 状态栏颜色(资源 id)
        .drawableRes(R.drawable.bg_gradient)  // 状态栏背景(drawable 资源)
        .light(false)                         // light 模式(true: 字体变灰)
        .applyStatusBar()                     // 应用到状态栏
}

到此,就把 「UltimateBarX」 的实现全部讲完了,欢迎体验使用,如遇到问题,欢迎提 issues,我会长期维护的。

https://github.com/Zackratos/UltimateBarX

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值