0

一个Android沉浸式状态栏上的黑科技

 1 year ago
source link: https://blog.csdn.net/guolin_blog/article/details/125234545
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.

一个Android沉浸式状态栏上的黑科技

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

说起来,在不知不觉中,我竟然凑成了这沉浸式状态栏三部曲。

其实最开始的时候,我主要是因为工作上的原因想要在Android版的Edge浏览器上实现首页图片沉浸式的功能。

那么为了实现这个功能,我提前去做了一些技术调研,并将调研的结果整理成了一篇文章,具体可参阅 再学一遍android:fitsSystemWindows属性

做完技术调研之后,接下来就是功能实现了。对于Android版的Edge浏览器而言,首页图片的沉浸式一直是部分网友长久以来的呼声,经过我的各种攻坚和踩坑之后,终于将这个功能完成了。具体可参阅 我为Android版Microsoft Edge所带来的变化

实现沉浸式之后的效果如下图所示:

在这里插入图片描述

不过,有朋友在评论区提出了这样一个疑问:

在这里插入图片描述

确实,这是一个做沉浸式功能时比较容易被忽略的问题。如果背景图片的颜色和状态栏图标的颜色非常接近的话,那么的确会造成状态栏图标看不清楚的情况。

这里我举了一些沉浸式效果做得不太好的案例,具体是什么App我就不提了。

在这里插入图片描述
在这里插入图片描述

可以看到,这些App虽然实现了沉浸式状态栏的效果,但是由于状态栏上的图标变得难以看清,所以最终效果可能反而不好。

但是,Edge浏览器是不会存在这种问题的。为什么呢?这就是我在上篇文章中说的,在实现沉浸式状态栏时运用了一些小黑科技。那么借助这些小黑科技,我终于可以凑成这沉浸式状态栏三部曲了。

话不多说,下面技术开讲。

其实想要解决上图中的这种由于颜色值接近,导致部分内容看不清的情况,我能想到两种解决方案。一种是从设计层面解决,一种是从技术层面解决。

从设计层面解决相对会比较容易一些,同时应该也是大部分App会采用的方案,那就是在背景图的上方再盖一层阴影。有了这层阴影之后,我们可以让状态栏上的图标始终都是浅色的。即使出现浅色的背景图,由于阴影层的存在,状态栏上的图标依然是可以看得清的。

但如果只是用这个方案解决的话,那么我就不会写本篇文章了。因为这里我们会采用第二种方案,从技术层面解决。

首先从技术层面进行分析,要解决这个问题,无非就是需要将背景图颜色和状态栏图标的颜色区分开。

Android系统其实给了我们API来控制状态栏图标的颜色,但是只能设置成黑、白这两种颜色,而不可以将状态栏图标改成五颜六色的样子。

默认情况下,系统会认为我们拥有的是一个深色的状态栏,那么状态栏上面的图标自然就应该白色的,因为只有这样才能看得清上面的图标。

而调用如下API则可以让系统认为我们拥有的是一个浅色的状态栏:

private fun setLightStatusBar() {
    val flags = window.decorView.systemUiVisibility
    window.decorView.systemUiVisibility = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}

如此一来,状态栏上面的图标就会变成黑色的,以和浅色的状态栏相互映衬。

如果要动态恢复成默认的深色状态栏,只需要这样设置:

private fun setDarkStatusBar() {
    val flags = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    window.decorView.systemUiVisibility = flags xor View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}

这就是我们拥有的用于控制状态栏图标颜色的API。

好了,现在有了这个法宝来控制状态栏图标的颜色,那么接下来的问题就是,什么时候应该显示白色的状态栏图标?什么时候应该显示黑色的状态栏图标?

答案是显而易见的,为了能让前景背景的颜色区分更加明显,当然应该是底部是深色背景图的时候显示白色的状态栏图标,底部是浅色背景图的时候显示黑色的状态栏图标。

因此,现在的问题就转移成了,我们如何才能识别一张背景图的指定区域是属于深色还是浅色?

非常幸运,在Android系统上我们是可以做到这一点的,只需要借助Google提供的Palette库即可。

Palette是一个专门用于对图像进行颜色提取和识别的库,功能虽然不能说是非常强大,但是已经完全可以满足我们这里的需求了。

要使用Palette库,首先需要将它引入到项目当中,如下所示:

dependencies {
    implementation 'androidx.palette:palette:1.0.0'
}

接下来我们就可以借助Palette来进行一些颜色提取功能了,示例用法如下:

Palette
    .from(bitmap)
    .setRegion(left, top, right, bottom)
    .maximumColorCount(colorCount)
    .generate {

}

这是Palette最基础、最常见的用法。

首先,我们传入一个bitmap对象,这样Palette就会对它来进行图像解析。

然后调用setRegion()方法来指定解析这个bitmap对象的哪个区域。比方说我们本篇文章是要解决状态栏图标的问题,那肯定就要去解析手机状态栏那个区域的颜色值,其他区域的颜色值对我们来说没有意义。

接着调用maximumColorCount()方法来告诉Palette一共需要提取多少个颜色特征点。具体的颜色提取算法是由Palette自己控制的,我们无需关心。反正只需要知道,最终提取出来的这些颜色值都是这个bitmap的指定区域里最具代表性的就可以了。

最后调用generate()方法开始解析,Palette会开启异步线程来执行解析操作,并将最终结果回调到Lambda表达式当中。

现在我们已经得到这些提取出的特征点颜色值了,那么接下来,我们又该如何处理它们呢?

需要说明的事,后续的处理逻辑其实并没有一个非常严格的规定。我只说一下我个人的处理方式,大家也完全可以去定义自己的处理逻辑。

先贴一下代码,我再进行解释:

Palette
    .from(bitmap)
    .maximumColorCount(colorCount)
    .setRegion(left, top, right, bottom)
    .generate {
        it?.let { palette ->
            var mostPopularSwatch: Palette.Swatch? = null
            for (swatch in palette.swatches) {
                if (mostPopularSwatch == null
                    || swatch.population > mostPopularSwatch.population) {
                    mostPopularSwatch = swatch
                }
            }
            mostPopularSwatch?.let { swatch ->
                val luminance = ColorUtils.calculateLuminance(swatch.rgb)
                // 当luminance小于0.5时,我们认为这是一个深色值.
                if (luminance < 0.5) {
                    setDarkStatusBar()
                } else {
                    setLightStatusBar()
                }
            }
        }
    }

由于刚才在maximumColorCount()方法中传入了提取颜色特征点的数量,因此generate()方法的回调当中我们就可以得到多个颜色特征点(Swatch)。

而每个颜色特征点都会有一个权重值,调用getPopulation()方法可以获取,表示该特征点在选定的bitmap区域的重要程度。我选取了权重值最高的那个特征点来作为这个bitmap区域的代表颜色值。

接下来再调用ColorUtils.calculateLuminance()方法来计算选取的这个颜色值的亮度。当亮度低于0.5时,我就认为这是一个深色的颜色值,那么此时将状态栏设置成深色模式,状态栏图标就会自动变成白色。反之就将状态栏设置成浅色模式,此时状态栏图标就会自动变成黑色。

大概流程就是这个样子,我觉得原理还是非常简单的,我甚至都没有给出一个完整的实例,只是贴出了一些代码片段。

至于Palette,终归只是一个比较小众的库,知道或使用过的人可能并不多,所以用上这种小众技术我觉得足以称得上是黑科技了。

那么最后我们就来看一看实际的运行效果吧。

这里我准备了几张不同的背景图,由Palette解析之后,会根据识别出的颜色值动态更改状态栏图标的颜色。

这是深色背景图的效果。

在这里插入图片描述

这是浅色背景图的效果。

在这里插入图片描述

可以看到,不管在什么背景图下,状态栏图标的颜色都可以做到自动适配,保证图标始终是清晰可见的。

目前这种使用Palette来动态进行颜色识别的方案,我感觉至少是可以保证99%以上的场景都能够正确适配的,但是也存在一些特别极端的场景。

比如说背景图就是一张黑白左右分割的图片,这种情况下Palette会选取哪种颜色来作为代表色其实是不确定的。但不管是选中了黑还是白,都一定会导致状态栏上有一半区域的图标是不可见的。效果如下图所示:

在这里插入图片描述

不过对于这种极端情况,我觉得就没必要强求了。甚至我都并不认为这是一个Bug,反而觉得这是一种很酷的效果,你们觉得呢?

好的,本篇文章就到这里。文中我只帖出了所有关键代码的示例,以及最终运行效果的截图。如果你不想自己动手去敲一遍,也可以直接参考我的完整源码:

https://github.com/guolindev/ImmersiveStatusBar

沉浸式状态栏三部曲到此完结。

如果想要学习Kotlin和最新的Android知识,可以参考我的新书 《第一行代码 第3版》点击此处查看详情

关注我的技术公众号,每天都有优质技术文章推送。

微信扫一扫下方二维码即可关注:

20181224140138240.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK