selector是Android中的背景选择器。一个selector使用几个不同的drawable来表示相同的图形,根据对象的状态来决定使用哪一个drawable。比如,一个按钮可以有不同的状态,默认状态、被按下的状态。
官方文档:
https://developer.android.com/guide/topics/resources/drawable-resource.html
一、selector的使用
selector的使用语法如下。
其中会使用到的所有的状态如下。
android:state_enabled :是否可用状态(适用于所有View)。
android:state_pressed :被点击状态(如按钮被触摸或点击)。
android:state_focused :获取到焦点状态(如文本输入框获取到焦点)。
android:state_selected :被选中状态(使用view.setSelected()可触发)。
android:state_checkable :是否可勾选状态(适用于可勾选的控件,如CheckBox)。
android:state_checked :是否被勾选状态。
android:state_hovered :光标悬停状态。4.0版本以上支持。
android:state_activated :被激活状态(使用view.setActivated()可触发)。3.0版本以上支持。
android:state_window_focused :当前应用程序窗口是否在前台的状态(如通知栏被拉下或对话框弹出时,当前窗口失去焦点)。
典型案例,为按钮添加selector,按钮拥有默认状态和点击态。在res/drawable/路径下添加button_bg.xml。
将上面定义的selector添加到Button中。
或
button.setBackgroundDrawable(context.getResources.getDrawable(R.drawable.button_bg));
注意:selector中从上到下第一个能够匹配当前状态的item会被使用。如果一个item没有定义任何状态,那么可以被任何一个状态所匹配。所以按钮的默认状态总是放到最后。
二、selector的执行流程
当我们定义好一个selector的xml文件,使用setBackgroundDrawable()方法传入该selector,系统内部会创建一个StateListDrawable类型的对象。StateListDrawable类由Drawable类派生而来。
那么,View是如何根据不同的状态来显示对应的背景图的呢。
当View的状态发生改变时,会执行它的refreshDrawableState()方法来刷新背景图的Drawable对象。比如,在View的setSelected()、setActivated()等方法中,都能看到当修改完状态后,会执行refreshDrawableState()方法。
public void refreshDrawableState() {
......
drawableStateChanged();
ViewParent parent = mParent;
if (parent != null) {
parent.childDrawableStateChanged(this);
}
}
refreshDrawableState()方法内部,主要是调用了drawableStateChanged()。从方法中可以看到,其内部会调用Drawable类的setState()方法来完成。
protected void drawableStateChanged() {
// 获取View当前状态的资源id数组
final int[] state = getDrawableState();
// 背景Drawable对象
final Drawable bg = mBackground;
if (bg != null && bg.isStateful()) {
// 根据View当前状态,更新对应状态下的Drawable对象
bg.setState(state);
}
......
}
接下来进入Drawable类的setState()方法。当状态值发生改变时,会回调onStateChange()方法。
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
Drawable类中的onStateChange()方法只有一行return false,StateListDrawable类对onStateChange()进行了重写。
我们进入StateListDrawable类的onStateChange()方法。
@Override
protected boolean onStateChange(int[] stateSet) {
final boolean changed = super.onStateChange(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);
}
return selectDrawable(idx) || changed;
}
在方法内部,调用indexOfStateSet()方法,根据传入的状态,从mStateListState对象中,找到第一个能够匹配该状态的索引(匹配算法的实现,在android.util.StateSet类中的stateSetMatches(int[] stateSpec, int[] stateSet)方法)。最后,调用selectDrawable()方法。
selectDrawable()方法在StateListDrawable类的直属父类DrawableContainer类中。在selectDrawable()方法中根据索引获取对应的Drawable对象,赋值给成员变量mCurrDrawable,最后调用invalidateSelf()方法。
public boolean selectDrawable(int idx) {
......
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(idx);
mCurrDrawable = d;
}
......
invalidateSelf();
return true;
}
invalidateSelf()方法在父类Drawable类中。在方法内部,获取Callback对象,调用Callback的invalidateDrawable()方法,参数传入this即当前的Drawable对象。
public void invalidateSelf() {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
接下来需要弄明白的是上面方法中的Callback对象是谁。在Drawable类中找到getCallback()方法,方法中返回的是成员变量mCallback。mCallback通过setCallback()方法赋值。
private WeakReference mCallback = null;
public Callback getCallback() {
if (mCallback != null) {
return mCallback.get();
}
return null;
}
public final void setCallback(Callback cb) {
mCallback = new WeakReference(cb);
}
其实View类实现了Callback接口。我们再次回到最初的方法setBackgroundDrawable()中。
public class View implements Drawable.Callback {
......
public void setBackgroundDrawable(Drawable background) {
......
if (background != null) {
......
background.setCallback(this);
mBackground = background;
......
}
}
......
}
关键代码:background.setCallback(this),也就是说View将自己赋值给了Drawable的成员变量mCallback。再结合上面invalidateSelf()方法中的关键代码:callback.invalidateDrawable(this),我们可以知道这里的callback就是View对象。好了,接着invalidateSelf()方法往下走,执行View类的invalidateDrawable()方法。
View类的invalidateDrawable()方法如下。
public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
rebuildOutline();
}
}
View在invalidateDrawable()方法中,调用了invalidate()方法重新绘制。至此,View的背景drawable就发生了改变。