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

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

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

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

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

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

二,View的DrawableState的设置过程

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

图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的时序图:

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

图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);
            }
        }
    }

    原文作者:imyfriend
    原文地址: https://blog.csdn.net/imyfriend/article/details/8091476
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞