分享

探索Android半透明状态栏

 个人文档awpyia 2017-05-10

每日推荐


这个项目提供了非常多精心挑选的Android库、工具、开源项目、书籍、博客、教程等,质量非常高,如果你不清楚自己要学习什么,或者想要挑选一些开源库学习源码,这个项目应该可以帮助你。


https://github.com/aritraroy/UltimateAndroidReference


本文作者


本文由kyleduo投稿。

kyleduo的博客地址:

https://blog.


Material Design将Immersive(沉浸感)这个词带到了我们面前。相比于全屏沉浸感,我们见到更多的,是在4.4及以上版本的半透明状态栏(translucent statusBar)效果。


如下:



注意:

半透明状态栏和状态栏着色是两个不同的概念。半透明状态栏并不是手动设置状态栏的颜色,而是通过设置Theme的属性,让内容(content)显示在状态栏下面。


网上提到这个效果的不少,但是真的讲明白的不多。下面我来带你一起digging~


1

效果和问题


上面示例图是5.0及以上系统(API v21)的效果,因为Android版本的关系,在4.4(API v19)上,相同的属性会导致不同的效果。


下面是对比:



可以看到,4.4上的效果相对于5.0及以上(下面说的5.0都指5.0及以上)的半透明,4.4上是一个渐变的效果。


不去讨论孰优孰劣,暂时记住这个效果。


上面就是Demo的效果,Demo只有一个页面,层级如下:



但是4.4和5.0还有更大的不同,下面提到的实现方式的不同将导致编码的差异。


2

实现和差异


半透明状态栏是在4.4版本引入的,但是fitsSystemWindows这个属性在API 16就已经引入了,用来让View根据Window的缩进(WindowInsets)进行响应处理——设置padding或者offset;不只是用在状态栏上,NavigationBar对Content的影响,也通过这个标记位传递到View上。


回顾一下通常我们看到的实现方法:


  1. 在styles.xml中设置android:windowTranslucentStatus属性为true。

  2. 在布局文件中设置android:fitsSystemWindows属性为true。


如果你真的查到了这些方法并且按照上面写的做了,那么极大可能你做出的效果在4.4和5.0上是不一样的,或者是需要再次调整和尝试修改。


上面提到的步骤中,第一步是没有问题的,4.4及以上的styles.xml文件加上这个属性即可。


关键在于第二步,fitsSystemWindows属性应该设置到哪个/些View上?


这个问题引出了这篇博客的关键,4.4和5.0系统对于fitsSystemWindows属性解析的不同实现。我也是遇到了设置这个属性的问题,才开始进行实现原理的分析,发现之前的理解和使用,根本就是错的。


4.4 KitKat v19


两个系统的处理逻辑的相同点是,都是从ViewTree的顶端开始,向下进行深度优先遍历,让各级子View进行处理。不同点在于派发的方法和逻辑。


要看的方法是View.fitSystemWindows方法和ViewGroup.fitSystemWindows方法(v19 SDK),方法下面有解释。


View.fitSystemWindows



根据自己的标记为判断是否要响应insets。如果需要的话,调用internalSetPadding方法设置padding。


ViewGroup.fitSystemWindows



深度遍历子View,依次调用自己和子View的fitSystemWindows方法,一旦fitSystemWindows方法返回true,停止遍历,完成处理。


总结一下就是:深度遍历,直到给第一个设置标记的View设置padding。


5.0 Lollipop v21


这是说是v21,其实源码里很多地方是按照v20作为分界点,但是里面的逻辑并不会执行(因为外层有v21判断),所以这里可以按照v21来区分。


我们在v21的SDK中可以看到,上面的View.fitSystemWindows已经过时了:


This method was deprecated in API level 20.
As of API 20 use dispatchApplyWindowInsets(WindowInsets) to apply insets to views. Views should override onApplyWindowInsets(WindowInsets) or usesetOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener) to implement handling their own insets.


依然看一下方法实现:



else分支的fitSystemWindowsInt方法就是v19的实现。

PFLAG3_APPLYING_INSETS标记表明了正在处理dispatchApplyWindowInsets遍历。


v21开始使用dispatch/apply逻辑,类似TouchEvent事件处理。父控件依次调用dispatchApplyWindowInsets方法,而View类的dispatchApplyWindowInsets方法中使用onApplyWindowInsets方法或者OnApplyWindowInsetsListener对象进行处理。


下面看一下主要的三个方法。


ViewGroup.dispatchApplyWindowInsets



如果自己没有消费并且子View也没有消费,交给父View处理。

遍历的结束条件就是insets对象的isConsumed标记为true,因为把insets返回给了父View,父View的相同方法也会停止遍历,依次向上。


View.dispatchApplyWindowInsets



View.onApplyWindowInsets



consumeSystemWindowInsets方法将消费掉所有Inset并将isConsumed()标记置为true。


总结一下:深度遍历,从上至下依次消费Insets,直到WindowInsets的isConsumed方法返回true,通常是调用过consumeSystemWindowInsets方法。


3
分析和实例


下面讨论上面例子中出现的ViewGroup的实现以及不同参数设置的情况,为了节省空间,方便输入,关键的四个View:AppBarLayout、CollapsingToolbarLayout、Toolbar、ImageView(Toolbar同级)分别简写为ABL、CTL、TB、IV,然后用T和F表示fitsSystemWindows属性的值。


ABL、CTL等Support包中的View,使用通过ViewCompact设置OnApplyWindowInsetsListener的方式,处理,通常写法如下:



onWindowInsetChanged是核心方法;但要注意setOnApplyWindowInsetsListener只在5.0及以上SDK生效,也就是说onWindowInsetChanged方法在5.0以下版本不会被调用。


AppBarLayout.onWindowInsetChanged()



invalidateScrollRanges将重置ScrollRange相关的标记位,同时记录insets的值,其他位置通过getTopInset()获取insets对顶部偏移量的影响(只有insets的top影响布局)。

ABL不消费Insets。


CollapsingToolbarLayout.onWindowInsetChanged()



可以看到,只要CTL的标记为true,是一定消费Insets的。

mLastInsets属性在onLayout方法中用到,下面是相关实现:



这个逻辑是:

遍历所有子View,如果子View没有设置fitsSystemWindows标记,只要getTop()的值小于insetTop,就将其偏移到insetTop。

换句话说:设置了标记的子View会在StatusBar下面(under)绘制,没有设置标记的子View会被挤下去(down)。


同时,CTL还有一个有意思的逻辑:



如果CTL的直接父View是ABL,会同步ABL的fitsSystemWindows属性的值。

下面开始讨论各种不同参数的情况。


均为true: ABL = CTL = TB = IV = T


结果:



错误原因:

4.4:深度遍历,第一个遇到的View是ABL,执行View的默认逻辑,设置paddingTop。所以露出了背景颜色,同时子View都被挤到了下面。

5.0:因为CTL设置了true,而且子View也都设置了true,所以TB和IV都在StatusBar下面绘制。


先保证5.0能显示,根据上面的错误,我们应该把TB设置为false,IV设置为true。这样5.0会显示正常,而4.4,情况和上面一样。


结果:


符合预期(4.4没有换图,一模一样,偷懒了),5.0正常,原因其实上面也说了,这里不再重复。只要清楚了原理,结果就显而易见了。

如果到这里你不太理解,再看看上面源码。


下面轮到4.4了。


回头看一下ViewTree的层级关系:ABL -> CTL -> IV、TB。从上到下,我们期望的是TB执行View默认的逻辑(设置padding),所以应该是F -> F -> F、T。(IV应该是false,因为要出现在StatusBar下面)。


结果:



符合预期,4.4显示正确,但是5.0回到了第一次尝试的结果。原因就留给你啦,相信你一定能解释。


4

正确做法


经过几次尝试,我们发现要使得4.4和5.0都正确显示,需要给View的属性根据SDK版本设置不同的值,我们可以通过styles.xml简化这个操作。


v19



v21



v21的CTL可以设置为false,是因为CTL的值会同步ABL的值,所以这里的值没有作用,T或者F的结果是一样的。


layout长这样(省略了无关属性)



5
更多


以上,我们可以实现适配4.4和5.0的半透明状态栏效果,通知知晓了其中的原理。下面再讨论两个场景:


普通布局


上面的实例中使用到的布局都是Support包中的,还有一种情况是我们使用的是普通的布局,比如Linearlayout、RelativeLayout等。使用这些布局,应该怎么实现呢?



普通布局都使用默认实现,不管是4.4和5.0都将进行深度优先遍历,直到WindowInsets被消费。所以对于普通布局的做法是,只给ToolBar设置fitsSystemWindows=true属性。

不仅仅是ToolBar,任何布局都直接给期望的那个View设置fitsSystemWindows=true属性即可。


4.4 效果优化


4.4上的渐变效果,会显得ToolBar很高,视觉效果并不好。我们可以针对4.4做一些优化,在上面覆盖显示一个半透明View来模拟5.0上的效果,以第一个页面为例:



act_main.xml



@layout/stub_kitkat_statusbar只包含一个背景为半透明的View。


MainActivity.java



这里获取ToolBar的PaddingTop并设置为Overlay的高度。


总结


4.4和5.0处理WindowInsets的逻辑,相同点是都进行深度优先遍历。


不同点是4.4逐级调用fitSystemWindows方法,第一个带有fitsSystemWindows属性的View处理之后,整个流程结束;


5.0通过类似Touch事件的dispatch和apply逻辑完成对WindowInsets的消费,消费可以通过onApplayWindowInsets方法或者Listener的方式,直到消费完成,流程结束。


fitsSystemWindows属性表明该View将根据Window的Insets进行适应,这个“适应”,一般来说是设置padding,CollapsingToolbarLayout的处理方法是对子View进行offset偏移。


共同点是:表明该View的内容或者子View要向下移出状态栏的区域。一般情况下,只需要给一个View设置该属性。


为了实现半透明状态栏效果,需要做两件事:

  1. 在主题中设置android:windowTranslucentStatus值为true。

  2. 给恰当的View设置android:fitsSystemWindows属性为true。


以Toolbar为例,如果Toolbar在普通布局中,直接给Toolbar设置以上属性即可;如果是Demo中那种Material Design嵌套结构,就需要根据4.4和5.0的实现逻辑进行适配,方法这里就不赘述了。


Demo源码:

https://github.com/kyleduo/ExamplesFromMyBlog/tree/master/DiggingTranslucentStatusBar

后续踩坑记录:

https://blog./2017/05/03/digging-translucentstatusbar-2/

https://blog./2017/05/05/digging-translucentstatusbar-3/

ZZS

无论你是有 Java 基础希望学 Android 开发的程序员,还是想进一步提升能力的 Android 开发者,都可以在这个Udacity & Google 官方参与制作 的课程项目中找到适合自己的成长路径!



*独家硅谷技术课程

*行业领导者设计的实战项目

*一对一学习辅导

*名企颁发学习认证

*毕业直达滴滴面试


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多