如何維護(替換)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。說到這裏,可能有些朋友已經隱隱猜到了,不過別急,容我娓娓道來。markdown
DataBinding是Android官方推出的數據綁定庫,儘管已有數年,可是我估計仍有部分開發者尚未接觸甚至有些抵觸,具體就不細說,可是我但願你暫且能擁抱它,繼續閱讀。
數據綁定讓數據變化能直接反映到佈局中,對於控件已有的屬性,例如TextView的android:text
屬性,一旦經過DataBinding綁定:oop
<TextView android:text="@{name}" android:layout_width="wrap_content" android:layout_height="wrap_content" /> 複製代碼
在運行時內部就會調用TextView內部的setText方法。其實現原理的關鍵就是DataBinding經過提供的@BindingAdapter
註解,該註解將任意指定的屬性和任意指定的方法關聯,DataBinding會在編譯的時候動態生成的調用關係,而對於經常使用的控件,DataBinding已經預置了對應的註解方法,例如如下就是TextView的setText方法:佈局
@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提供的標準屬性,也就是說,咱們能夠提供任意字符串做爲屬性,而任意方法很好理解,上面的代碼片斷很好的表達了這個意思,咱們惟一須要關注的就是這個方法的參數:第一個參數是指定註解中的屬性的做用域,後面的參數則是和註解所聲明的屬性一一對應,那麼結合到咱們本文的主題,答案也就呼之欲出了:ui
提供一個用@BindingAdapter
註解的方法,做用域指定爲View(即任意控件);參數約定爲drawable.xml中的屬性,不就達到了目的嗎。是不是感受到一絲絲巧妙?既然方案有了,下面咱們來看具體實現。spa
限於drawable屬性的豐富性,本文以經常使用的屬性solid 和 corner爲例展開。如如下片斷所示:code
@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…