Android -- selector&&StateListDrawable

selector                                                                               

<?xml version="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>

StateListDrawable                                                                   

该类定义了不同状态值下与之对应的图片资源,即我们可以利用该类保存多种状态值,多种图片资源。

public void addState (int[] stateSet, Drawable drawable)

给特定的状态集合设置drawable图片资源,参考前面的selector的xml文件,我们利用代码去构建一个相同的StateListDrawable类对象

//初始化一个空对象  
StateListDrawable stalistDrawable = new StateListDrawable();  
//获取对应的属性值 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);  
//没有任何状态时显示的图片,我们给它设置我空集合  
stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.pic5);

上面的“-”负号表示对应的属性值为false

public boolean isStateful ()

表明该状态改变了,对应的drawable图片是否会改变。(在StateListDrawable类中,该方法返回为true,显然状态改变后,我们的图片会跟着改变。)

状态                                                                                   

android:state_selected 选中
android:state_focused 获得焦点
android:state_pressed 点击
android:state_enabled 设置是否响应事件,指所有事件

selected和focused的区别                                                            

  1. selected不同于focus状态,通常在AdapterView类群下例如ListView或者GridView会使某个View处于selected状态,并且获得该状态的View处于高亮状态。
  2. 一个窗口只能有一个视图获得焦点(focus),而一个窗口可以有多个视图处于”selected”状态中。
  • focused状态一般是由按键操作引起的;
  • pressed状态是由触摸消息引起的;
  • selected则完全是由应用程序主动调用setSelected()进行控制。

分析                                                                                   

当View任何状态值发生改变时,都会调用refreshDrawableList()方法去更新对应的背景Drawable对象。

//主要功能是根据当前的状态值去更换对应的背景Drawable对象  
    public void refreshDrawableState() {  
        mPrivateFlags |= DRAWABLE_STATE_DIRTY;  
        //所有功能在这个函数里去完成  
        drawableStateChanged();  
        //...  
    }  

    // 获得当前的状态属性--- 整型集合 ; 调用Drawable类的setState方法去获取资源。  
    protected void drawableStateChanged() {  
        //该视图对应的Drawable对象,通常对应于StateListDrawable类对象  
        Drawable d = mBGDrawable;     
        if (d != null && d.isStateful()) {  //通常都是成立的  
            //getDrawableState()方法主要功能:会根据当前View的状态属性值,将其转换为一个整型集合  
            //setState()方法主要功能:根据当前的获取到的状态,更新对应状态下的Drawable对象。  
            d.setState(getDrawableState());  
        }  
    }  

    public final int[] getDrawableState() {  
        if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {  
            return mDrawableState;  
        } else {  
            //根据当前View的状态属性值,将其转换为一个整型集合,并返回  
            mDrawableState = onCreateDrawableState(0);  
            mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;  
            return mDrawableState;  
        }  
    }

setState()

//如果状态态值发生了改变,就回调onStateChange()方法。  
public boolean setState(final int[] stateSet) {  
    if (!Arrays.equals(mStateSet, stateSet)) {  
        mStateSet = stateSet;  
        return onStateChange(stateSet);  
    }  
    return false;  
}

判断状态值是否发生了变化,如果发生了变化,就调用onStateChange()方法进一步处理。

//状态值发生了改变,我们需要找出第一个吻合的当前状态的Drawable对象  
protected boolean onStateChange(int[] stateSet) {  
    //要找出第一个吻合的当前状态的Drawable对象所在的索引位置, 具体匹配算法请自己深入源码看看  
    int idx = mStateListState.indexOfStateSet(stateSet);  
    ...  
    //获取对应索引位置的Drawable对象  
    if (selectDrawable(idx)) {  
        return true;  
    }  
    ...  
}

根据新的状态值,从StateListDrawable实例对象中,找到第一个完全吻合该新状态值的索引下标处 ; 继而,调用selectDrawable()方法去获取索引下标的当前Drawable对象。

public boolean selectDrawable(int idx)  
{  
    if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {  
        //获取对应索引位置的Drawable对象  
        Drawable d = mDrawableContainerState.mDrawables[idx];  
        ...  
        mCurrDrawable = d; //mCurrDrawable即使当前Drawable对象  
        mCurIndex = idx;  
        ...  
    } else {  
       ...  
    }  
    //请求该View刷新自己,这个方法我们稍后讲解。  
    invalidateSelf();  
    return true;  
}

Drawable.Callback                                                                    

public static interface Callback {  
    //如果Drawable对象的状态发生了变化,会请求View重新绘制,  
    //因此我们对应于该View的背景Drawable对象能够”绘制出来”.  
    public void invalidateDrawable(Drawable who); 
 
    public void scheduleDrawable(Drawable who, Runnable what, long when);   
    public void unscheduleDrawable(Drawable who, Runnable what);  
}

Android框架View类继承了该接口,同时实现了这三个函数的默认处理方式,其中invalidateDrawable()方法如下:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource   
{  
    ...  
    //Invalidates the specified Drawable.  
    //默认实现,重新绘制该视图本身  
    public void invalidateDrawable(Drawable drawable) {  
        if (verifyDrawable(drawable)) { //是否是同一个Drawable对象,通常为真  
            final Rect dirty = drawable.getBounds();  
            final int scrollX = mScrollX;  
            final int scrollY = mScrollY;  
            //重新请求绘制该View,即重新调用该View的draw()方法  ...  
            invalidate(dirty.left + scrollX, dirty.top + scrollY,  
                    dirty.right + scrollX, dirty.bottom + scrollY);  
        }  
    }  
    ...  
}

因此,我们的Drawable类对象必须将View设置为回调对象,否则,即使改变了状态,也不会显示对应的背景图。

Drawable d  ;                // 图片资源                        
d.setCallback(View v) ;  // 视图v的背景资源为 d 对象

我是天王盖地虎的分割线                                                             

 

 

参考:http://blog.csdn.net/qinjuning/article/details/7474827

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。