3

Android 自动取色并设置沉浸式状态栏 - Stars-one

 1 year ago
source link: https://www.cnblogs.com/stars-one/p/16632818.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文为作者原创,转载请注明出处,谢谢配合
作者:stars-one
链接:https://www.cnblogs.com/stars-one/p/16632818.html

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


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'

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

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

APP的原图为这样

1210268-20220823230614514-1629782911.png

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

使用以下方法:

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

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

1210268-20220823230641481-197158091.png

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

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

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

如下图所示

1210268-20220827172139156-972907563.png

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

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

效果就出来了:

1210268-20220827175950013-1664638061.png

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

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

效果比之前的要好些:

1210268-20220827180157192-1134560885.png

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

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

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

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

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

1210268-20220823231229813-1008542210.png

其实解决方法如上述一样,只要设置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使用进行说明

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页面层级问题

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

1210268-20220828143532365-926045139.png

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

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

1210268-20220828143853484-2087275049.png

沉浸式状态栏实现原理

沉浸式状态栏主要实现思路:先将原先的状态栏设置为透明色,之后在父布局一个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中进行消费


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK