自定义View之DrawableState

一、什么是DrawableState?

我第一次见到这个词的时候,也是一脸懵逼。感觉应该是一个很复杂的东西,平时开发中应该用不到,即使用到了应该也很复杂。但如果我告诉你平时用到的selector就是通过DrawableState来控制View在不同状态(比如被按下,不可用,被选中等)显示不同内容的时候,似乎离我们平时的开发特别近。

比如一个Button,它有pressed,focused,或者其它状态。当我们为按下button的是,背景色就会发生变化;当button不可用的时候也会显示不同的color。这些都是DrawableState的功劳。

DrawableState是View的属性,一般是配合Drawable对象进行使用。

二、DrawableState有哪些状态?

android官方API上列出了下面的几个状态,这些状态值都是true/false。

  • state_pressed:被按下;
  • state_focused:获得焦点,例如当用户选择文本输入时;
  • state_hovered:当光标悬停在对象上时应使用此项目,这个通常与focused state相同;
  • state_selected:被选中;
  • state_checkable:能否被选中;
  • state_checked:被选中;
  • state_enabled:能够接受触摸或者点击事件
  • state_activated:被激活
  • android:state_window_focused:应用程序是否在前台。

看完了上面这些状态,再来看下在selector中的使用方法:

<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true"
          android:drawable="@drawable/button_pressed" /> <!-- pressed -->
    <item android:state_focused="true"
          android:drawable="@drawable/button_focused" /> <!-- focused -->
    <item android:state_hovered="true"
          android:drawable="@drawable/button_focused" /> <!-- hovered -->
    <item android:drawable="@drawable/button_normal" /> <!-- default -->
</selector>

selector就是标注了不同状态下对应显示不同的内容。

三、View中和DrawableState相关的方法

首先DrawableState是View的方法,是一个int数组,用来储存各种状态。
然后我们来看下相关的方法:(这里主要参考《Android 中 View 的中的 DrawableState》里面写的非常详细)

1. protected int[] onCreateDrawableState(int extraSpace)
  • jjavadoc: 这个方法会在 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() 方法。
2. public final int[] getDrawableState()
  • javadoc: 返回一个资源 id 的数组,这些 id 代表着 View 的当前状态。
  • View中的实现: 如果没有 PFLAG_DRAWABLE_STATE_DIRTY 标志,直接返回缓存的 mDrawableState;否则,调用 onCreateDrawableState 获取新的状态返回,并把 PFLAG_DRAWABLE_STATE_DIRTY 标志去掉。
  • ViewGroup中的实现: 没有重载。
3. 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() 方法。
4. public void refreshDrawableState()
  • javadoc: 这个方法被调用来强制更新一个 View 的 drawable state。这个方法会导致 View 的 drawableStateChanged 方法被调用。如果对新的 drawable state 感兴趣,可以通过 getDrawableState 方法获取。
  • View中的实现: 设置上 PFLAG_DRAWABLE_STATE_DIRTY 标志,调用 drawableStateChanged(),如果有父 View,调用父 View 的 childDrawableStateChanged() 方法。
  • ViewGroup中的实现: 没有重载。
5. protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState)
  • javadoc: 将你存储在 additionalState 中的状态和 baseState 中的状态合并到一起(baseState 通常是由 getDrawableState 方法得到的),为了简化,baseState 会作为参数被返回,也就是,baseArray 中必须提前预留存放 additionalState 的空间,否则也无法合并成功。
  • View中的实现: 从 baseState 数组第一个为 0 的元素开始,将 additionalState 数组中的内容拷贝过来,最后把 baseState 返回。
  • ViewGroup中的实现: 没有重载。
6. public void childDrawableStateChanged(View child)
  • javadoc: 这个是 ViewGroup 的方法。如果有 FLAG_ADD_STATES_FROM_CHILDREN 标志的话,刷新这个 ViewGroup 的 drawable state,将子 View 的状态加入进去.
  • View中的实现: 没有定义。
  • ViewGroup中的实现: 如果有FLAG_ADD_STATES_FROM_CHILDREN 标志,调用 refreshDrawableState() 方法。

四、Java代码中是如何判断DrawableState?

我们在selector中设置不同state下的drawable。接下来当view的state发生了变化,是如何根据不同state,以及怎样去刷新新状态下的drawable?我们从源码上来看一下。

以最简单的button为例,我们设置了state_press时的color,当按下时就会显示state_press = true时的颜色。Button的继承了TextView,TextView又继承了View。所以下面会主要从这三个类的源码上来分析。

  1. 我们先定义一个selector,内容就是设置normal状态下的颜色,还有state_press=true时的背景色。代码太简单,就不放了。然后在设置Button的background为我们刚才定义的selector。
  2. 再来看下,Java代码中是如何获取我们设置的selector。background属性是在View中获取的:


    View获取背景属性

    然后赋值给一个Drawable对象background。关于Drawable,如果不会的同学可以自己学习一下。

  3. 当Button被按下,这时候会调用refreshDrawableState()方法来通知View的DawableState状态发生了变化。比如上面说的被按下press:


    Button的press事件
  4. View在刷新DrawableState的时候


    refreshDrawableState源码

    这里在刷新的时候,会调用drawableStateChanged通知状态改变。然后通知parent,有子view的DrawableState发生了变化。

  5. 然后就来看一下Button在drawableStateChanged的时候,做了什么。
    结果就是Button中没有对drawableStateChanged的处理,所以要从它的父类中寻找。
    Button是继承了TextView,先来看下TextView中是如何处理的。直接上源码:


    TextView中drawableStateChanged方法源码

    TextView主要进行了文字颜色(包括textcolor、hint、link时的字体颜色)和Drawables的刷新。这也就是说,如果我们对Button或者TextView、EditText(EditText也是继承了TextView)的字体颜色这是了selector,那么当控件的状态发生了变化,字体颜色也会发生变化。
    因为我们在最开始的时候说的是被按下时背景色的变化,所以就先不去看TextColor了。继续找关于background的设置,去super(也就是view)中去找。

  6. View中drawableStateChanged()方法进行了哪些处理


    View中drawableStateChanged()源码

    View中drawableStateChanged()源码
  • 先获取当前的drawableState和background对象;
  • 把新drawableState通过setState方法设置给background对象。如果新的state影响到了显示效果,那么就会返回true,通知要发生变化。
  • 然后再判断前景色和滑动,这里和咱们无关。
  • 然后判断是否有状态变化的动画效果。如果有的话,根据新状态进行显示。
  • 最后如果新状态导致显示效果发生了变化,那么调用invalidate进行view重绘。
  • 这样新状态下的drawable就会重新绘到了View的背景上。也就有了我们看到的按下效果。
  • 当我们松开按钮后,drawable的状态又恢复了,继续走这么一遍流程。这样按下和松开的效果就有了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容