深入解析 Android 的 StateListDrawable,项目实战
}
可以看到当 View 初始化或状态发生改变都会调用 onCreateDrawableState()方法计算新的 state:
protected int[] onCreateDrawableState(int extraSpace) {if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&mParent instanceof View) {return ((View) mParent).onCreateDrawableState(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;if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&ThreadedRenderer.isAvailable()) {// This is set if HW acceleration is requested, even if the current// process doesn't allow it. This is just to allow app preview// windows to better match their app.viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;}if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;
final int privateFlags2 = mPrivateFlags2;if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;}if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;}
drawableState = StateSet.get(viewStateIndex);
//noinspection ConstantIfStatementif (false) {Log.i("View", "drawableStateIndex=" + viewStateIndex);Log.i("View", toString()
" pressed=" + ((privateFlags & PFLAG_PRESSED) != 0)
" en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
" fo=" + hasFocus()
" sl=" + ((privateFlags & PFLAG_SELECTED) != 0)
" 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 final 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 属性,创建 D
rawable 对象。
通过 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 对象初始化完成。
评论