cover_image

Android 多主题之坑

xyczero Android程序员 2016年06月01日 00:55

声明:本文为xyczero原创,授权发布在 Android程序员公众号,转载请参考原文协议。

原文:http://www.xyczero.com/blog/article/29/


今天的文章来自 xyczero 同学,xyczero 同学目前就职于B站,也是一位热爱开源,乐于分享的小伙伴,其博客 xyczero.com 积累颇多,推荐大家关注。


关于 Android 多主题早前我也推荐过类似的文章,但这一篇,满满的都是各种填坑经验,相信今后大家在做类似需求的时候会很有帮助,因此推荐给各位。


作者透露将会在不久后开源文中提到的多主题框架,也是非常值得期待。

年后重构了一版多主题框架,在重构过程中遇到了不少的坑,特此记录下与君共勉。(Tips: 多主题框架也将于6月初开源啦^.^)

多彩主题和夜间主题

在写多主题框架时,首先一个概念要分清就是多彩主题和夜间模式。

  • 多彩主题其实是白天模式的衍生,与夜间模式是对立的。

  • 虽然夜间和多彩是对立,但还是建议多彩主题应该与夜间模式解偶,因为有时夜间模式的颜色变化并不是简单的颜色取反,受产品设计的影响较大,有时甚至一个tag在夜间和多彩中的取色完全不一样的,这时如果还在强求通过一次编码“通吃“多彩和夜间,这样的做法完全是不明智的,同时也会导致框架易用性变差。

    当然如果某些控件在夜间模式下的需求只是简单的颜色取反,对于这种情况,框架是应当给予适配支持的(不能一棒子打死嘛),因为这种特性支持很简单,所以可以在基本不增加框架学习使用成本的前提下,大大减少程序员的重复编码,提高了开发效率。

  • 关于夜间模式的具体实现方式有很多,在这里推荐一篇文章 Android夜间模式最佳实践,文中一共概述了三种实现方式,其中第三种通过修改uiMode来切换夜间模式 其实就是Google在support库23.2.0版本(新增支持夜间模式,其实早就支持了0,0)中采用的方式,只不过在AppcompatDeleglate中进行了封装,使用起来更加简单了。

关于ColorDrawable

API21以下是不支持染色的,所以从兼容性上考虑,一般地对ColorDrawable直接new而不是染色。

源码如下(API19):

图片

关于GradientDrawable

比较特殊,API22以下是不支持直接tint的,这点在support库中有很清楚的说明(DrawableCompatLollipop.java$setTintList):

图片

另外值得注意的是,GradientDrawable不支持tint的原因有两点。

1. 在API21以下它并没有实现onStateChange方法,而onStateChange在view中的默认实现是直接返回false,所以它就不会随着状态的变化刷新UI了。

2. 在API21的GradientDrawable源码中并没有支持setTint,这有点奇怪,因为其他Drawable基本都支持了,有时间要仔细对比下源码。

图片

关于setPressed(boolean)

setPressed 方法不同于setSelected方法,虽然它在执行过程中会更新Drawable的state状态,但是不会调用invalidate函数。

备注:并不是针对所有Drawable,stateDrawableList会在setPressed执行过程中调用invalidate()

附上API23部分源码:

图片

特别地,当一个textview设置了一张png为background并对该background设置了normaltint和pressed tint,然后你会发现background在按下时背景色并没有tint。

解决的方法:

  1. 在view的drawstateChanged()中手动调用invalidate方法。

  2. 在view的drawstateChanged()中apply新的drawable state。

  3. 等待你来补充。

关于.9png

.9png在绘制时如果.9png内含有padding值,则5.0以下时view的padding会消失。如果想要view的padding保留,目前比较好的做法就是在set前先将view的padding值保存下来,然后等set之后再重新setPadding回去(首先要明确的一点是drawable和view的padding是有区别的)。

关于StateListDrawable对child tint 无效

这是一个5.0以下的bug,现在比较好的解决方案就是继承StateListDrawable,重写它的selectDrawable方法,每次在状态切换获取对应的drawable时,手动进行setColorFilter设置。附上链接

http://stackoverflow.com/questions/6018602/statelistdrawable-to-switch-colorfilters

关于setButtonDrawable方法

setButtonDrawable方法在API21以下存在一个 非常隐蔽的bug。

在API21以下,如果CompoundButton已设置了一个buttonDrawable(非空),然后在调用setButtonDrawable(null),你会发现之前设置的buttonDrawable仍然存在!根本没有被置空。

至于原因非常简单,对比一下源码就一目了然了。下面附上API23 和API19的相关源码。

图片

关于obtain属性

好吧,这个obtain属性非常怪,有时候会出些莫名其妙的bug。

  • 在API21以下,如果在int [] ATTRS数组中将android属性放在自定义属性之后读取,则你会发现android属性的值将无法取到,-,-是不是很奇葩。

  • 在API19上,如果将drawableLeft之类的android属性放在一个int [] ATTRS中通过TypeArray读取时,除了第一个android属性能取到resourceId,之后的drawableXxx的resourceId解析的值都为0。

  • 目前的解决方案是针对每个attr都单独obtain一次,如果有更好的解决方案,欢迎支持。

拖沓了两个月终于踩着五月份的尾巴把文章发了,唏嘘…(拖延症害死人--|||)

微信扫一扫
关注该公众号

继续滑动看下一个
Android程序员
向上滑动看下一个