selector之StateListDrawable

1 前言

在文章开头,有一点我要说明,这篇文章很长,请耐心看完,有什么不对的地方,欢迎指正哦。以下分析是基于android 9.0,全文概述了系统从selectorxml的解析,到当前系统接收到来自底层的按压事件的上报,再到view响应事件,并根据具体的状态完成背景图切换的一系列流程。

2 selector 的使用

androidselector 对于android开发者而言再熟悉不过了,只需在drawable目录下定义一个selectorxml文件,在布局文件中引用这个xml文件或者在代码中setBackgroundDrawable的时候使用此xml就可以实现控件按下或有焦点等不同状态的效果。

在此之前,我们先了解一些基础知识。首先要了解一个Drawable类,Drawable是一个抽象的可绘制的图片类,这个类可以从一个本地路径中创建一个图片,也可以使用从定义好的xml中创建,从路径中创建一个Bitmap对象并将它转换成BitmapDrawable;当图为.9.png时,则转换成NinePatchDrawable;当从xml中定义的selector标签转换为StateListDrawable。可以总结为以下几点。

  • bitmap --> BitmapDrawable
  • color --> ColorDrawable
  • shape --> GradientDrawable
  • selector --> StateListDrawable

更多细节,具体可以查看官网可绘制对象资源的介绍。

3 从 xml 文件解析 selector

selector是给view作为背景用的,如果要找源头,自然先从view找起。我们先来看一下view的构造方法,里面解析了view的属性background并赋值给变量backgrounda我们知道是TypedArray,进到它的getDrawable()看看具体干了啥。

package android.view;

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {

    final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

        final int N = a.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.View_background:
                    //将 background 属性赋值给 drawable
                    background = a.getDrawable(attr);
                    break;
            ...

            }
        ...
        }
}

进来之后,发现它调用了getDrawableForDensity(),在此方法里,它调用Resources类的loadDrawable()

package android.content.res;

public class TypedArray {

    @Nullable
    public Drawable getDrawable(@StyleableRes int index) {
        return getDrawableForDensity(index, 0);
    }

    @Nullable
    public Drawable getDrawableForDensity(@StyleableRes int index, int density) {

        final TypedValue value = mValue;
        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
            if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                throw new UnsupportedOperationException(
                        "Failed to resolve attribute at index " + index + ": " + value);
            }

            if (density > 0) {
                mResources.getValueForDensity(value.resourceId, density, value, true);
            }
            return mResources.loadDrawable(value, value.resourceId, density, mTheme);
        }
        return null;
    }

}

进到Resources类,我们继续跟踪,结果只调用了ResourcesImpl类的loadDrawable()方法。

package android.content.res;

public class Resources {

   @NonNull
    Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
            throws NotFoundException {
            //this,把当前 Resources 作为参数传进去了
        return mResourcesImpl.loadDrawable(this, value, id, density, theme);
    }

}

不急,我们继续走。进到loadDrawable()方法。哇,写了一堆代码,我们提取出我们最想要的信息。它调用了loadDrawableForCookie()方法来加载图片。

package android.content.res;

public class ResourcesImpl {

    Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
            int density, @Nullable Resources.Theme theme)
            throws NotFoundException {

        final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;

        if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
            if (value.density == density) {
                value.density = mMetrics.densityDpi;
            } else {
                value.density = (value.density * mMetrics.densityDpi) / density;
            }
        }

        try {
            if (TRACE_FOR_PRELOAD) {
                // Log only framework resources
                if ((id >>> 24) == 0x1) {
                    final String name = getResourceName(id);
                    if (name != null) {
                        Log.d("PreloadDrawable", name);
                    }
                }
            }

            final boolean isColorDrawable;
            final DrawableCache caches;
            final long key;
            if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
                    && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
                isColorDrawable = true;
                caches = mColorDrawableCache;
                key = value.data;
            } else {
                isColorDrawable = false;
                caches = mDrawableCache;
                key = (((long) value.assetCookie) << 32) | value.data;
            }

            if (!mPreloading && useCache) {
                final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
                if (cachedDrawable != null) {
                    cachedDrawable.setChangingConfigurations(value.changingConfigurations);
                    return cachedDrawable;
                }
                /// M: Boost cache on system @{
                synchronized (mAccessLock) {
                    final Drawable boostDrawable = mMtkBoostDrawableCache
                            .getBoostCachedDrawable(wrapper, key);
                    if (boostDrawable != null) {
                        Slog.w(TAG, "Using Boost");
                        return boostDrawable;
                    }
                }
                /// @}
            }

            final Drawable.ConstantState cs;
            if (isColorDrawable) {
                cs = sPreloadedColorDrawables.get(key);
            } else {
                cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
            }

            Drawable dr;
            boolean needsNewDrawableAfterCache = false;
            if (cs != null) {
                if (TRACE_FOR_DETAILED_PRELOAD) {
                    // Log only framework resources
                    if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
                        final String name = getResourceName(id);
                        if (name != null) {
                            Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
                                    + Integer.toHexString(id) + " " + name);
                        }
                    }
                }
                dr = cs.newDrawable(wrapper);
            } else if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            } else {
                //有用信息在这里,wrapper 就是 Resources
                dr = loadDrawableForCookie(wrapper, value, id, density);
            }
            // DrawableContainer' constant state has drawables instances. In order to leave the
            // constant state intact in the cache, we need to create a new DrawableContainer after
            // added to cache.
            if (dr instanceof DrawableContainer)  {
                needsNewDrawableAfterCache = true;
            }

            // Determine if the drawable has unresolved theme attributes. If it
            // does, we'll need to apply a theme and store it in a theme-specific
            // cache.
            final boolean canApplyTheme = dr != null && dr.canApplyTheme();
            if (canApplyTheme && theme != null) {
                dr = dr.mutate();
                dr.applyTheme(theme);
                dr.clearMutated();
            }

            // If we were able to obtain a drawable, store it in the appropriate
            // cache: preload, not themed, null theme, or theme-specific. Don't
            // pollute the cache with drawables loaded from a foreign density.
            if (dr != null) {
                dr.setChangingConfigurations(value.changingConfigurations);
                if (useCache) {
                    cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
                    if (needsNewDrawableAfterCache) {
                        Drawable.ConstantState state = dr.getConstantState();
                        if (state != null) {
                            dr = state.newDrawable(wrapper);
                        }
                    }
                }
            }

            return dr;
        } catch (Exception e) {
            String name;
            try {
                name = getResourceName(id);
            } catch (NotFoundException e2) {
                name = "(missing name)";
            }

            final NotFoundException nfe = new NotFoundException("Drawable " + name
                    + " with resource ID #0x" + Integer.toHexString(id), e);
            nfe.setStackTrace(new StackTraceElement[0]);
            throw nfe;
        }
    }

继续往下走,它会先判断文件是否以xml结尾,并且构造了一个XmlResourceParserrp,并把它作为参数传给了DrawablecreateFromXmlForDensity()静态方法。最后是通过这个rp创造了Drawable

    private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,int id, int density) {

        if (file.endsWith(".xml")) {
                    final XmlResourceParser rp = loadXmlResourceParser(
                            file, id, value.assetCookie, "drawable");
                    dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
                    rp.close();
                } else {
                    final InputStream is = mAssets.openNonAsset(
                            value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                    AssetInputStream ais = (AssetInputStream) is;
                    dr = decodeImageDrawable(ais, wrapper, value);
        }

    }

继续进到createFromXmlInnerForDensity()方法。

public abstract class Drawable {

    public static Drawable createFromXmlForDensity(@NonNull Resources r,
            @NonNull XmlPullParser parser, int density, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
        AttributeSet attrs = Xml.asAttributeSet(parser);

        int type;
        //noinspection StatementWithEmptyBody
        //为了只过滤出 START_TAG 标签,也就是 selector
        while ((type=parser.next()) != XmlPullParser.START_TAG
                && type != XmlPullParser.END_DOCUMENT) {
            // Empty loop.
        }

        if (type != XmlPullParser.START_TAG) {
            throw new XmlPullParserException("No start tag found");
        }

        Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme);

        if (drawable == null) {
            throw new RuntimeException("Unknown initial tag: " + parser.getName());
        }

        return drawable;
    }

我们发现这个方法只调用了DrawableInflaterinflateFromXmlForDensity()方法。接着走。

    static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
            @Nullable Theme theme) throws XmlPullParserException, IOException {
        return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
                density, theme);
    }


}

我们看到inflateFromTag()方法,并把解析器解析出来的tag名字作为参数传进去了。

package android.graphics.drawable;

public final class DrawableInflater {

    Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs, int density, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
        if (name.equals("drawable")) {
            name = attrs.getAttributeValue(null, "class");
            if (name == null) {
                throw new InflateException(" tag must specify class attribute");
            }
        }

        //name = parse.getName()
        Drawable drawable = inflateFromTag(name);
        if (drawable == null) {
            drawable = inflateFromClass(name);
        }
        drawable.setSrcDensityOverride(density);
        //会调用 inflate() 方法
        drawable.inflate(mRes, parser, attrs, theme);
        return drawable;
    }

跟踪了这么久,终于在这里看到了,当识别到selector标签时,会实例化一个StateListDrawable对象返回。

    private Drawable inflateFromTag(@NonNull String name) {
        switch (name) {
            case "selector":
                return new StateListDrawable();
            case "animated-selector":
                return new AnimatedStateListDrawable();
            case "level-list":
                return new LevelListDrawable();
            case "layer-list":
                return new LayerDrawable();
            case "transition":
                return new TransitionDrawable();
            case "ripple":
                return new RippleDrawable();
            case "adaptive-icon":
                return new AdaptiveIconDrawable();
            case "color":
                return new ColorDrawable();
            case "shape":
                return new GradientDrawable();
            case "vector":
                return new VectorDrawable();
            case "animated-vector":
                return new AnimatedVectorDrawable();
            case "scale":
                return new ScaleDrawable();
            case "clip":
                return new ClipDrawable();
            case "rotate":
                return new RotateDrawable();
            case "animated-rotate":
                return new AnimatedRotateDrawable();
            case "animation-list":
                return new AnimationDrawable();
            case "inset":
                return new InsetDrawable();
            case "bitmap":
                return new BitmapDrawable();
            case "nine-patch":
                return new NinePatchDrawable();
            case "animated-image":
                return new AnimatedImageDrawable();
            default:
                return null;
        }
    }

}

至此,我们终于理解了为什么文章开头讲selector最后实例化一个StateListDrawable。至于里面的状态以及对应的图是如何存取的,接着看接下来的分析。

4 selector 状态图的存储

由于上面调用了inflateFromTag()方法返回drawable之后,会立即调用它的inflate()方法,也就是StateListDrawable,我们继续跟踪。

public class StateListDrawable extends DrawableContainer {

    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
            throws XmlPullParserException, IOException {
        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
        super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
        updateStateFromTypedArray(a);
        updateDensity(r);
        a.recycle();

        inflateChildElements(r, parser, attrs, theme);

        onStateChange(getState());
    }

进到updateStateFromTypedArray()方法,它会解析StateListDrawable的各种属性。

    private void updateStateFromTypedArray(TypedArray a) {
        final StateListState state = mStateListState;

        // Account for any configuration changes.
        state.mChangingConfigurations |= a.getChangingConfigurations();

        // Extract the theme attributes, if any.
        state.mThemeAttrs = a.extractThemeAttrs();

        state.mVariablePadding = a.getBoolean(
                R.styleable.StateListDrawable_variablePadding, state.mVariablePadding);
        state.mConstantSize = a.getBoolean(
                R.styleable.StateListDrawable_constantSize, state.mConstantSize);
        state.mEnterFadeDuration = a.getInt(
                R.styleable.StateListDrawable_enterFadeDuration, state.mEnterFadeDuration);
        state.mExitFadeDuration = a.getInt(
                R.styleable.StateListDrawable_exitFadeDuration, state.mExitFadeDuration);
        state.mDither = a.getBoolean(
                R.styleable.StateListDrawable_dither, state.mDither);
        state.mAutoMirrored = a.getBoolean(
                R.styleable.StateListDrawable_autoMirrored, state.mAutoMirrored);
    }

继续看inflate()方法里调用的inflateChildElements()方法。当检测到不是START_TAG,就跳过;当不是item标签也跳过。然后把drawable标签对应的图片取出来,赋值给变量dr,然后调用extractStateSet()得到对应的状态states,最后将状态和图片存起来。

    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
            Theme theme) throws XmlPullParserException, IOException {
        final StateListState state = mStateListState;
        final int innerDepth = parser.getDepth() + 1;
        int type;
        int depth;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth
                || type != XmlPullParser.END_TAG)) {
            //不是 START_TAG,就跳过
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            //不是 item 标签也跳过
            if (depth > innerDepth || !parser.getName().equals("item")) {
                continue;
            }

            // This allows state list drawable item elements to be themed at
            // inflation time but does NOT make them work for Zygote preload.
            final TypedArray a = obtainAttributes(r, theme, attrs,
                    R.styleable.StateListDrawableItem);
            //把 drawable 标签的图片从 xml 里读出来
            Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
            a.recycle();

            //取出状态值
            final int[] states = extractStateSet(attrs);

            // Loading child elements modifies the state of the AttributeSet's
            // underlying parser, so it needs to happen after obtaining
            // attributes and extracting states.
            if (dr == null) {
                while ((type = parser.next()) == XmlPullParser.TEXT) {
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new XmlPullParserException(
                            parser.getPositionDescription()
                                    + ":  tag requires a 'drawable' attribute or "
                                    + "child tag defining a drawable");
                }
                dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
            }

            //把状态值和图片存起来
            state.addStateSet(states, dr);
        }
    }

}

我们进到attrs文件里查看,定义了名为StateListDrawableItem的样式,属性名为drawable。之所以我们在xml里用andorid:drawable的形式,就是因为这里限定了。

frameworks/base/core/res/res/values/attrs.xml



        
        

知道了如何取出drawable,我们继续研究,它是如何取出state状态值的。
继续进到extractStateSet()方法。发现它过滤掉了drawableid,只读取状态值,当状态设置为true时,则返回stateResId,若为false时,则为-stateResId

public class StateListDrawable extends DrawableContainer {

    int[] extractStateSet(AttributeSet attrs) {
        int j = 0;
        final int numAttrs = attrs.getAttributeCount();
        int[] states = new int[numAttrs];
        for (int i = 0; i < numAttrs; i++) {
            final int stateResId = attrs.getAttributeNameResource(i);
            switch (stateResId) {
                case 0:
                    break;
                case R.attr.drawable:
                case R.attr.id:
                    // Ignore attributes from StateListDrawableItem and
                    // AnimatedStateListDrawableItem.
                    continue;
                default:
                    //把状态的值取出来,当为true时,则为stateResId,当为false时,则为-stateResId
                    states[j++] = attrs.getAttributeBooleanValue(i, false)
                            ? stateResId : -stateResId;
            }
        }
        states = StateSet.trimStateSet(states, j);
        return states;
    }

}

什么意思呢?就是当我们在xml如下表示的时候



 
 

 

转换成java代码,就会变成如下所示,当为false时,状态值表示的时候,会有一个减号(减号):

StateListDrawable stalistDrawable = new StateListDrawable();

stalistDrawable.addState(new int []{-android.R.attr.state_pressed}, getResources().getDrawable(R.drawable.btn_style_one_pressed_false));
stalistDrawable.addState(new int []{android.R.attr.state_pressed}, getResources().getDrawable(R.drawable.btn_style_one_pressed_true));

//默认状态时,用一个空数组表示
stalistDrawable.addState(new int []{}, getResources().getDrawable(R.drawable.btn_style_default));

首先进到addState()方法。发现它调用了StateListStateaddStateSet()方法。而上面追踪代码到最后,它也是调用addStateSet()方法,和java代码如出一辙。StateListStateStateListDrawable的内部类。

public class StateListDrawable extends DrawableContainer {

    public void addState(int[] stateSet, Drawable drawable) {
        if (drawable != null) {
            mStateListState.addStateSet(stateSet, drawable);
            // in case the new state matches our current state...
            onStateChange(getState());
        }
    }

}

为了更好的理解下面的讲解,首先我们先了解一下类关系图。

class.png

进到类StateListState,在addStateSet()方法里,又调用了addChild()方法。此方法写在父类DrawableContainerState里。把drawable参数传进去,返回一个下标,并将stateSet状态保存在对应下标的mStateSets二维数组中。

static class StateListState extends DrawableContainerState {

    int addStateSet(int[] stateSet, Drawable drawable) {
            final int pos = addChild(drawable);
            mStateSets[pos] = stateSet;
            return pos;
    }

}

进到addChild()方法查看,它把dr存在mDrawables一维数组变量中。并将下标值返回。

protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,Resources res) {

    public final int addChild(Drawable dr) {
            final int pos = mNumChildren;
            if (pos >= mDrawables.length) {
                growArray(pos, pos+10);
            }

            dr.mutate();
            dr.setVisible(false, true);
            dr.setCallback(mOwner);

            mDrawables[pos] = dr;
            mNumChildren++;
            mChildrenChangingConfigurations |= dr.getChangingConfigurations();

            invalidateCache();

            mConstantPadding = null;
            mCheckedPadding = false;
            mCheckedConstantSize = false;
            mCheckedConstantState = false;

            return pos;
        }

}

至此,通过上面的分析得出:“状态”用一维数组stateSet表示,根据上面的例子也能够看出。且把这个表示状态的一维数组stateSet保存在StateListState类的变量mStateSets二维数组中;“状态图片”保存在父类DrawableContainerState的变量mDrawables一维数组中,此下标与mStateSets保存状态的下标一一对应。

在知道了状态和图片是如何存储之后,我们再来研究:当状态改变后,它是如何更新view视图的呢?也就是如何更新背景图的呢?

5 view 视图的更新

首先,TP会把我们的触摸事件上报,就拿press按压来举例子。当我们按下屏幕,底层接收到按压事件,然后上报给上层,上层再处理这个按压事件,经过一些列复杂的事件传递,最终会调用view类的setPressed()方法。这时候会根据传进来的按压布尔值,对mPrivateFlags进行位运算,并且判断当前状态是否改变,当改变了就需要调用refreshDrawableState()方法进行刷新状态。

public void setPressed(boolean pressed) {
        //判断按压状态是否更新
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

        if (pressed) {
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            mPrivateFlags &= ~PFLAG_PRESSED;
        }

        if (needsRefresh) {
            refreshDrawableState();
        }
        dispatchSetPressed(pressed);
}

进到viewrefreshDrawableState()方法中,又调用了drawableStateChanged()方法,我们继续跟踪。

public void refreshDrawableState() {
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        drawableStateChanged();

        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
}

它会调用getDrawableState()获取当前的状态数组,然后设置一下drawable的状态,当设置成功,就会返回true,否则就返回false,当成功的时候,会调用invalidate()进行重绘。

protected void drawableStateChanged() {
        final int[] state = getDrawableState();
        boolean changed = false;

        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            changed |= bg.setState(state);
        }

        ...

        if (changed) {
            invalidate();
        }

}

我们一步步解析这个drawableStateChanged()方法。首先我们看一下getDrawableState()是如何获取当前状态的。初次进来的时候,会调用onCreateDrawableState(0)来给状态数组赋值。

public final int[] getDrawableState() {
        if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
            return mDrawableState;
        } else {
            mDrawableState = onCreateDrawableState(0);
            mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
            return mDrawableState;
        }
}

在这个方法里,会根据当前的flag和各种状态进行与运算,当不等于0的时候,会对viewStateIndex进行或运算,将标志位一个个加进去。最终会调用StateSet.get()方法,将viewStateIndex作为参数传进去,最后得到对应的状态值。这里面的运算可就有意思了。这里不得不赞叹一下google的大神们。

protected int[] onCreateDrawableState(int extraSpace) {
     ...

        int[] drawableState;

        int privateFlags = mPrivateFlags;

        int viewStateIndex = 0;
        if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
        if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
        if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
        if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
        if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
        if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;

     ...

        //把 viewStateIndex 传进去得到对应的状态值
        drawableState = StateSet.get(viewStateIndex);

        if (extraSpace == 0) {
            return drawableState;
        }

        final int[] fullState;
        if (drawableState != null) {
            fullState = new int[drawableState.length + extraSpace];
            System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
        } else {
            fullState = new int[extraSpace];
        }

        return fullState;
}

StateSet.get()代码我贴出来了,有兴趣的可以自己研究下。总而言之,就是传进去的是实则是VIEW_STATE_IDS偶数下标项的位组合,返回的是VIEW_STATE_IDS奇数下标项的组合数组。

public class StateSet {

    private static final int[][] VIEW_STATE_SETS;

    public static final int VIEW_STATE_WINDOW_FOCUSED = 1;
    public static final int VIEW_STATE_SELECTED = 1 << 1;
    public static final int VIEW_STATE_FOCUSED = 1 << 2;
    public static final int VIEW_STATE_ENABLED = 1 << 3;
    public static final int VIEW_STATE_PRESSED = 1 << 4;
    public static final int VIEW_STATE_ACTIVATED = 1 << 5;
    public static final int VIEW_STATE_ACCELERATED = 1 << 6;
    public static final int VIEW_STATE_HOVERED = 1 << 7;
    public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
    public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;


    static final int[] VIEW_STATE_IDS = new int[] {
            R.attr.state_window_focused,    VIEW_STATE_WINDOW_FOCUSED,
            R.attr.state_selected,          VIEW_STATE_SELECTED,
            R.attr.state_focused,           VIEW_STATE_FOCUSED,
            R.attr.state_enabled,           VIEW_STATE_ENABLED,
            R.attr.state_pressed,           VIEW_STATE_PRESSED,
            R.attr.state_activated,         VIEW_STATE_ACTIVATED,
            R.attr.state_accelerated,       VIEW_STATE_ACCELERATED,
            R.attr.state_hovered,           VIEW_STATE_HOVERED,
            R.attr.state_drag_can_accept,   VIEW_STATE_DRAG_CAN_ACCEPT,
            R.attr.state_drag_hovered,      VIEW_STATE_DRAG_HOVERED
    };


    static {
        final int[] orderedIds = new int[VIEW_STATE_IDS.length];
        for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
            final int viewState = R.styleable.ViewDrawableStates[i];
            for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) {
                if (VIEW_STATE_IDS[j] == viewState) {
                    orderedIds[i * 2] = viewState;
                    orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
                }
            }
        }

        final int NUM_BITS = VIEW_STATE_IDS.length / 2;
        VIEW_STATE_SETS = new int[1 << NUM_BITS][];
        for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
            final int numBits = Integer.bitCount(i);
            final int[] set = new int[numBits];
            int pos = 0;
            for (int j = 0; j < orderedIds.length; j += 2) {
                if ((i & orderedIds[j + 1]) != 0) {
                    set[pos++] = orderedIds[j];
                }
            }
            VIEW_STATE_SETS[i] = set;
        }
    }

    //我们将状态值传进去,返回VIEW_STATE_IDS的奇数项的一项或多项
    public static int[] get(int mask) {
        if (mask >= VIEW_STATE_SETS.length) {
            throw new IllegalArgumentException("Invalid state set mask");
        }
        return VIEW_STATE_SETS[mask];
    }

}

扯远了,好,我们再回到上面的drawableStateChanged()方法继续看,刚刚我们从getDrawableState()一路追过来,现在我们接着说下面setState()方法。把刚才得到的状态值,调用DrawablesetState()方法存进去。

protected void drawableStateChanged() {
        final int[] state = getDrawableState();
        boolean changed = false;

        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            changed |= bg.setState(state);
        }

        ...

        if (changed) {
            invalidate();
        }

}

把当前状态赋值给成员变量mStateSet,并且调用onStateChange()方法通知状态改变。并把状态值赋值给成员变量mStateSet

public abstract class Drawable {

    public boolean setState(@NonNull final int[] stateSet) {
        if (!Arrays.equals(mStateSet, stateSet)) {

            mStateSet = stateSet;
            //通知状态改变
            return onStateChange(stateSet);
        }
        return false;
    }

}

这个onStateChange()方法是在父类Drawable里定义的,子类StateListDrawable重写了该方法,所以我们直接看子类的方法即可。

首先在该方法里,调用indexOfStateSet()方法,并把状态作为参数传进去了,并返回该状态对应的下标值。

public class StateListDrawable extends DrawableContainer {

    @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()是通过mStateSets二维数组里的每一项逐一和参数进行对比,将匹配的下标返回。

    int indexOfStateSet(int[] stateSet) {
            final int[][] stateSets = mStateSets;
            final int N = getChildCount();
            for (int i = 0; i < N; i++) {
                if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
                    return i;
                }
            }
            return -1;
    }

最后通过返回的id下标,调用selectDrawable(),并把下标值作为参数传进去了,下面我们再看selectDrawable()方法,根据下标,选出对应的drawable,更新mCurrDrawablemCurIndex,并且最后调用invalidateSelf()进行自我刷新。

public class DrawableContainer extends Drawable implements Drawable.Callback {

    public boolean selectDrawable(int index) {
    ...

    if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
            final Drawable d = mDrawableContainerState.getChild(index);
            mCurrDrawable = d;
            mCurIndex = index;
            if (d != null) {
                if (mDrawableContainerState.mEnterFadeDuration > 0) {
                    mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
                }
                initializeDrawableForDisplay(d);
            }
        } else {
            mCurrDrawable = null;
            mCurIndex = -1;
        }
    ...

    invalidateSelf();

    }

}

invalidateSelf()刷新背景图,是调用的父类Drawable中定义的方法。里面调用的callbackinvalidateDrawable()

public abstract class Drawable {

    public void invalidateSelf() {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }

}

那么这个callback是什么呢?请看View中的setBackgroundDrawable()方法里,调用了background.setCallback(this),这里view把自己传给了Callback。所以最终调用的是viewinvalidateDrawable()方法进行背景刷新。

setBackgroundDrawable()方法里,会调用isStateful()方法进行判断,多状态是否可用,父类Drawable类里,此方法返回false,子类StateListDrawable里重写了此方法,返回true

public class View implements Drawable.Callback, KeyEvent.Callback,
    AccessibilityEventSource {

    @Override
    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();
        }
    }

}

终于快看到胜利的曙光了,invalidateDrawable()方法里调用了invalidate()方法,重绘,会调用draw()方法。这个方法里面会有一堆的代码,我们捡重点看,主要是drawBackground()方法。


public void draw(Canvas canvas) {

    drawBackground(canvas);

}

最后,我们终于在viewdrawBackground()方法里,我们看到background.draw(canvas),把view的背景重新绘制了。如果你以为到这里就结束了,那你肯定忘了一件事,那就是:具体绘制哪一张背景图,我们到现在还没有说到。接着看。

private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        setBackgroundBounds();

        ...

        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            //重新绘制背景
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
}

我们知道,最后调用了drawabledraw()进行背景图的绘制,这个方法,由子类DrawableContainer重写了。哇,它调用了mCurrDrawable的绘制方法,还记得,它什么时候被赋值的嘛,我们上面讲过。是在状态改变时,在selectDrawable()方法里进行的赋值,它把表示当前状态的背景图赋值给了mCurrDrawable变量。当通知view需要更新背景,这时view又反过来调用了drawabledraw()方法。

public class DrawableContainer extends Drawable implements Drawable.Callback {

    @Override
    public void draw(Canvas canvas) {
        if (mCurrDrawable != null) {
            mCurrDrawable.draw(canvas);
        }
        if (mLastDrawable != null) {
            mLastDrawable.draw(canvas);
        }
    }

}

至此,是真的分析结束了。喜大普奔,喜大普奔。最后,可以用下面一张图来说明整个过程。

summary.png

你可能感兴趣的:(selector之StateListDrawable)