標籤:
Android開發技巧——自訂控制項之自訂屬性
掌握自訂控制項是很重要的,因為通過自訂控制項,能夠:解決UI問題,最佳化布局效能,簡化布局代碼。
上一篇講了如何通過xml把幾個控制群組織起來,並繼承某個ViewGroup子類,把它們封裝起來使用。這是我們接觸到的最簡單的一種自定製控制項了。但許多時候,我們還需要在布局檔案中使用它們的時候,能通過屬性傳入一些值,來影響最終的顯示結果。
我們在做項目中經常會遇到的一個情況:一張圖片加一個文本的組合。比如儲值賬戶成功之後顯示的一個介面,上面是一個表示成功的表徵圖,下面是對應的解說文字:“儲值成功”。或者是在登入介面的賬戶輸入框中,輸入框的左邊需要顯示一個表示賬戶的表徵圖。如下所示:
笨重的實現表徵圖文字組合
一開始對於這樣的情況,我們可能會採用ImageView加TextView的方式。後來通過lint工具的提示,或者是其他的方式,你可能會知道TextView的幾個屬性drawableLeft,drawableRight,drawableTop以及drawableBottom可以做到。但是使用的時候,你會發現這幾個屬性設定進去的圖片,是按其本身大小來顯示的。
好像也沒關係,讓設計師切好圖就是了。但是心裡卻是沒底的。因為Android手機萬萬種,你公司的測試機卻只有那兩三個,也許換上某個大屏低解析度的千元機,表徵圖就被撐大了。所以你還是希望能設定圖片的大小。
使用一個TextView實現
設定一個TextView的drawable大小,可以通過在java代碼中調用drawable的setBounds(int left, int top, int right, int bottom)方法設定它的界限,然後調用setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)方法把我們的drawable對象設定進去。
但是每次調用都要設定未免太麻煩,所以我們可以繼承TextView寫一個DrawableTextView,重寫setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)方法;或者是setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top, Drawable end, Drawable bottom),如果你是通過drawableStart及drawableEnd屬性來設定圖片的話。通過在這裡對drawable進行setBounds(),也一樣能達到上面的效果。
現在我們用一個TextView實現了上面TextView+ImageView的效果,達到了我們的目的:最佳化布局效能,簡化布局代碼。
但是對於這樣的控制項,我們可能在項目的多個地方用到,或者在其他項目也會用到。所以希望能把它做到更通用。這時我們就不能寫死drawable的寬和高,而是希望能夠在外部xml使用它時進行控制,也就是——自訂屬性。
自訂屬性
首先在res/values/attrs.xml檔案中(如果沒有請建立)定義一個declare-styleable,然後在裡面定義兩個屬性,分別表示drawable的寬和高:
<declare-styleable name="DrawableTextView"> <attr name="drawableHeight" format="dimension"/> <attr name="drawableWidth" format="dimension"/></declare-styleable>
其中declare-styleable的名字name通常寫為我們的控制項的類名,這樣在寫布局代碼中,Android Studio就可以給我們對應的屬性提示了。
在attr中定義的是我們的屬性,name是屬性的名字,format是屬性的類型,這裡我們把類型定義為dimension。
屬性是定義好了,也可以布局代碼中使用了。但是我們還需要在我們的自訂的DrawableTextView中讀取屬性。首先重寫構造方法:
public DrawableTextView(Context context) { super(context); } public DrawableTextView(Context context, AttributeSet attrs) { super(context, attrs); applyAttributes(context, attrs); } public DrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); applyAttributes(context, attrs); }
然後在裡面調用我們自訂的applyAttributes()方法,在裡面我們會讀取到屬性。我們需要在我們的類中聲明兩個屬性:
private int mDrawableWidth; private int mDrawableHeight;
然後在applyAttributes()方法中,通過TypedArray擷取屬性值:
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView); mDrawableWidth = typedArray.getDimensionPixelSize(R.styleable.DrawableTextView_drawableWidth, 0); mDrawableHeight = typedArray.getDimensionPixelSize(R.styleable.DrawableTextView_drawableHeight, 0); typedArray.recycle();
這裡說明一下,attrs是我們在xml中定義的相關聯的屬性集。通過調用context.obtainStyledAttributes(attrs, R.styleable.DrawableButton);,我們能夠接收到在Context所對應的theme中我們的樣式屬性,也就是這裡的屬性值我們也可以通過在theme中來設定。關於通過在theme中指定屬性值,後續的部落格會再詳細說明,這裡暫略不談。第二個參數傳的是我們所定義的declare-styleable
得到TypedArray對象之後,通過它裡面的getXXX方法我們可以得到各個屬性的值,比如通過getDimensionPixelSize(int index, int defValue)方法得到屬性尺寸對應的像素大小。第一個參數是屬性的索引,第二個是預設值。在擷取完屬性之後,必須調用typedArray.recycle()對它進行回收,以便後面的調用者可以複用。因為Resources對象中會緩衝一些TypedArray的資料,TypedArray使用的時候會從中擷取,以最佳化記憶體利用。
拿到屬性值並賦給我們的成員變數之後,我們就可以在後面的方法進行設定了。由於本篇主要講自訂屬性,關於該項目的後續實現,可以參考我在Github上的項目 DrawableWidget。
上面的例子中,關於在attrs定義屬性其實是存在著問題的。因為我們直接就定義了drawableWidth及drawableHeight屬性,如果我有一個項目使用到它,並且同時使用了另外一個庫,那個庫也定義了這兩個屬性的話,就會產生衝突了。所以要強調一點:對於自訂屬性,必須加上我們自己的首碼。比如把屬性定義為nbDrawableWidth等等。(我那個項目在寫的時候還不成熟,而且自己有些懶,沒遇到問題總是不想改,暫且留作反例吧。)
共用屬性
還有一種情況。我們在布局裡會經常遇到這樣一種情況:
在布局裡面的每一個條目,會用分割線分開。而這些條件還會分成幾組,組裡面的分割線是有左邊距的。這樣的情況很好實現,重寫這個layout,在onDraw裡面畫線就可以了。我們把右邊的每一個item看成是:上面一條滿的分割線或者是不帶分割線,下面是一條帶邊距的分割線,或者是滿的分割線。這樣我們就可以定義一個pwBorder屬性,裡面定義<flag name="TOP" value="1"/>``<flag name="BOTTOM" value="2"/>表示標記位。然後在代碼裡擷取pwBorder的值,與1做&操作,大於0就表示要畫上面的分割線,否則不畫,再和2做&操作,大於0就表示畫完整的分割線,否則畫帶邊距的分割線。如果需要左分割線或右分割線時,還可以再定義值為4或者8的標誌位。
在xml引用它時,屬性是這樣的:
app:pwBorder="TOP|BOTTOM"
但是,可能除了這個RelativeLayout需要這種屬性之外,我們在寫的其他控制項,可能也要包括這個屬性以及其他屬性。這時我們可以把attr的定義抽出來,寫在declare-styleable節點外面。如下:
<!--帶邊框的Layout屬性--> <attr name="pwBorder"> <flag name="TOP" value="1"/> <flag name="BOTTOM" value="2"/> </attr> <attr name="pwBorderWidth" format="dimension"/> <attr name="pwBorderColor" format="color"/>
然後再這樣使用:
<declare-styleable name="BorderLinearLayout"> <attr name="pwBorder"/> <attr name="pwBorderWidth"/> <attr name="pwBorderColor"/> </declare-styleable> <declare-styleable name="TextFieldView"> <attr name="pwBorder"/> <attr name="pwBorderWidth"/> <attr name="pwBorderColor"/> <attr name="pwLabel"/> <attr name="pwValue" format="string"/> <attr name="pwIcon" format="reference"/> </declare-styleable>
我建議使用這種方式來定義屬性。因為這樣當我們的屬性與其他庫的屬性正好重名時,如果定義的format相同,也是可以被合并,並且通過編譯的。
本文原創,轉載請註明出處:http://blog.csdn.net/maosidiaoxian/article/details/50009725
[1]https://github.com/msdx/DrawableWidget
Android開發技巧——自訂控制項之自訂屬性