View源码——duplicateParentState

2019-01-28  本文已影响22人  sollian

基于api28

设置当前view是否使用父view的状态,默认false。该状态主要影响drawable的显示。
对应的java方法:

    public void setDuplicateParentStateEnabled(boolean enabled) {
        setFlags(enabled ? DUPLICATE_PARENT_STATE : 0, DUPLICATE_PARENT_STATE);
    }

    public boolean isDuplicateParentStateEnabled() {
        return (mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE;
    }

需要注意一下三点:

  1. 当前的实现只是设置DUPLICATE_PARENT_STATE标识位,并不会引起重绘,所以如果在view被add到父view后才设置,可能不会起作用。
  2. 如果父view设置了addStateFromChildren(父view添加子view的状态,下面会讲),那么设置duplicateParentState会导致崩溃。
  3. 对于父view没有的状态,需要子view自行处理。

第一点

对于第一点好理解,就是一个时序问题。

第二点

对于第二点,过一遍源码就清楚了。

View的state改变时,会调用refreshDrawableState方法来刷新drawable的显示:

    public void refreshDrawableState() {
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        drawableStateChanged();

        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }

跟进一下drawableStateChanged

    @CallSuper
    protected void drawableStateChanged() {
        final int[] state = getDrawableState();
        ...
    }

    public final int[] getDrawableState() {
        ...
        mDrawableState = onCreateDrawableState(0);
        ...
    }

    protected int[] onCreateDrawableState(int extraSpace) {
        if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
                mParent instanceof View) {
            return ((View) mParent).onCreateDrawableState(extraSpace);
        }
        ...
    }

这里只保留了关键代码。可以看到,在onCreateDrawableState方法中,如果设置了DUPLICATE_PARENT_STATE标识位,则直接拿父view的状态,这时子view自身的状态完全被忽略了。

另外,refreshDrawableState还会调用父view的childDrawableStateChanged方法。

下面看下ViewGroup中的源码:

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        ...
        if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
            mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
        }
        ...
    }

若子view设置了DUPLICATE_PARENT_STATE,则父view会设置一个FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE标识。

ViewGroup有个android:addStatesFromChildren属性,默认false,对应的标志位是FLAG_ADD_STATES_FROM_CHILDREN,对应的java方法为:

    public void setAddStatesFromChildren(boolean addsStates) {
        if (addsStates) {
            mGroupFlags |= FLAG_ADD_STATES_FROM_CHILDREN;
        } else {
            mGroupFlags &= ~FLAG_ADD_STATES_FROM_CHILDREN;
        }

        //这里会主动更新drawable状态,与setDuplicateParentStateEnabled不同
        refreshDrawableState();
    }

    public boolean addStatesFromChildren() {
        return (mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0;
    }

然后看看被子view调用的childDrawableStateChanged方法

    @Override
    public void childDrawableStateChanged(View child) {
        if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
            refreshDrawableState();
        }
    }

若设置了FLAG_ADD_STATES_FROM_CHILDREN标志位,则调用自身的refreshDrawableState来更新状态。

ViewGroup的drawableStateChanged:

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) {
            if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
                //这里就是第二点中提到的崩溃
                throw new IllegalStateException("addStateFromChildren cannot be enabled if a"
                        + " child has duplicateParentState set to true");
            }

            final View[] children = mChildren;
            final int count = mChildrenCount;

            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) {
                    //对设置了DUPLICATE_PARENT_STATE标识的子view,通知其更新状态
                    child.refreshDrawableState();
                }
            }
        }
    }

然后是ViewGroup对onCreateDrawableState的处理:

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) {
            //未设置FLAG_ADD_STATES_FROM_CHILDREN,则使用默认实现
            return super.onCreateDrawableState(extraSpace);
        }

        int need = 0;
        int n = getChildCount();
        for (int i = 0; i < n; i++) {
            int[] childState = getChildAt(i).getDrawableState();

            if (childState != null) {
                need += childState.length;
            }
        }

        //先获取自身状态
        int[] state = super.onCreateDrawableState(extraSpace + need);

        for (int i = 0; i < n; i++) {
            int[] childState = getChildAt(i).getDrawableState();
            if (childState != null) {
                //在添加上子view的状态
                state = mergeDrawableStates(state, childState);
            }
        }

        //最终返回的是自身状态+每个子view的状态
        return state;
    }

至此,第二点已经讲完。

第三点

至于第三点,“对于父view没有的状态,需要子view自行处理”,直接看一个子类的实现就清楚了:

CompoundButton

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked()) {
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }

CompoundButton添加了checked状态,其他View没有,所以需要自行处理。
需要提到的一点是,若CompoundButton的父类设置了FLAG_ADD_STATES_FROM_CHILDREN标志位,则也会获得checked状态。

总结:

  1. android:duplicateParentState="true"对应DUPLICATE_PARENT_STATE标志位,android:addStatesFromChildren="true"对应FLAG_ADD_STATES_FROM_CHILDREN标志位。
  2. 若父view设置了android:addStatesFromChildren="true",则子View不可再设置android:duplicateParentState="true",否则会崩溃。
  3. 子view的android:duplicateParentState="true"需要在添加到父view之前设置。
  4. android:duplicateParentState="true"会使子view完全套用父view的状态,但对于额外添加的状态(如checked状态)需要子view自行处理。
  5. 设置android:addStatesFromChildren="true",父view最终的状态是自身状态与所有子view状态的合集,并不是完全使用某个子view的状态。
  6. 设置android:addStatesFromChildren="true",父view会获得一些自身没有的状态,比如checked状态。

对于第6点的一个例子是:

    <LinearLayout
        android:id="@+id/parent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:addStatesFromChildren="true"
        android:background="@drawable/bg_selector_2"
        android:clickable="true"
        android:gravity="center"
        android:orientation="vertical">

        <RadioButton
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_selector_1"
            android:clickable="true"
            android:padding="10dp"
            android:text="点我"
            android:textColor="#000"
            android:textSize="18dp" />
    </LinearLayout>

bg_selector_2.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/bg_pressed_2" android:state_pressed="true" />
    <item android:drawable="@drawable/bg_checked_2" android:state_checked="true" />
    <item android:drawable="@drawable/bg_normal_2" />
</selector>

LinearLayout可以在RadioButton被选中时,显示bg_selector_2中的bg_checked_2作为背景

上一篇下一篇

猜你喜欢

热点阅读