• Android 自动取色并设置沉浸式状态栏


    本文为作者原创,允许转载,不过请在文章开头明显处注明链接和出处!!! 谢谢配合~
    作者:stars-one
    链接:https://www.cnblogs.com/stars-one/p/16632818.html

    本篇大约有4609个字,阅读预计需要5.76分钟


    Android 自动取色并设置沉浸式状态栏 - Stars-One的杂货小窝

    最近在进行产品的优化,也是研究了下沉浸式状态栏的实现方法及自动取色,记录一下笔记

    设置沉浸式状态栏

    1.添加依赖

    这里,是使用了一个Android的工具开源库来实现了功能,首先需要依赖

    // Android的工具类   https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/README-CN.md
    implementation 'com.blankj:utilcodex:1.31.0'
    

    2.使用

    工具类中有个BarUtils的类,里面提供了对应状态栏和导航栏的对应方法,如设置透明状态栏,设置状态栏颜色等

    下面,我们就是用到其中的setStatusBarColor()方法

    APP的原图为这样

    我们下面需要设置沉浸式的状态栏

    使用以下方法:

    BarUtils.setStatusBarColor(this, ColorUtils.getColor(R.color.design_default_color_primary))
    

    ColorUtils也是工具库中的一个工具类,通过它可以拿到定义到color.xml中的方法

    我们可以看到,颜色是变了,但是布局似乎被ActionBar遮挡住了一部分

    这个时候有以下的解决方法:

    在你的Activity对应的布局中加上android:fitsSystemWindows="true"的属性即可解决

    如下图所示

    当然,这样做的话会比较繁琐,我们可以考虑使用代码的方式来设置

    val contentParent = findViewById(android.R.id.content)
    contentParent.getChildAt(0).fitsSystemWindows = true
    

    效果就出来了:

    但细看的话,觉得还是看出ActionBar的边界,我们可以考虑使用setStatusBarColor()另一个参数方法

    BarUtils.setStatusBarColor(this, ColorUtils.getColor(R.color.design_default_color_primary),true)
    

    效果比之前的要好些:

    实际上,这个的传参的isDecor代表了两种实现沉浸式状态栏的方法

    • true,则是往DecorView添加状态栏,
    • false,则是往ContentView中添加状态栏

    所以,这里我个人是比较推荐DecorView,因为显示的效果比较好

    3.补充-主题为NoActionBar被遮挡问题解决

    在设置主题为NoActionBar之后,会发现出现下面的情况

    其实解决方法如上述一样,只要设置android:fitsSystemWindows="true"或者用代码去设置即可解决,这里不再赘述

    设置状态栏亮色或暗色图标

    上面,我们只是设置了状态栏的颜色,但是没有设置对应的显示图标,如果你状态栏设置的颜色比较亮的话,这个时候为了方便图标显示,你得将图标变为暗色的图标

    那么具体要怎么实现呢?

    其实BarUtil还是有提供对应的方法

    val color = ColorUtils.getColor(R.color.design_default_color_primary)
    BarUtils.setStatusBarColor(this, color)
    
    //设置状态栏图标是否为亮色图标
    BarUtils.setStatusBarLightMode(this, ColorUtils.isLightColor(color))
    

    这里,我们需要判断下颜色是否为亮色,怎么判断呢?

    我们上述引用的库中,带有一个工具类ColorUtils,可以通过其isLightColor()方法来判断颜色是否亮色

    于是我们两个方法结合起来,就是上面的设置的方法了

    PS:如果想要实现导航条颜色也需要变换,可以使用此方法setNavBarColor(),如下代码:

    BarUtils.setNavBarColor(this,color)
    

    自动取色

    1.添加依赖

    自动取色这里是用到了另外一个开源库palette

    这个是谷歌官方出的一个库,主要是用来发布获取图片的主色调

    implementation 'androidx.palette:palette:1.0.0'
    
    //如果是Kotlin,推荐使用这个
    implementation 'androidx.palette:palette-ktx:1.0.0'
    

    下面我是Kotlin使用进行说明

    2.使用

    Palette是从图片Bitmap中进行颜色提取,所以需要我们传一个图片

    这里,我们可以直接将当前屏幕的截图Bitmap对象拿到,然后通过Palette去提取对应的颜色特征点即可

    截图:

    //截图
    val drawingCache = ScreenUtils.screenShot(this)
    

    ScreenUtils还是上述工具类库中的工具类,方便获取截图Bitmap对象

    Plaette用法:

    Palette.from(drawingCache).generate {
        //具体获得到的颜色特征数据
        
    }
    

    实际上,如果不设置如何参数,上述就可以取颜色特征点了,Palette会开启异步线程来执行解析操作,并将最终结果回调到Lambda表达式当中

    具体的颜色提取算法是由Palette自己控制的,我们无需关心。反正只需要知道,最终提取出来的这些颜色值都是这个bitmap的指定区域里最具代表性的就可以了。

    一般来说,还是推荐设置对应的参数

    比如说我们的需求,是要动态取色,那么,这个颜色应该怎么样才比较准确呢?

    那当然是直接取状态栏下方的页面数值,这样就比较好的有着沉浸式效果了

    那么,这个需要我们就可以setRegion()方法来指定解析这个bitmap对象的哪个区域,其他区域的颜色值对我们来说没有意义

    当然,除此之外,我们调用maximumColorCount()方法来告诉Palette一共需要提取多少个颜色特征点。

    于是,代码就变为以下:

    //先截图,取当前截图的主要色调
    val drawingCache = ScreenUtils.screenShot(this)
    
    Palette.from(drawingCache).maximumColorCount(5).generate {
        //取色成功后的异步回调,取主色调
        it?.let {
            val swatches = it.swatches
            //便利找寻主色调
            var mostSwatch :Palette.Swatch?=null
            swatches.forEach {
                if (mostSwatch != null) {
                    //population指的是出现最多的颜色
                    if (mostSwatch!!.population < it.population) {
                        mostSwatch = it
                    }
                } else {
                    mostSwatch = it
                }
            }
    
            //设置状态栏为主色调
            mostSwatch?.let {
                val color = it.rgb
                BarUtils.setStatusBarColor(this, color, true)
                BarUtils.setStatusBarLightMode(this, ColorUtils.isLightColor(color))
    
                val contentParent: ViewGroup = findViewById(android.R.id.content)
                contentParent.getChildAt(0).fitsSystemWindows = true
            }
    
        }
    }
    

    原理研究

    这里,稍微总结下看到的相关知识原理,不追求长篇大论,各位要深入了解可自行搜索资料

    关于Activity页面层级问题

    首先,我们要了解下对应的层级关系图,如下图所示:

    上述图中,DecorView其实是继承FrameLayout,然后其中包裹一个子View

    这个子View是LinearLayout,方向为竖直方向,其内有两个FrameLayout

    沉浸式状态栏实现原理

    沉浸式状态栏主要实现思路:先将原先的状态栏设置为透明色,之后在父布局一个View来代替状态栏的占位(高度与状态栏的高度一致)

    这里的父布局有两种情况,一种是在DecorView中新增一个新的View来代替状态栏的占位,另外一种则是在ContentView中(通过android.R.id.content可以找到)添加View

    如果是第二种方法,因为在不同版本中,此布局包含的子View有所区别,所以在高版本会出现状态栏被遮挡的情况

    上述这句总结还未验证过,如果说错可以希望在评论区指出

    这个时候可以通过设置fitsSystemWindows进行解决

    关于fitsSystemWindows属性

    上面也是提到了使用android:fitsSystemWindows="true"解决布局状态栏被遮挡问题,实际上,这个属性只是个标识属性,具体要对应的布局去实现

    上面意思呢?就比如说,你的Activity布局是FrameLayout,你定义了这个fitsSystemWindows,那么实际上也不会发生偏移,因为FrameLayout布局中没有对此进行适配

    实际上的适配工作,就是布局判断下这个标识,然后自动的加上对应的偏移,就形成了上述我们要的效果

    具体可以参考郭霖大佬的文章再学一遍android:fitsSystemWindows属性

    fitsSystemWindows在ViewGroup中通过dispatchApplyWindowInsets()进行分发给子View

    如果dispatchApplyWindowInsets 中把insets.consumeSystemWindowInsets()消费掉, 那么inset事件就无法传递到子View,子View设置fitsSystemWindows=true将会没有反应

    View会通过onApplyWindowInsets()消费掉WindowInsets, 当然其要求是父View必须设置fitSystemWindows=false 这样WindowInsets才能传递到子View中进行消费

    参考

  • 相关阅读:
    Prometheus Install
    提升代码可读性,减少if-else的几个小技巧
    NFT:以最简单的方式解释NFT项目的一般架构
    使用bisect模块进行二分查找操作 bisect.bisect()
    牛客-TOP101-BM43
    编程初学者应该先学C++、Java还是Python?
    两道相似的三维dp动态规划题
    【ElementUI】InfiniteScroll 无限滚动组件在部分浏览器中滚动失效 的 解决方案
    tslib库编译与移植
    自媒体账号十万粉丝如何变现?
  • 原文地址:https://www.cnblogs.com/stars-one/p/16632818.html