View源码——duplicateParentState
基于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;
}
需要注意一下三点:
- 当前的实现只是设置
DUPLICATE_PARENT_STATE
标识位,并不会引起重绘,所以如果在view被add到父view后才设置,可能不会起作用。 - 如果父view设置了
addStateFromChildren
(父view添加子view的状态,下面会讲),那么设置duplicateParentState
会导致崩溃。 - 对于父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状态。
总结:
-
android:duplicateParentState="true"
对应DUPLICATE_PARENT_STATE
标志位,android:addStatesFromChildren="true"
对应FLAG_ADD_STATES_FROM_CHILDREN
标志位。 - 若父view设置了
android:addStatesFromChildren="true"
,则子View不可再设置android:duplicateParentState="true"
,否则会崩溃。 - 子view的
android:duplicateParentState="true"
需要在添加到父view之前设置。 -
android:duplicateParentState="true"
会使子view完全套用父view的状态,但对于额外添加的状态(如checked状态)需要子view自行处理。 - 设置
android:addStatesFromChildren="true"
,父view最终的状态是自身状态与所有子view状态的合集,并不是完全使用某个子view的状态。 - 设置
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
作为背景