1 前言
在文章开头,有一点我要说明,这篇文章很长,请耐心看完,有什么不对的地方,欢迎指正哦。以下分析是基于android 9.0
,全文概述了系统从selector
的xml
的解析,到当前系统接收到来自底层的按压事件的上报,再到view
响应事件,并根据具体的状态完成背景图切换的一系列流程。
2 selector 的使用
android
的selector
对于android
开发者而言再熟悉不过了,只需在drawable
目录下定义一个selector
的xml
文件,在布局文件中引用这个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
并赋值给变量background
。a
我们知道是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
结尾,并且构造了一个XmlResourceParser
rp,并把它作为参数传给了Drawable
的createFromXmlForDensity()
静态方法。最后是通过这个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;
}
我们发现这个方法只调用了DrawableInflater
的inflateFromXmlForDensity()
方法。接着走。
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()
方法。发现它过滤掉了drawable
和id
,只读取状态值,当状态设置为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()
方法。发现它调用了StateListState
的addStateSet()
方法。而上面追踪代码到最后,它也是调用addStateSet()
方法,和java
代码如出一辙。StateListState
是StateListDrawable
的内部类。
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());
}
}
}
为了更好的理解下面的讲解,首先我们先了解一下类关系图。
进到类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);
}
进到view
的refreshDrawableState()
方法中,又调用了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()
方法。把刚才得到的状态值,调用Drawable
的setState()
方法存进去。
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
,更新mCurrDrawable
和mCurIndex
,并且最后调用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
中定义的方法。里面调用的callback
的invalidateDrawable()
。
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
。所以最终调用的是view
的invalidateDrawable()
方法进行背景刷新。
在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);
}
最后,我们终于在view
的drawBackground()
方法里,我们看到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);
}
}
我们知道,最后调用了drawable
的draw()
进行背景图的绘制,这个方法,由子类DrawableContainer
重写了。哇,它调用了mCurrDrawable
的绘制方法,还记得,它什么时候被赋值的嘛,我们上面讲过。是在状态改变时,在selectDrawable()
方法里进行的赋值,它把表示当前状态的背景图赋值给了mCurrDrawable
变量。当通知view
需要更新背景,这时view
又反过来调用了drawable
的draw()
方法。
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);
}
}
}
至此,是真的分析结束了。喜大普奔,喜大普奔。最后,可以用下面一张图来说明整个过程。