1、View的幾種不同狀態(tài)屬性
2、如何根據(jù)不同狀態(tài)去切換我們的背景圖片。
開篇介紹:Android背景選擇器selector用法匯總
對(duì)Android開發(fā)有經(jīng)驗(yàn)的同學(xué),對(duì) **<selector>**節(jié)點(diǎn)的使用一定很熟悉,該節(jié)點(diǎn)的作用就是定義一組狀態(tài)資源圖片,使其能夠
在不同的狀態(tài)下更換某個(gè)View的背景圖片。例如,如下的hello_selection.xml文件定義:
"1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:state_window_focused="true" android:drawable= "@drawable/pic1" />
<item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/pic2" />
<item android:state_selected="true" android:drawable="@drawable/pic3" />
<item android:state_focused="true" android:drawable="@drawable/pic4" />
<item android:drawable="@drawable/pic5" />
</selector>
更多關(guān)于 **<selector>**節(jié)點(diǎn)的使用請(qǐng)參考該博客**<****[android背景選擇器selector用法匯總](http://blog.sina.com.cn/s/blog_4b93170a0100qhwa.html)****>**
其實(shí),前面說的xml文件,最終會(huì)被Android框架解析成**StateListDrawable類對(duì)象。**
知識(shí)點(diǎn)一:StateListDrawable類介紹
類功能說明:該類定義了不同狀態(tài)值下與之對(duì)應(yīng)的圖片資源,即我們可以利用該類保存多種狀態(tài)值,多種圖片資源。
常用方法為:
public void addState (int[] stateSet, Drawable drawable)
功能: 給特定的狀態(tài)集合設(shè)置drawable圖片資源
使用方式:參考前面的hello_selection.xml文件,我們利用代碼去構(gòu)建一個(gè)相同的StateListDrawable類對(duì)象,如下:
//初始化一個(gè)空對(duì)象
StateListDrawable stalistDrawable = new StateListDrawable();
//獲取對(duì)應(yīng)的屬性值 Android框架自帶的屬性 attr
int pressed = android.R.attr.state_pressed;
int window_focused = android.R.attr.state_window_focused;
int focused = android.R.attr.state_focused;
int selected = android.R.attr.state_selected;
stalistDrawable.addState(new int []{pressed , window_focused}, getResources().getDrawable(R.drawable.pic1));
stalistDrawable.addState(new int []{pressed , -focused}, getResources().getDrawable(R.drawable.pic2);
stalistDrawable.addState(new int []{selected }, getResources().getDrawable(R.drawable.pic3);
stalistDrawable.addState(new int []{focused }, getResources().getDrawable(R.drawable.pic4);
//沒有任何狀態(tài)時(shí)顯示的圖片,我們給它設(shè)置我空集合
stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);
上面的“-”負(fù)號(hào)表示對(duì)應(yīng)的屬性值為false
當(dāng)我們?yōu)槟硞€(gè)View使用其作為背景色時(shí),會(huì)根據(jù)狀態(tài)進(jìn)行背景圖的轉(zhuǎn)換。
public boolean isStateful ()
功能: 表明該狀態(tài)改變了,對(duì)應(yīng)的drawable圖片是否會(huì)改變。
注:在StateListDrawable類中,該方法返回為true,顯然狀態(tài)改變后,我們的圖片會(huì)跟著改變。
知識(shí)點(diǎn)二:View的五種狀態(tài)值
一般來說,Android框架為View定義了四種不同的狀態(tài),這些狀態(tài)值的改變會(huì)引發(fā)View相關(guān)操作,例如:更換背景圖片、是否
觸發(fā)點(diǎn)擊事件等;視
視圖幾種不同狀態(tài)含義見下圖:
其中selected和focused的區(qū)別有如下幾點(diǎn):
1,我們通過查看setSelected()方法,來獲取相關(guān)信息。
SDK中對(duì)setSelected()方法----對(duì)于與selected狀態(tài)有如下說明:
public void **setSelected (boolean selected)
Since: [APILevel 1](file:///F:/android-sdk-windows/docs/guide/appendix/api-levels.html#level1)
Changes the selection state of this view. Aview can be selected or not. Note that selection is not the same as
focus. Views are typically selected in the context of an AdapterView like ListView or GridView ;the selected view is
the view that is highlighted.
** Parameters selected true if the view must be selected, false otherwise
由以上可知:selected不同于focus狀態(tài),通常在AdapterView類群下例如ListView或者GridView會(huì)使某個(gè)View處于
selected狀態(tài),并且獲得該狀態(tài)的View處于高亮狀態(tài)。
2、一個(gè)窗口只能有一個(gè)視圖獲得焦點(diǎn)(focus),而一個(gè)窗口可以有多個(gè)視圖處于”selected”狀態(tài)中。
總結(jié):focused狀態(tài)一般是由按鍵操作引起的;
pressed狀態(tài)是由觸摸消息引起的;
selected則完全是由應(yīng)用程序主動(dòng)調(diào)用setSelected()進(jìn)行控制。
例如:當(dāng)我們觸摸某個(gè)控件時(shí),會(huì)導(dǎo)致pressed狀態(tài)改變;獲得焦點(diǎn)時(shí),會(huì)導(dǎo)致focus狀態(tài)變化。于是,我們可以通過這種
更新后狀態(tài)值去更新我們對(duì)應(yīng)的Drawable對(duì)象了。
問題:如何根據(jù)狀態(tài)值的改變?nèi)ダL制/顯示對(duì)應(yīng)的背景圖?
當(dāng)View任何狀態(tài)值發(fā)生改變時(shí),都會(huì)調(diào)用refreshDrawableList()方法去更新對(duì)應(yīng)的背景Drawable對(duì)象。
其整體調(diào)用流程如下: View.[Java](http://lib.csdn.net/base/javase)類中
//路徑:\frameworks\base\core\java\android\view\View.java
/* Call this to force a view to update its drawable state. This will cause
* drawableStateChanged to be called on this view. Views that are interested
* in the new state should call getDrawableState.
/
//主要功能是根據(jù)當(dāng)前的狀態(tài)值去更換對(duì)應(yīng)的背景Drawable對(duì)象
public void refreshDrawableState() {
mPrivateFlags |= DRAWABLE_STATE_DIRTY;
//所有功能在這個(gè)函數(shù)里去完成
drawableStateChanged();
...
}
/ This function is called whenever the state of the view changes in such
* a way that it impacts the state of drawables being shown.
/
// 獲得當(dāng)前的狀態(tài)屬性--- 整型集合 ; 調(diào)用Drawable類的setState方法去獲取資源。
protected void drawableStateChanged() {
//該視圖對(duì)應(yīng)的Drawable對(duì)象,通常對(duì)應(yīng)于StateListDrawable類對(duì)象
Drawable d = mBGDrawable;
if (d != null && d.isStateful()) { //通常都是成立的
//getDrawableState()方法主要功能:會(huì)根據(jù)當(dāng)前View的狀態(tài)屬性值,將其轉(zhuǎn)換為一個(gè)整型集合
//setState()方法主要功能:根據(jù)當(dāng)前的獲取到的狀態(tài),更新對(duì)應(yīng)狀態(tài)下的Drawable對(duì)象。
d.setState(getDrawableState());
}
}
/Return an array of resource IDs of the drawable states representing the
* current state of the view.
*/
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
//根據(jù)當(dāng)前View的狀態(tài)屬性值,將其轉(zhuǎn)換為一個(gè)整型集合,并返回
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
通過這段代碼我們可以明白View內(nèi)部是如何獲取更細(xì)后的狀態(tài)值以及動(dòng)態(tài)獲取對(duì)應(yīng)的背景Drawable對(duì)象----setState()方法
去完成的。這兒我簡單的分析下Drawable類里的setState()方法的功能,把流程給走一下:
Step 1 、 setState()函數(shù)原型 ,
函數(shù)位于:frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 類中
[java] view plaincopyprint?
//如果狀態(tài)態(tài)值發(fā)生了改變,就回調(diào)onStateChange()方法。
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
該函數(shù)的主要功能: 判斷狀態(tài)值是否發(fā)生了變化,如果發(fā)生了變化,就調(diào)用onStateChange()方法進(jìn)一步處理。
Step 2 、onStateChange()函數(shù)原型:
該函數(shù)位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 類中
//狀態(tài)值發(fā)生了改變,我們需要找出第一個(gè)吻合的當(dāng)前狀態(tài)的Drawable對(duì)象
protected boolean onStateChange(int[] stateSet) {
//要找出第一個(gè)吻合的當(dāng)前狀態(tài)的Drawable對(duì)象所在的索引位置, 具體匹配算法請(qǐng)自己深入源碼看看
int idx = mStateListState.indexOfStateSet(stateSet);
...
//獲取對(duì)應(yīng)索引位置的Drawable對(duì)象
if (selectDrawable(idx)) {
return true;
}
...
}
該函數(shù)的主要功能: 根據(jù)新的狀態(tài)值,從StateListDrawable實(shí)例對(duì)象中,找到第一個(gè)完全吻合該新狀態(tài)值的索引下標(biāo)處 ;
繼而,調(diào)用selectDrawable()方法去獲取索引下標(biāo)的當(dāng)前Drawable對(duì)象。
具體查找算法在 mStateListState.indexOfStateSet(stateSet) 里實(shí)現(xiàn)了。基本思路是:查找第一個(gè)能完全吻合該新狀態(tài)值
的索引下標(biāo),如果找到了,則立即返回。 具體實(shí)現(xiàn)過程,只好看看源碼咯。
Step 3 、selectDrawable()函數(shù)原型:
該函數(shù)位于 frameworks\base\graphics\java\android\graphics\drawable\StateListDrawable.java 類中
public boolean selectDrawable(int idx)
{
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
//獲取對(duì)應(yīng)索引位置的Drawable對(duì)象
Drawable d = mDrawableContainerState.mDrawables[idx];
...
mCurrDrawable = d; //mCurrDrawable即使當(dāng)前Drawable對(duì)象
mCurIndex = idx;
...
} else {
...
}
//請(qǐng)求該View刷新自己,這個(gè)方法我們稍后講解。
invalidateSelf();
return true;
}
該函數(shù)的主要功能是選擇當(dāng)前索引下標(biāo)處的Drawable對(duì)象,并保存在mCurrDrawable中。
知識(shí)點(diǎn)三: 關(guān)于Drawable.Callback接口
該接口定義了如下三個(gè)函數(shù):
[java] view plaincopyprint?
//該函數(shù)位于 frameworks\base\graphics\java\android\graphics\drawable\Drawable.java 類中
public static interface Callback {
//如果Drawable對(duì)象的狀態(tài)發(fā)生了變化,會(huì)請(qǐng)求View重新繪制,
//因此我們對(duì)應(yīng)于該View的背景Drawable對(duì)象能夠”繪制出來”.
public void invalidateDrawable(Drawable who);
//該函數(shù)目前還不懂
public void scheduleDrawable(Drawable who, Runnable what, long when);
//該函數(shù)目前還不懂
public void unscheduleDrawable(Drawable who, Runnable what);
}
其中比較重要的函數(shù)為:
public void**invalidateDrawable**(Drawable who)
函數(shù)功能:如果Drawable對(duì)象的狀態(tài)發(fā)生了變化,會(huì)請(qǐng)求View重新繪制,因此我們對(duì)應(yīng)于該View的背景Drawable對(duì)象
能夠重新”繪制“出來。
Android框架View類繼承了該接口,同時(shí)實(shí)現(xiàn)了這三個(gè)函數(shù)的默認(rèn)處理方式,其中**invalidateDrawable**()方法如下:
[java] view plaincopyprint?
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource
{
...
//Invalidates the specified Drawable.
//默認(rèn)實(shí)現(xiàn),重新繪制該視圖本身
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) { //是否是同一個(gè)Drawable對(duì)象,通常為真
final Rect dirty = drawable.getBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
//重新請(qǐng)求繪制該View,即重新調(diào)用該View的draw()方法 ...
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
}
}
...
}
因此,我們的Drawable類對(duì)象必須將View設(shè)置為回調(diào)對(duì)象,否則,即使改變了狀態(tài),也不會(huì)顯示對(duì)應(yīng)的背景圖。 如下:
Drawable d ; // 圖片資源
d.setCallback(View v) ; // 視圖v的背景資源為 d 對(duì)象
知識(shí)點(diǎn)四:View繪制背景圖片過程
在前面的博客中《[**Android中View繪制流程以及invalidate()等相關(guān)方法分析**](http://blog.csdn.net/qinjuning/article/details/7110211)》,我們知道了一個(gè)視圖的背景繪制過程時(shí)在
View類里的draw()方法里完成的,我們這兒在回顧下draw()的流程,同時(shí)重點(diǎn)講解下繪制背景的操作。
[java] view plaincopyprint?
//方法所在路徑:frameworks\base\core\java\android\view\View.java
//draw()繪制過程
private void draw(Canvas canvas){
//該方法會(huì)做如下事情
//1 、繪制該View的背景
//其中背景圖片繪制過程如下:
//是否透明, 視圖通常是透明的 , 為true
if (!dirtyOpaque) {
//開始繪制視圖的背景
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX; //獲取偏移值
final int scrollY = mScrollY;
//視圖的布局坐標(biāo)是否發(fā)生了改變, 即是否重新layout了。
if (mBackgroundSizeChanged) {
//如果是,我們的Drawable對(duì)象需要重新設(shè)置大小了,即填充該View。
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
//View沒有發(fā)生偏移
if ((scrollX | scrollY) == 0) {
background.draw(canvas); //OK, 該方法會(huì)繪制當(dāng)前StateListDrawable的當(dāng)前背景Drawable
} else {
//View發(fā)生偏移,由于背景圖片值顯示在布局坐標(biāo)中,即背景圖片不會(huì)發(fā)生偏移,只有視圖內(nèi)容onDraw()會(huì)發(fā)生偏移
//我們調(diào)整canvas對(duì)象的繪制區(qū)域,繪制完成后對(duì)canvas對(duì)象屬性調(diào)整回來
canvas.translate(scrollX, scrollY);
background.draw(canvas); //OK, 該方法會(huì)繪制當(dāng)前StateListDrawable的當(dāng)前背景Drawable
canvas.translate(-scrollX, -scrollY);
}
}
}
...
//2、為繪制漸變框做一些準(zhǔn)備操作
//3、調(diào)用onDraw()方法繪制視圖本身
//4、調(diào)用dispatchDraw()方法繪制每個(gè)子視圖,dispatchDraw()已經(jīng)在Android框架中實(shí)現(xiàn)了,在ViewGroup方法中。
//5、繪制漸變框
}
That's all ! 我們用到的知識(shí)點(diǎn)也就這么多吧。 如果大家有絲絲不明白的話,可以去看下源代碼,具體去分析下這些流程到底
是怎么走下來的。
我們從宏觀的角度分析了View繪制不同狀態(tài)背景的原理,View框架就是這么做的。為了易于理解性,
下面我們通過一個(gè)小Demo來演示前面種種流程。
** Demo 說明:**
我們參照View框架中繪制不同背景圖的實(shí)現(xiàn)原理,自定義一個(gè)View類,通過給它設(shè)定StateListDrawable對(duì)象,使其能夠在
不同狀態(tài)時(shí)能動(dòng)態(tài)"繪制"背景圖片。 基本流程方法和View.java類實(shí)現(xiàn)過程一模一樣。
截圖如下:
 
** 初始背景圖 觸摸后顯示的背景圖(pressed)**
一、主文件MainActivity.java如下:
[java] view plaincopyprint?
/**
-
@author http://http://blog.csdn.net/qinjuning
*/
public class MainActivity extends Activity
{@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);LinearLayout ll = new LinearLayout(MainActivity.this); CustomView customView = new CustomView(MainActivity.this); //簡單設(shè)置為 width 200px - height 100px吧 ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(200 , 100); customView.setLayoutParams(lp); //需要將該View設(shè)置為可點(diǎn)擊/觸摸狀態(tài),否則觸摸該View沒有效果。 customView.setClickable(true); ll.addView(customView); setContentView(ll);
}
}功能很簡單,為Activity設(shè)置了視圖 。
二、 自定義View如下 , CustomView.java :
[java] view plaincopyprint?
/**
-
@author http://http://blog.csdn.net/qinjuning
/
//自定義View
public class CustomView extends View /extends Button*/
{
private static String TAG = "TackTextView";private Context mContext = null;
private Drawable mBackground = null;
private boolean mBGSizeChanged = true;; //視圖View布局(layout)大小是否發(fā)生變化public CustomView(Context context)
{
super(context);
mContext = context;
initStateListDrawable(); // 初始化圖片資源
}// 初始化圖片資源
private void initStateListDrawable()
{
//有兩種方式獲取我們的StateListDrawable對(duì)象:
// 獲取方式一、手動(dòng)構(gòu)建一個(gè)StateListDrawable對(duì)象
StateListDrawable statelistDrawable = new StateListDrawable();int pressed = android.R.attr.state_pressed; int windowfocused = android.R.attr.state_window_focused; int enabled = android.R.attr.state_enabled; int stateFoucesd = android.R.attr.state_focused; //匹配狀態(tài)時(shí),是一種優(yōu)先包含的關(guān)系。 // "-"號(hào)表示該狀態(tài)值為false .即不匹配 statelistDrawable.addState(new int[] { pressed, windowfocused }, mContext.getResources().getDrawable(R.drawable.btn_power_on_pressed)); statelistDrawable.addState(new int[]{ -pressed, windowfocused }, mContext.getResources().getDrawable(R.drawable.btn_power_on_nor)); mBackground = statelistDrawable; //必須設(shè)置回調(diào),當(dāng)改變狀態(tài)時(shí),會(huì)回掉該View進(jìn)行invalidate()刷新操作. mBackground.setCallback(this); //取消默認(rèn)的背景圖片,因?yàn)槲覀冊(cè)O(shè)置了自己的背景圖片了,否則可能造成背景圖片重疊。 this.setBackgroundDrawable(null); // 獲取方式二、、使用XML獲取StateListDrawable對(duì)象 // mBackground = mContext.getResources().getDrawable(R.drawable.tv_background);
}
protected void drawableStateChanged()
{
Log.i(TAG, "drawableStateChanged");
Drawable d = mBackground;
if (d != null && d.isStateful())
{
d.setState(getDrawableState());
Log.i(TAG, "drawableStateChanged and is 111");
}Log.i(TAG, "drawableStateChanged and is 222"); super.drawableStateChanged();
}
//驗(yàn)證圖片是否相等 , 在invalidateDrawable()會(huì)調(diào)用此方法,我們需要重寫該方法。
protected boolean verifyDrawable(Drawable who)
{
return who == mBackground || super.verifyDrawable(who);
}
//draw()過程,繪制背景圖片...
public void draw(Canvas canvas)
{
Log.i(TAG, " draw -----");
if (mBackground != null)
{
if(mBGSizeChanged)
{
//設(shè)置邊界范圍
mBackground.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
mBGSizeChanged = false ;
}
if ((getScrollX() | getScrollY()) == 0) //是否偏移
{
mBackground.draw(canvas); //繪制當(dāng)前狀態(tài)對(duì)應(yīng)的圖片
}
else
{
canvas.translate(getScrollX(), getScrollY());
mBackground.draw(canvas); //繪制當(dāng)前狀態(tài)對(duì)應(yīng)的圖片
canvas.translate(-getScrollX(), -getScrollY());
}
}
super.draw(canvas);
}
public void onDraw(Canvas canvas) {
...
}
}將該View設(shè)置的背景圖片轉(zhuǎn)換為節(jié)點(diǎn)xml,形式如下:
[java] view plaincopyprint?
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:state_window_focused="true"
android:drawable="@drawable/btn_power_on_pressed"></item>
<item android:state_pressed="false"
android:state_window_focused="true"
android:drawable="@drawable/btn_power_on_nor"></item>
</selector>
基本上所有功能都在這兒顯示出來了, 和我們前面說的一模一樣吧。
當(dāng)然了,如果你想偷懶,大可用系統(tǒng)定義好的一套工具 , 即直接使用setBackgroundXXX()或者在設(shè)置對(duì)應(yīng)的屬性,但是,
萬變不離其宗,掌握了繪制原理,可以瀟灑走江湖了。
** 示例Demo下載地址: http://download.csdn.net/detail/qinjuning/4237298**