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的区别
- selected不同于focus状态,通常在AdapterView类群下例如ListView或者GridView会使某个View处于selected状态,并且获得该状态的View处于高亮状态。
- 一个窗口只能有一个视图获得焦点(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
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。