深入解析 Android 的 StateListDrawable,【工作感悟】
" wf=" + hasWindowFocus()
": " + Arrays.toString(drawableState));}
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;}
从代码来看,viewStateIndex 的计算,drawableState 获取都离不开 StateSet 类。下面插播一下 StateSet 的内容。
StateSet
StateSet 里定义了 Android 的常见状态,为了节省内存,用二进制的位来表示:
/** @hide /public static final int VIEW_STATE_WINDOW_FOCUSED = 1;/* @hide /public static final int VIEW_STATE_SELECTED = 1 << 1;/* @hide /public static final int VIEW_STATE_FOCUSED = 1 << 2;/* @hide /public static final int VIEW_STATE_ENABLED = 1 << 3;/* @hide /public static final int VIEW_STATE_PRESSED = 1 << 4;/* @hide /public static final int VIEW_STATE_ACTIVATED = 1 << 5;/* @hide */public static f
inal int VIEW_STATE_ACCELERATED = 1 << 6;/** @hide /public static final int VIEW_STATE_HOVERED = 1 << 7;/* @hide /public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;/* @hide */public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
预先定义了一个 key-value 形式的数组 VIEW_STATE_IDS,key 表示属性 id,value 表示状态值:
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};
VIEW_STATE_SETS 是一个静态变量,类型为int[][]
。初始化过程:
static {if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) {throw new IllegalStateException("VIEW_STATE_IDs array length does not match ViewDrawableStates style array");}
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;}}
上面的代码主要做了 3 件事:
系统内部通过声明名为 ViewDrawableStates 的属性组可以对 VIEW_STATE_IDS 重排序。
为了简化理解,假设排序后的 orderedIds[]和 VIEW_STATE_IDS 是一样的。
VIEW_STATE_IDS 中定义了 10 中状态,一共有 2 的 10 次方 1024 中组合,所以 VIEW_STATE_SETS 定义为 1024 行。
VIEW_STATE_SETS 数组的行索引值表示状态组合值,列表时状态属性 id。
例如:VIEW_STATE_IDS[0x0011][0]=R.attr.state_window_focused
,VIEW_STATE_IDS[0x0011][1]=state_selected
枚举所有可能性
看下 View 在获取 state 使用的 get()方法:
public static int[] get(int mask) {if (mask >= VIEW_STATE_SETS.length) {throw new IllegalArgumentException("Invalid state set mask");}return VIEW_STATE_SETS[mask];}
只是返回了 VIEW_STATE_IDS 对应的行而已。
View 的状态计算过程就介绍完成了。
StateListDrawable 初始化
定义 <selector/>
的 xml 文件被解析后,创建 StateListDrawable 对象,调用 inflate()方法,infalte()方法里调用 inflateChildElements()方法解析 <item/>
:
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)) {if (type != XmlPullParser.START_TAG) {continue;}
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 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()
": <item> tag requires a 'drawable' attribute or "
"child tag defining a drawable");}dr = Drawable.createFromXmlInner(r, parser, attrs, theme);}
state.addStateSet(states, dr);}}
以解析单个<item/>
为例,解析过程如下:
extractStateSet()方法解析
<item/>
定义的 android:state_xxx 属性,返回 states:
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:states[j++] = attrs.getAttributeBooleanValue(i, false)? stateResId : -stateResId;}}states = StateSet.trimStateSet(states, j);return states;}
android:state_xxx 为 true,用属性 id 表示;反之,用属性 id 的负值表示。
解析
<item/>
android:drawable 属性,创建 Drawable 对象。通过 state.addStateSet(states, dr)将 states 和 drawable 联系起来。
state 为 StateListState 对象,addStateSet()实现如下:
int addStateSet(int[] stateSet, Drawable drawable) {final int pos = addChild(drawable);mStateSets[pos] = stateSet;return pos;}
mStateSets 是int[][]
数组,上面方法把 Drawable 对象和 stateSet 建立了一一对应的关系。
看下 addChild()的实现,主要工作把 Drawable 对象存入数组:
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;}
至此,StateListDrawable 对象初始化完成。
match 过程
View 的 states 计算好了,StateListDrawable 也初始化完成了,接下来就是 match,找到对应 Drawable 的过程了。
View 计算完当前 states 会将 StatelistDrawable 对象也设置为当前 state:
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();}}
由于 StateListDrawable 没有重写 setState()方法,所有看下 Drawable 的 setState()实现:
public boolean setState(@NonNull final int[] stateSet) {if (!Arrays.equals(mStateSet, stateSet)) {mStateSet = stateSet;return onStateChange(stateSet);}return false;}
StateListDrawable 重写了 onStateChange()方法,又回到 StateListDrawable 了:
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);}
评论