Android 中 View 的中的 DrawableState
挺久之前就会用 selector 这种资源来给一个 drawable 对象标识不同的状态了,但是之前的认识只停留在设置一个正常状态的图片,设置一个 state_pressed 状态的图片,然后把这个 selector 作为控件的背景,当控件被点击(前提是设置了可点击或者点击监听器)的时候图片会自动切换。但是 View 内部究竟是如何对 Drawable 对象的状态进行控制的,之前一直不清楚,直到前几天,因为工作的原因研究了一下相关的源码,终于对它有了一个比较深的认识。同时也学会如何 通过简单操作去改变控件中的 Drawable 的状态,相对于之前每次状态改变手动更换图片来说,控制 drawable state 的方式实在太爽了。
View 是如何控制 Drawable 状态的。
首先看和
Drawable状态相关的几个方法:方法:
protected void drawableStateChanged()
javadoc: 这个方法会在View的状态改变并影响到正在显示的drawable的状态的时候会被调用。如果这个View拥有一个StateListAnimator的话,这个StateListAnimator也会被调用来执行必要的状态改变动画。如果重写这个方法的话要确保调用父类的方法。
View 中的实现: 调用getDrawableState()获得当前的drawable state,并把它赋值给mBackground和mStateListAnimator。
ViewGroup 中的实现: 如果有FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE标志的话,还会遍历每个子View,如果子View有DUPLICATE_PARENT_STATE标志,就调用子View的refreshDrawableState()方法。方法: protected int[] onCreateDrawableState(int extraSpace)
javadoc注释: 为View生成新的Drawable的状态。这个方法会在缓存的Drawable state被认为失效(invalid)之后被view系统所调用。如果要获取当前的状态,你应该使用getDrawableState方法。方法参数extraSpace如果不是0的话,这个值可以代表你希望返回的数组中除了装载当前的状态之外,额外的空间,你可以使用这些空间来存放你自己的状态。
View 中的实现: 如果View被设置为和父View的drawable state一致,返回父view的drawable state。否则从各种Flag中获取view的状态信息并封装到一个数组中返回(数组长度是收集到的信息数量加上参数extraSpace的大小)。
ViewGroup 中的实现: 如果没有FLAG_ADD_STATES_FROM_CHILDREN标志的话直接调用父方法返回。否则除了调用父方法,还要遍历子View,通过getDrawableState拿到它们的drawable state,使用mergeDrawableStates合并到自己的drawable state。方法:
public final int[] getDrawableState()
javadoc: 返回一个资源 id 的数组,这些 id 代表着 View 的当前状态。
View中的实现: 如果没有 PFLAG_DRAWABLE_STATE_DIRTY 标志,直接返回缓存的 mDrawableState;否则,调用 onCreateDrawableState 获取新的状态返回,并把 PFLAG_DRAWABLE_STATE_DIRTY 标志去掉。
ViewGroup中的实现: 没有重载。方法:
protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState)
javadoc: 将你存储在 additionalState 中的状态和 baseState 中的状态合并到一起(baseState 通常是由 getDrawableState 方法得到的),为了简化,baseState 会作为参数被返回,也就是,baseArray 中必须提前预留存放 additionalState 的空间,否则也无法合并成功。
View中的实现: 从 baseState 数组第一个为 0 的元素开始,将 additionalState 数组中的内容拷贝过来,最后把 baseState 返回。
ViewGroup中的实现: 没有重载。方法:
public void refreshDrawableState()
javadoc: 这个方法被调用来强制更新一个 View 的 drawable state。这个方法会导致 View 的 drawableStateChanged 方法被调用。如果对新的 drawable state 感兴趣,可以通过 getDrawableState 方法获取。
View中的实现: 设置上 PFLAG_DRAWABLE_STATE_DIRTY 标志,调用 drawableStateChanged(),如果有父 View,调用父 View 的 childDrawableStateChanged() 方法。
ViewGroup中的实现: 没有重载。方法:
public void childDrawableStateChanged(View child)
javadoc: 这个是 ViewGroup 的方法。如果有 FLAG_ADD_STATES_FROM_CHILDREN 标志的话,刷新这个 ViewGroup 的 drawable state,将子 View 的状态加入进去.
View中的实现: 没有定义。
ViewGroup中的实现: 如果有FLAG_ADD_STATES_FROM_CHILDREN 标志,调用 refreshDrawableState() 方法。
然后我们来梳理一下这些方法调用的流程:
- 当
View的状态(onCreateDrawableState方法需要收集的各种标志)发生变化的时候,会调用refreshDrawableState方法。 - 在
refreshDrawableState方法中会设置上PFLAG_DRAWABLE_STATE_DIRTY标志,然后调用drawableStateChanged方法。 - 在
drawableStateChanged方法中,会调用getDrawableState方法获取当前状态,并把状态赋值给mBackground和StateListAnimator。 - 在
getDrawableState方法中,会发现存在PFLAG_DRAWABLE_STATE_DIRTY标志,缓存的drawable state已经失效,就会依次查看所有与drawable state相关的标志,组装新的drawable state返回。
- 当
如何让自定义的控件中的 drawable 可以响应状态的变化
你在编程中可能会遇到这样的问题,当你自定义一个控件,而这个控件中除了有背景还可能会有其他的图案,你希望当你按在控件上的时候,所有的图案都发生状态的变化,但结果是只有背景发生了状态的改变。其他图案没有响应状态的变化,为什么呢?因为这些 drawable 没有被设置相应的状态。
想要让自己的 drawable 也像 mBackground 一样随着控件的状态变化而变化,就需要把上面那些方法中用到 mBackground 的位置也加上我们自己的 drawable 的操作,也就是 drawableStateChanged() 方法:
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
// mDrawalbe 是我们自己的 drawable 对象,如果有更多,需要每个都进行这样的操作
Drawable d = mDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
实际上
ImageView中的drawableStateChanged方法的实现和上面是一模一样的。
如何定义自己的 drawable state
首先,drawable state 是没办法自己定义的,所有的 drawable state 就是你在定义 selector 的时候可用的那些。这里说的定义自己的 drawable state 是指 在合适的时机使控件处于某个 drawable state 。比如,你能会希望自己的控件可以支持 state_check, 在某种情况下所有的 drawable 都处于选中状态,另一种情况都处于未选中状态,就像 CheckBox 那样。
你需要这样做:
定义一个成员变量来标识当前的选中状态
private boolean mChecked;定义一个改变选中状态的方法,在状态改变时调用
refreshDrawableState方法public void setChecked(boolean checked) { if (mChecked != checked) { mChecked = checked; // 刷新 drawable state refreshDrawableState(); } }重写
onCreateDrawableState, 根据自己的标志来确定是否加上更多状态// 代表选中状态的集合 private static final int[] CHECK_STATE_SET = new int[] { android.R.attr.state_checked }; @Override public int[] onCreateDrawableState(int extraSpace) { if (!mChecked) { // 如果未选中,直接返回父类的结果 return super.onCreateDrawableState(extraSpace); } else { // 如果选中,将父类的结果和选中状态合并之后返回 return mergeDrawableStates( super.onCreateDrawableState(extraSpace + 1), CHECK_STATE_SET); } }
然后当你希望切换选中状态的时候,调用 setChecked 方法就行了。
本文详细解析了Android中View的DrawableState机制,包括相关方法的调用流程和自定义控件中响应状态变化的方法。通过实例演示如何定义自己的Drawablestate,以及如何在合适的时机使控件处于特定状态。
308

被折叠的 条评论
为什么被折叠?



