关闭

Android 中 View 的中的 DrawableState

标签: selectorandroiddrawablestate
1525人阅读 评论(0) 收藏 举报
分类:

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,并把它赋值给 mBackgroundmStateListAnimator
      ViewGroup 中的实现: 如果有 FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE 标志的话,还会遍历每个子 View,如果子 ViewDUPLICATE_PARENT_STATE 标志,就调用子 ViewrefreshDrawableState() 方法。

    • 方法: protected int[] onCreateDrawableState(int extraSpace)
      javadoc注释:View 生成新的 Drawable 的状态。这个方法会在缓存的 Drawable state 被认为失效(invalid)之后被 view 系统所调用。如果要获取当前的状态,你应该使用 getDrawableState 方法。方法参数 extraSpace 如果不是0的话,这个值可以代表你希望返回的数组中除了装载当前的状态之外,额外的空间,你可以使用这些空间来存放你自己的状态。
      View 中的实现: 如果 View 被设置为和父 Viewdrawable state 一致,返回父 viewdrawable 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() 方法。

  • 然后我们来梳理一下这些方法调用的流程:

    1. View 的状态(onCreateDrawableState方法需要收集的各种标志)发生变化的时候,会调用 refreshDrawableState 方法。
    2. refreshDrawableState 方法中会设置上 PFLAG_DRAWABLE_STATE_DIRTY 标志,然后调用 drawableStateChanged 方法。
    3. drawableStateChanged 方法中,会调用 getDrawableState 方法获取当前状态,并把状态赋值给 mBackgroundStateListAnimator
    4. 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 那样。

你需要这样做:

  1. 定义一个成员变量来标识当前的选中状态

    private boolean mChecked;
  2. 定义一个改变选中状态的方法,在状态改变时调用 refreshDrawableState 方法

    public void setChecked(boolean checked) {
    if (mChecked != checked) {
        mChecked = checked;
        // 刷新 drawable state
        refreshDrawableState();
    }
    }
  3. 重写 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 方法就行了。

2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:10805次
    • 积分:225
    • 等级:
    • 排名:千里之外
    • 原创:11篇
    • 转载:0篇
    • 译文:3篇
    • 评论:0条