View的DrawableState(即StateListDrawable)变化的源码分析

来源:互联网 发布:淘宝店铺装修 编辑:程序博客网 时间:2024/06/07 13:03

       XML文件中给Button控件设置android:background属性或者在代码里直接调用View.setBackgroundDrawable函数设置背景,这些恐怕每个android开发人员都干过。的确,为了让我们的应用表现的更加人性化,这些控件的状态变化是必不可少的。可是,对于为什么这样用就起作用,不知道大家分析过没有,最近我分析了一下,现在和大家共享一下。

一,从XML中解析出StateListDrawable的过程。见下图:

图1 从XML中解析出StateListDrawable的过程

      从图1中,我们可以看到一个完整的从XML中解析出StateListDrawable的过程,觉得图已经画的比较清楚了,所以就不在多说了。

二,View的DrawableState的设置过程

图2 View的DrawableState的设置过程

图2有些地方感觉还要多说两句,

1,onCreateDrawableState函数里有完整当前视图控件状态判断的方法,具体代码如下:

        int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0); // 下压状态判断。这个状态的设置一般是在setPressed函数里进行的。        viewStateIndex = (viewStateIndex << 1)                + (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0); // 使能状态判断。这个状态的设置一般是setEnabled函数里进行的。        viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0); // 焦点状态判断。这个状态的设置一般是requestFocus函数里进行的。        viewStateIndex = (viewStateIndex << 1)                + (((privateFlags & SELECTED) != 0) ? 1 : 0); // 选择状态判断。这个状态的设置一般是setSelected函数里进行的。        final boolean hasWindowFocus = hasWindowFocus();        viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0); // 视图所在窗口焦点状态判断。        drawableState = VIEW_STATE_SETS[viewStateIndex];


2,一个窗口中可以有多个视图处于selected状态,但是只能有一个处于focused状态;

3,视图处于focused状态时,不一定处于window_focused状态。典型的情况是,窗口初次创建时,ViewRoot会执行performTraversals(),执行中根视图会执行requestFocus()操作,此时一般会有一个视图处于focused状态;但是这个时候视图并不一定是处于window_focused状态,因为window_focused是由系统发出WINDOW_FOCUS_CHANGED消息时,让ViewRoot执行windowFocusChanged()来改变的。这些可以通过重写View的requestFocus()和onWindowFocusChanged()来证明;

4,数组View.VIEW_STATE_SETS是一个非常重要的成员变量,它记录View的所有可能状态。View.VIEW_STATE_SETS这个数组里的部分状态组合是用View.stateSetUnion函数计算出来的。考虑到这个数组成员在后面StateSet.stateSetMatches函数里将被使用来判断状态是否匹配,觉得有必要具体说一下,下面是它的实现代码:

    private static int[] stateSetUnion(final int[] stateSet1,                                       final int[] stateSet2) {        final int stateSet1Length = stateSet1.length;        final int stateSet2Length = stateSet2.length;        final int[] newSet = new int[stateSet1Length + stateSet2Length]; // 新状态集合newSet大小是stateSet1和stateSet2大小之和,默认值都是0        int k = 0;        int i = 0;        int j = 0;        // This is a merge of the two input state sets and assumes that the        // input sets are sorted by the order imposed by ViewDrawableStates.        for (int viewState : R.styleable.ViewDrawableStates) {            if (i < stateSet1Length && stateSet1[i] == viewState) {                newSet[k++] = viewState; // 把stateSet1里的状态值赋给newSet                i++;            } else if (j < stateSet2Length && stateSet2[j] == viewState) {                newSet[k++] = viewState; // 把stateSet2里的状态值赋给newSet                j++;            }            if (k > 1) {                assert(newSet[k - 1] > newSet[k - 2]);            }        }        return newSet;    }

      我们来看一下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" />    </declare-styleable>


      从这里我们看到,其实数组R.styleable.ViewDrawableStates里其实就是R.attr.state_pressed,R.attr.state_focused,R.attr.state_selected ,R.attr.state_window_focused和 R.attr.state_enabled 的集合。用语言总结一下View.stateSetUnion函数的作用,将状态集合stateSet1和stateSet2里的状态值,按照状态值在数组R.styleable.ViewDrawableStates里的先后顺序赋给新状态集合newSet。

5,考虑到完整性和对称性,这里把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;            }            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;                    }                }            }            if (mustMatch && !found) {                // We've reached the end of states to match and we didn't                // find a must-match state.                return false;            }        }        return true;    }


三,View的DrawableState变化过程。有了上面的基础,这里就非常简单了,我就举一个例子来具体说明一下吧,下图是View.setPressed的时序图:


图3 View.setPressed时序图

把图3和图2结合起来看,就可以清楚地理解状态变化是如何实现的了。

四,相关的一些小技巧

有时候UI可能不给开发人员背景图资源,但是还是希望开发人员做到Button下压时,Button上的文字颜色变暗。这个时候,就可以用这个小技巧来实现了:设置android:textColor属性。具体例子请参见《Android 中设置TextView的颜色setTextColor》

备注:

1,Button是TextView的子类。

2,相关代码实现在TextView.drawableStateChanged函数里

    protected void drawableStateChanged() {        super.drawableStateChanged(); // background在这里起作用了        if (mTextColor != null && mTextColor.isStateful()                || (mHintTextColor != null && mHintTextColor.isStateful())                || (mLinkTextColor != null && mLinkTextColor.isStateful())) {            updateTextColors(); // 就是这里textColor开始起作用了        }        final Drawables dr = mDrawables;        if (dr != null) {            int[] state = getDrawableState();            if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {                dr.mDrawableTop.setState(state);            }            if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {                dr.mDrawableBottom.setState(state);            }            if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {                dr.mDrawableLeft.setState(state);            }            if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {                dr.mDrawableRight.setState(state);            }        }    }