写点什么

深入解析 Android 的 StateListDrawable,【工作感悟】

用户头像
Android架构
关注
发布于: 2021 年 11 月 06 日
  • " 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


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


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 件事:


  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 属性,创建 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);}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
深入解析Android的StateListDrawable,【工作感悟】