最近在进行产品的优化,也是研究了下沉浸式状态栏的实现方法及自动取色,记录一下笔记
这里,是使用了一个Android的工具开源库来实现了功能,首先需要依赖
// Android的工具类 https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/README-CN.md
implementation 'com.blankj:utilcodex:1.31.0'
工具类中有个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代表了两种实现沉浸式状态栏的方法
所以,这里我个人是比较推荐DecorView,因为显示的效果比较好
在设置主题为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)
自动取色这里是用到了另外一个开源库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
}
}
}
这里,稍微总结下看到的相关知识原理,不追求长篇大论,各位要深入了解可自行搜索资料
首先,我们要了解下对应的层级关系图,如下图所示:

上述图中,DecorView其实是继承FrameLayout,然后其中包裹一个子View
这个子View是LinearLayout,方向为竖直方向,其内有两个FrameLayout

沉浸式状态栏主要实现思路:先将原先的状态栏设置为透明色,之后在父布局一个View来代替状态栏的占位(高度与状态栏的高度一致)
这里的父布局有两种情况,一种是在DecorView中新增一个新的View来代替状态栏的占位,另外一种则是在ContentView中(通过android.R.id.content可以找到)添加View
如果是第二种方法,因为在不同版本中,此布局包含的子View有所区别,所以在高版本会出现状态栏被遮挡的情况
上述这句总结还未验证过,如果说错可以希望在评论区指出
这个时候可以通过设置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中进行消费
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问
当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一