Android如何优雅地解决重复Drawable资源

6,930 阅读4分钟

1. 前言

最近鸿洋大神和路遥大佬分别在他们的公众号上发布了关于解决Shape/Selector冗余的方案。这篇文章在上周末就已经写好了。虽然类似的解决方案特别多,实现思路也都差不多。但我仍然要安利一下我的这个解决方案。原因有以下几点。

  1. 很纯粹,就是用代码的方式实现了xml实现的Drawable,不用重写自定义View或者Hook系统的基础组件。

  2. 最大程度的复刻xml所拥有的能力,甚至连单位dp还是px的api都提供好了。

  3. 使用Builder模式将方法和参数都约束起来,使用起来很方便,不用去众多的api中寻找方法。结合Kotlin的语法,一个字“香”。

  4. 内部实现了缓存策略,以及根据Hash判重策略,这也是目前市面上的其他解决方案所没有的。

当然美中不足的是,目前所有的xml替换都是需要手工去完成,如果在编译期能够通过gradle插件自动转换,那就完美了。如果您有相关的经验,可以尝试一起把这个库做得更好。

2. Android为什么用xml生成Drawable

xml是Android系统中生成Drawable的首选方案,所以很多同学都习惯了使用xml生成GradientDrawable和SelectorDrawable,它们确实很方便。但是随之而来的问题,我相信很多同学都是深有体会的,哪怕是GradientDrawable中一个圆角大小的改动,或者一个颜色值的改动,都需要在原来的xml文件基础上拷贝一份新的xml文件,这样导致项目中的drawable文件越来越多。甚至一些编码规范没做好的团队,明明完全一样效果的drawable在项目中也有可能出现多份。

针对这种情况,有没有必要处理呢?大部分的xml文件也就1 2kb,占用空间很小,对包体积大小影响也没那么大。虽然说Android系统Drawable缓存是以文件名为维度的,但是它的回收策略做的挺棒的,冗余的xml对内存占用有影响,但没那么大。

那就任由文件数量膨胀吗?我觉得答案是见仁见智的,不处理也可以,无非就是写起来臃肿点呗,至少不用花时间去想一套解决方案。当然我们也可以精益求精,使用代码生成Drawable方案,实现与xml完全一样的效果,同时又能避免冗余的xml文件出现。

意外的收获👉 在项目使用svg作为图片时,发现在Android5.0 和Android6.0手机上,xml定义的selector图片显示不正常。究其原因是因为Android7.0以下不支持svg格式的fillType,导致selector渲染出来的图片有问题。想了很多方法都无法解决,最终通过代码生成selector的方案解决了。

在开始写通过代码生成Drawable之前,首先思考一个问题?为什么Android系统会首选xml生成Drawable方案呢?

通过分析xml渲染Drawable原理,我觉得系统兼容可能是使用xml的一个重要原因。以GradientDrawable的setPadding方法为例,该方法在Android Q版本引入。如果我们在xml文件引入padding,在Android Q以下版本也不会出问题。如果是代码中使用就需要做版本判断

<padding android:top="10dp" android:bottom="10dp" android:left="10dp" android:right="10dp"></padding>

闲话少叙,先看看最终的效果,下图左边是通过xml生成GradientDrawable,右边是通过代码生成GradientDrawable效果。

3. xml实现和代码实现

看下具体代码实现

  1. GradientDrawable xml实现

  2. GradientDrawable代码实现

  3. StateListDrawable xml实现

  4. StateListDrawable 代码实现

addState(StatePressed)方法表示android:state_pressed="true"

minusState(StatePressed)方法表示android:state_pressed="false"

当然也可以添加多个状态

4. 特性以及源码

该库有以下特性:

  1. xml能实现的,它全部能实现
  2. 使用Builder模式,更容易构建Drawable
  3. 支持所有的android:state_xxx
  4. GradientDrawable,只要所有构建的参数内容一样(顺序可以打乱),内存中只会保留一份

由于一部分读者还没掌握科学上网,源码自己放在公众号上了,代码量并不多,拿来就能用。源码地址👉源码地址