selector的使用及执行流程

selector是Android中的背景选择器。一个selector使用几个不同的drawable来表示相同的图形,根据对象的状态来决定使用哪一个drawable。比如,一个按钮可以有不同的状态,默认状态、被按下的状态。

官方文档:
https://developer.android.com/guide/topics/resources/drawable-resource.html

一、selector的使用
selector的使用语法如下。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:constantSize=["true" | "false"]
    android:dither=["true" | "false"]
    android:variablePadding=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:state_pressed=["true" | "false"]
        android:state_focused=["true" | "false"]
        android:state_hovered=["true" | "false"]
        android:state_selected=["true" | "false"]
        android:state_checkable=["true" | "false"]
        android:state_checked=["true" | "false"]
        android:state_enabled=["true" | "false"]
        android:state_activated=["true" | "false"]
        android:state_window_focused=["true" | "false"] />
</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。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@drawable/button_pressed" /> <!-- pressed -->
    <item android:drawable="@drawable/button_normal" /> <!-- default -->
</selector>

将上面定义的selector添加到Button中。
<Button
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:background="@drawable/button_bg" />

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<Callback> mCallback = null;

public Callback getCallback() {
    if (mCallback != null) {
        return mCallback.get();
    }
    return null;
}

public final void setCallback(Callback cb) {
    mCallback = new WeakReference<Callback>(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就发生了改变。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值