美文网首页
View源码——duplicateParentState

View源码——duplicateParentState

作者: sollian | 来源:发表于2019-01-28 18:06 被阅读22次

    基于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作为背景

    相关文章

      网友评论

          本文标题:View源码——duplicateParentState

          本文链接:https://www.haomeiwen.com/subject/sqfzjqtx.html