写点什么

深入解析 Android 的 StateListDrawable,项目实战

用户头像
Android架构
关注
发布于: 52 分钟前

}


可以看到当 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 件事:


  1. 系统内部通过声明名为 ViewDrawableStates 的属性组可以对 VIEW_STATE_IDS 重排序。


为了简化理解,假设排序后的 orderedIds[]和 VIEW_STATE_IDS 是一样的。


  1. VIEW_STATE_IDS 中定义了 10 中状态,一共有 2 的 10 次方 1024 中组合,所以 VIEW_STATE_SETS 定义为 1024 行。


VIEW_STATE_SETS 数组的行索引值表示状态组合值,列表时状态属性 id。


例如:VIEW_STATE_IDS[0x0011][0]=R.attr.state_window_focusedVIEW_STATE_IDS[0x0011][1]=state_selected


  1. 枚举所有可能性


看下 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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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 对象初始化完成。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
深入解析Android的StateListDrawable,项目实战