view的state源码分析
view的setPressed,setSelected等方法中都会更新state,进而更新显示的drawalbe的状态。
调用会改变state的方法后,都会调用到view.refreshDrawableState()
public void refreshDrawableState() { mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; drawableStateChanged(); ViewParent parent = mParent; if (parent != null) { parent.childDrawableStateChanged(this); } }
protected void drawableStateChanged() { finaint[] state = getDrawableState(); finaDrawable bg = mBackground; if (bg != nul&& bg.isStateful()) { bg.setState(state); } finaDrawable fg = mForegroundInfo != nul? mForegroundInfo.mDrawable : null; if (fg != nul&& fg.isStateful()) { fg.setState(state); } if (mScrollCache != null) { finaDrawable scrollBar = mScrollCache.scrollBar; if (scrollBar != nul&& scrollBar.isStateful()) { scrollBar.setState(state); } } if (mStateListAnimator != null) { mStateListAnimator.setState(state); } }
public finaint[] getDrawableState() { if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) { return mDrawableState; } else { mDrawableState = onCreateDrawableState(0); mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY; return mDrawableState; } }
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; // 根据当前状态来合成一个viewStateIndex 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 != nul&& mAttachInfo.mHardwareAccelerationRequested && HardwareRenderer.isAvailable()) { viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED; } if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED; finaint 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; } // 用合成的viewStateIndex获取一个有顺序的drawableState 数组。 drawableState = StateSet.get(viewStateIndex); if (extraSpace == 0) { return drawableState; } finaint[] 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; }
view的通用state
可以看到普通view支持的state只有10个,都是根据当前view的各种状态来组合一个viewStateIndex,然后就会去android.util.StateSet中去查找对应的state数组。
viewStateIndex按位算的,有10种状态占10位,某一位是1表示有此状态,0表示无此状态:
public static finaint VIEW_STATE_WINDOW_FOCUSED = 1; public static finaint VIEW_STATE_SELECTED = 1 << 1; public static finaint VIEW_STATE_FOCUSED = 1 << 2; public static finaint VIEW_STATE_ENABLED = 1 << 3; public static finaint VIEW_STATE_PRESSED = 1 << 4; public static finaint VIEW_STATE_ACTIVATED = 1 << 5; public static finaint VIEW_STATE_ACCELERATED = 1 << 6; public static finaint VIEW_STATE_HOVERED = 1 << 7; public static finaint VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8; public static finaint VIEW_STATE_DRAG_HOVERED = 1 << 9;
state的系统顺序
由于存在多种状态可以组合使用,即使相同的一组state的组合顺序也会不一样,那么就存在太多情况了,怎么办呢,Android官方的做法是用一个固定的state组合顺序来表示相同的一组state的所有不同顺序的组合。
这个顺序是在系统的attr.xml中的R.styleable.ViewDrawableStates定义的。
<declare-styleable name="ViewDrawableStates"> <attr name="state_pressed" /> <attr name="state_focused" /> <attr name="state_selected" /> <attr name="state_window_focused" /> <attr name="state_enabled" /> <attr name="state_activated" /> <attr name="state_accelerated" /> <attr name="state_hovered" /> <attr name="state_drag_can_accept" /> <attr name="state_drag_hovered" /> </declare-styleable>
比如当前view状态是enabled,selected,不管状态按什么顺序组合,那么对应的状态集都是{android.R.attr.state_selected, android.R.attr.state_enabled}。
源码
上边根据当前view的各种状态,按位与StateSet.VIEW_STATE_xxx合成了一个int的viewStateIndex,接着就通过StateSet.get(viewStateIndex)来获取state的数组,
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_SETS数组获取的,看下它是怎么创建的
static finaint[] 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 { // 首先根据在R.styleable.ViewDrawableStates中定义的各个state的顺序来生成一个有顺序的VIEW_STATE_IDS—orderedIds finaint[] orderedIds = new int[VIEW_STATE_IDS.length]; for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) { finaint 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]; } } } // 然后用VIEW_STATE_SETS数组根据orderedIds来存储不同state的组合的固定顺序。 finaint 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++) { finaint numBits = Integer.bitCount(i); finaint[] 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
发现经常使用的几个state并不在包含在上边,比如checked,
这是由于view的10中状态是基础状态,会到StateSet中去查找,这是所有view共有的状态,
而view的子类有些是有特殊的状态,如Checkbox的checked状态,一般是Override上边一些方法,并把自己的state加入进去。
如Checkbox的onCreateDrawableState:
@Override protected int[] onCreateDrawableState(int extraSpace) { finaint[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (isChecked()) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; }
drawalbe的state
WILD_CARD
public static finaint[] WILD_CARD = new int[0];
这个就是什么都没有state都没有定义的那个item。
这个特殊的state会匹配所有的状态,所以需要把此item放在最后一个,否则每次state改变获取的item都是这个什么state都没有定义的。
drawalbe.setState源码分析
接着上边的获取state后会把state设置进drawable中,以StateListDrawable为例:
public boolean setState(finaint[] stateSet) { if (!Arrays.equals(mStateSet, stateSet)) { mStateSet = stateSet; return onStateChange(stateSet); } return false; }
@Override protected boolean onStateChange(int[] stateSet) { finaboolean changed = super.onStateChange(stateSet); int idx = mStateListState.indexOfStateSet(stateSet); if (idx < 0) { idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); } return selectDrawable(idx) || changed; }
如果第一次没有找到的话就会,重新用StateSet.WILD_CARD进行查找。
StateListState.indexOfStateSet
int indexOfStateSet(int[] stateSet) { finaint[][] stateSets = mStateSets; finaint N = getChildCount(); for (int i = 0; i < N; i++) { if (StateSet.stateSetMatches(stateSets[i], stateSet)) { return i; } } return -1; }
这里表有两个变量:
- stateSets(二维数组),是通过addState加入的各种 state数组 而组成的二维数组,也就是说stateSets的第二维存储的是state数组,stateSets中的顺序是加入的顺序;
- stateSet是根据view的状态组合成的一个有固定顺序的state数组,传递进来是要到stateSets中查询。
这个方法就是把stateSets中的所有state数组按加入的顺序 和 stateSet进行比较。
StateSet.stateSetMatches
public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) { if (stateSet == null) { return (stateSpec == null || isWildCard(stateSpec)); } int stateSpecSize = stateSpec.length; int stateSetSize = stateSet.length; for (int i = 0; i < stateSpecSize; i++) { int stateSpecState = stateSpec[i]; if (stateSpecState == 0) { // We've reached the end of the cases to match against. return true; } // 我们有时会定义 负的state来表示不能有此state,下边的代码就是处理这个逻辑的 final boolean mustMatch; if (stateSpecState > 0) { // 正数表示必须要有 mustMatch = true; } else { // 负数表示必须不能有 // We use negative values to indicate must-NOT-match states. mustMatch = false; stateSpecState = -stateSpecState; } boolean found = false; for (int j = 0; j < stateSetSize; j++) { final int state = stateSet[j]; if (state == 0) { // We've reached the end of states to match. if (mustMatch) { // We didn't find this must-match state. return false; } else { // Continue checking other must-not-match states. break; } } if (state == stateSpecState) { if (mustMatch) { found = true; // Continue checking other other must-match states. break; } else { // Any match of a must-not-match state returns false. return false; } } } //for stateSetSize end if (mustMatch && !found) { // We've reached the end of states to match and we didn't // find a must-match state. return false; } } //for stateSpecSize end return true; }
上边的代码的两个数组:
- stateSet是根据当前view的各种状态计算出来的一个有顺序的state数组,这个数组的中的顺序是系统定义的。
- stateSpec是我们addState时加入的state数组,这个数组的中的顺序是我们定义的。
这个方法里就是拿stateSpec中的每一个和stateSet中的每一个进行比较,
- 如果stateSpec是WILD_CARD,也就是int[0],那么直接会到最后return true。
- 如果stateSpec中有正数的state,那么就表示stateSet中必须要出现这个,
- 如果stateSpec中有负数state,那么就表示stateSet中不能出现这个,
所以想要找到正确状态的drawable,就要在StateListDrawalbe.addState()的加入顺序上做调整。
例如:
StateListDrawalbe的加入的drawable顺序为:
- {selected}
- {enable}
- {selected , actived}
此时我们想要显示的是StateListDrawalbe最后一个对应的drawalbe,于是把把view设置为
view.setSelected(true); view.setActivated(true);
根据上边view的state可知,最后view的当前状态数组是{enable , selected , actived },
但是如果根据上边stateSetMatches查找代码最终匹配的却是第一个{selected},
所以就需要对StateListDrawalbe.addState()的加入顺序上做调整,让{selected , actived}放到第一个即可。
selectDrawable
接着上边的,在找到对应的drawable序号后:
public boolean selectDrawable(int idx) { if (idx == mCurIndex) { return false; } final long now = SystemClock.uptimeMillis(); if (mDrawableContainerState.mExitFadeDuration > 0) { if (mLastDrawable != null) { mLastDrawable.setVisible(false, false); } if (mCurrDrawable != null) { mLastDrawable = mCurrDrawable; mLastIndex = mCurIndex; mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; } else { mLastDrawable = null; mLastIndex = -1; mExitAnimationEnd = 0; } } else if (mCurrDrawable != null) { mCurrDrawable.setVisible(false, false); } if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { final Drawable d = mDrawableContainerState.getChild(idx); mCurrDrawable = d; mCurIndex = idx; if (d != null) { if (mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; } initializeDrawableForDisplay(d); } } else { mCurrDrawable = null; mCurIndex = -1; } if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) { if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { @Override public void run() { animate(true); invalidateSelf(); } }; } else { unscheduleSelf(mAnimationRunnable); } // Compute first frame and schedule next animation. animate(true); } invalidateSelf(); return true; }