如何维护(替换)drawable xml是android开发中一个老生常谈的话题。按照标准的Android布局开发模式,咱们不得不为各类UI效果新建不一样的xml文件进行描述,哪怕是简单的一个圆角。随着项目迭代,成百上千的xml连同那模棱两可的文件名,不只让开发者复用或清理的成本难以估计,还使得项目体积急剧增大。所以,下面咱们探索一种原理巧妙、适配全面的drawable替代方案。android
咱们先归纳下目前市面上已有的方案,大体分为两种实现方式。git
一种是继承某个(或某几个)经常使用的控件,而后将drawable.xml中的经常使用属性做为当前控件的自定义属性,最后在控件内部动态生成drawable做为该控件的背景。这种方案的优势很明显:能直观地将drawable效果描述做为控件的属性定义在布局xml中,具备很好的可读性;可是缺点也不可忽视,这些属性并不能应用到任意控件,致使在不少时候仍是不得不建立drawable.xml文件。github
另外一种方案则是将drawable的经常使用属性封装为代码API,以动态的方式在代码中生成并赋值给控件。这种方案理论上彻底抛弃了drawable.xml,能够适配任意控件,可是若想彻底以这种方式达到彻底替换xml,我的以为不可能,代码量大,关联性低是其最大的缺点,单看布局,无从知晓该控件的最终效果。不过,若是两相结合,做为对第一种方案的补充却是一个不错的方案。bash
上述两种方案各有千秋,但都没法彻底解决问题,咱们对上述两种方案进行分析,提出如下问题:为何不能有一种「既具备高可读性,又能全面适配」的drawable.xml替代方案呢?也就是说能同时兼顾前面提到的两种方案的优势,高可读性意味着对drawable的描述须要做为属性定义在布局文件中、全面适配意味这些属性对任意控件都有效。思来想去,答案彷佛只有一个:DataBinding。说到这里,可能有些朋友已经隐隐猜到了,不过别急,容我娓娓道来。布局
DataBinding是Android官方推出的数据绑定库,尽管已有数年,可是我估计仍有部分开发者尚未接触甚至有些抵触,具体就不细说,可是我但愿你暂且能拥抱它,继续阅读。
数据绑定让数据变化能直接反映到布局中,对于控件已有的属性,例如TextView的android:text
属性,一旦经过DataBinding绑定:ui
<TextView
android:text="@{name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
复制代码
在运行时内部就会调用TextView内部的setText方法。其实现原理的关键就是DataBinding经过提供的@BindingAdapter
注解,该注解将任意指定的属性和任意指定的方法关联,DataBinding会在编译的时候动态生成的调用关系,而对于经常使用的控件,DataBinding已经预置了对应的注解方法,例如如下就是TextView的setText方法:spa
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything. } } else if (!haveContentsChanged(text, oldText)) { return; // No content changes, so don't set anything.
}
view.setText(text);
}
复制代码
咱们须要关注的就是这个@BindingAdapter
注解,「任意指定的属性」这个属性并不是特指咱们在布局中Android提供的标准属性,也就是说,咱们能够提供任意字符串做为属性,而任意方法很好理解,上面的代码片断很好的表达了这个意思,咱们惟一须要关注的就是这个方法的参数:第一个参数是指定注解中的属性的做用域,后面的参数则是和注解所声明的属性一一对应,那么结合到咱们本文的主题,答案也就呼之欲出了:code
提供一个用@BindingAdapter
注解的方法,做用域指定为View(即任意控件);参数约定为drawable.xml中的属性,不就达到了目的吗。是不是感受到一丝丝巧妙?既然方案有了,下面咱们来看具体实现。xml
限于drawable属性的丰富性,本文以经常使用的属性solid 和 corner为例展开。如如下片断所示:继承
@BindingAdapter(value = {
"drawable_solidColor",
"drawable_radius",
}, requireAll = false)
public static void setViewBackground(View v, int color, int radius) {
GradientDrawable drawable = new GradientDrawable();
drawable.setColor(color);
drawable.setCornerRadius(radius);
view.setBackground(drawable);
}
复制代码
上面代码片断定义了两个属性:drawable_solidColor
, drawable_radius
,分别表示solid的color和corner的radius属性,也就是说稍后咱们就就能够在布局文件中为每一个View都指定该属性了;
这里可能有朋友会产生疑问,drawable的属性那么多,这里只定义了两个还好,若是把全部的drawable属性都定义,那岂不是每一个控件都要把每一个属性都指定一次,即便不须要。因此还须要提一下requireAll
参数,它表示是否须要每一个属性都必须绑定了数据才会调用setViewBackground
方法,设置为false后,就能够在布局文件中只指定须要的属性便可。
以上几行代码完成了基本定义,下面咱们来看看如何使用:
<layout>
<TextView
drawable_radius="@{10}"
drawable_solidColor="@{0xffff0000}"
android:layout_width="60dp"
android:layout_height="60dp" />
<layout/>
复制代码
不用怀疑,就是这么简单,即便这里不贴出效果图,我想你们脑海中已经浮现出来了,是否是以为一目了然?以此类推,其它的drawable属性也能够经过本方案逐一实现。
回顾本文,并无任何复杂的代码或高深的逻辑组合,仅提出一种巧妙的drawable.xml替代方案,具备「既具备高可读性,又能全面适配」的特色。
从成原本说,本方案应该是最低的(特别是对一些已经在使用DataBinding的项目):只须要定义一个方法便可,而效果倒是最优的:理论来说,实现该方案后,能够减小99%的drawable.xml建立。 若是非要说出本方案的缺点,那么它的实现原理所依赖的核心库DataBinding多是有些开发者所不能接受的。
读到这里,是否以为意犹未尽?没错,我已依据本文的方案替你们整理好了几乎全部经常使用的drawable属性提交到了GitHub,核心依然是只有一个方法,直接可用。
Github地址:github.com/whataa/noDr…