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。
Android中的Selector主要是用來改變ListView和Button控件的默認背景。其使用方法可以按一下步驟來設計:(以在mylist_view.xml為例)
1.創建mylist_view.xml文件
首先在res目錄下新建drawable文件夾,再在新建的drawable文件夾中新建mylist_view.xml,其目錄結構為:res/drawable/mylist_view.xml。
2.根據具體需求編輯mylist_view.xml文件
新建mylist_view.xml文件後,在沒有添加任何屬性時其內部代碼結構為:
[html]
- 下面就可以根據項目需求,在其內部定義為自己想要的樣式瞭,主要屬性如下: [html] view plaincopy
- android:drawable=@drawable/pic1 />
- 3.引用mylist_view.xml文件
三種方法可以來引用剛才創建的文件:
(1)在ListView中添加如下屬性代碼
[html]
- android:listSelector=@drawable/mylist_view (2)在ListView的item界面中添加如下屬性代碼
[html]- android:background=@drawable/mylist_view (3)利用JAVA代碼直接編寫 [java] view plaincopy
- Drawable drawable = getResources().getDrawable(R.drawable.mylist_view);
- listView.setSelector(drawable); 為瞭防止列表拉黑的情況發生,需要在ListView中添加以下的屬性代碼 [html
- android:cacheColorHint=@android:color/transparent 屬性介紹:
android:state_selected選中
android:state_focused獲得焦點
android:state_pressed點擊
android:state_enabled設置是否響應事件,指所有事件
- android:cacheColorHint=@android:color/transparent 屬性介紹:
- android:background=@drawable/mylist_view (3)利用JAVA代碼直接編寫 [java] view plaincopy
- android:listSelector=@drawable/mylist_view (2)在ListView的item界面中添加如下屬性代碼