android的selector對於android開發者而言再熟悉不過了,只要定義一個drawable目錄下定義一個selector的xml文件,在佈局文件中引用這個xml文件或者在代碼中setBackgroundDrawable的時候使用此xml就可以實現控制項按下或有焦點等不同狀態的效果。 那麼s ...
android的selector對於android開發者而言再熟悉不過了,只要定義一個drawable目錄下定義一個selector的xml文件,在佈局文件中引用這個xml文件或者在代碼中setBackgroundDrawable的時候使用此xml就可以實現控制項按下或有焦點等不同狀態的效果。
那麼setBackgroundDrawable後為什麼可以實現這個功能呢?
首先要瞭解一個Drawable類,Drawable是一個抽象的可繪製的圖片類,這個類可以從一個本地路徑中創建一個圖片,也可以使用從定義好的xml中創建,他們分別對應Drawable的createFromPath和createFromXml函數,其中createFromPath是從路徑中創建一個Bitmap對象並將它轉換成BitmapDrawable,而createFromXml是從xml中定義的標簽,例如selector的話就創建StateListDrawable對象,shape的話就創建GradientDrawable對象,color的話就創建ColorDrawable......而BitmapDrawable、StateListDrawable、GradientDrawable都是從Drawable類中派生而來。其中StateListDrawable類就是實現selector中定義的樣式的Drawable.
其次我們看Drawable怎麼跟View關聯的。
Drawable類有維護了一個控制項的不同狀態的變數mStateSet,當View.setBackgroundDrawable時,會調用Drawable的isStateful函數判斷是否有不同狀態的,StateListDrawable返回的true,如果是有狀態的就會將view的狀態賦值給drawable即d.setState(getDrawableState());
if (d.isStateful()) {d.setState(getDrawableState());
}
同時將傳入的Drawable作為背景的Drawable.當控制項接收到touch事件時會調用refreshDrawableState更新控制項狀態,同時也會更新背景的Drawable的狀態
protected void drawableStateChanged() {
Drawable d = mBGDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
然後會調用invalidateDrawable這個回調函數來刷新界面,同時調用draw函數實現繪製。
再次我們來看實現Selector功能的Drawable即StateListDrawable是如何實現Selector功能的。
上面我們己經看到在View狀態改變的時候,會調用Drawable的setState函數。在Drawable中是這樣實現setState的
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
它在改變狀態的時候會調用onStateChage來通知狀態己經改變了。而StateListDrawable是繼承Drawable的子類它覆寫了onStateChage函數
protected boolean onStateChange(int[] stateSet) {
int idx = mStateListState.indexOfStateSet(stateSet);
if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
+ Arrays.toString(stateSet) + " found " + idx);
if (idx < 0) {
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}
if (selectDrawable(idx)) {
return true;
}
return super.onStateChange(stateSet);
}
從上面的實現可以看到它在改變狀態的時候會調用selectDrawable來選擇一個當前狀態的drawable,這就是實現的關鍵了。StateListDrawable繼承了DrawableContainer而DrawableContainer繼承了Drawable,StateListState是StateListDrawable的內部類,它就是保存selector中定義的不同狀態的drawable的實現,它提供了addStateSet函數來增加某個狀態下對應的drawable對象並將它保存在mStateSets變數中,而indexOfStateSet函數則是查找某個狀態下對應的drawable。selectDrawable是DrawableContainer的類,它是根據傳入的狀態的索引來找到對應的drawable來當作當前狀態下的drawable。
OK,現我我們終於能理解為什麼selector是如何實現不同狀態不同樣式了。View使用Drawable來實現背景圖,selector對應StateListDrawable,當view狀態改變時,會改變drawable的狀態,StateListDrawable在改變狀態時會根據當前狀態選擇對應的drawable,這樣在view繪製時會調用drawable的draw函數,StateListDrawable draw的是當前狀態對應的drawable。