Infinito Nirone 7

白羽の矢を刺すスタイル

StateListDrawable の子にあたる Drawable の背景色を Animator で変えてみる

概要

Android Framework に数ある Drawable のなかでも、View の状態に応じて使う Drawable を切り替えることのできる StateListDrawable は多くの人が使っていると思います。ListView のアイテムの背景、ボタンの背景などインタラクションをする View に対して使うことが多いでしょうか。

ところでAndroid の場合、ユーザの指による操作以外にもマウスやキーボードなどの外部入力装置による操作もインタラクションの手段として使えます。外部入力装置を使う場合、いまどこを操作しようとしているかを見せるために、StateListDrawable の state_focused を使って Drawable を切り替えます。

StateListDrawable の各状態で使う Drawable は ShapeDrawable や BitmapDrawable など他の Drawable です。今回は、StateListDrawable の各状態に ShapeDrawable を設定し、state_focused などを考慮しつつ Java や Kotlin のコードから ShapeDrawable の背景を Animator で変えてみようと思います。

<selector>

次のような StateListDrawable を xml で書きます。クリック時には背景を濃いグレーに、フォーカスが当たった時は枠線を描きます。クリック時でかつフォーカスが当たったときは濃いグレーの背景に枠線を描きます。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_focused="true" android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#333333"/>
      <stroke android:width="3dp" android:color="00AAFF"/>
    </shape>
  </item>
  <item android:state_pressed="true">
    <shape android:shape="rectangle">
      <solid android:color="#333333"/>
    </shape>
  </item>
  <item android:state_focused="true">
    <shape android:shape="rectangle">
      <solid android:color="#999999"/>
      <stroke android:width="3dp" android:color="00AAFF"/>
    </shape>
  </item>
  <item>
    <shape android:shape="rectangle">
      <solid android:color="#999999"/>
    </shape>
  </item>
</selector>

Drawable の背景色を変更する

StateListDrawableDrawableContainerの子クラスで、DrawableContainerにはgetCurrentというメソッドがあります。StateListDrawableのような状態を持つものの場合は、現在表示しているものを返してくれるのでこれを使います。

さきほど xml で作った StateListDrawable の子はどれも<shape>ですので、getCurrentメソッドで得られるのもその時の状態で指定している<shape>です。<shape>タグを読み込むと、どの形(四角形、円など)かという ShapeDrawable とどのような背景色や枠線を描画するかという GradientDrawable のふたつが作られます。StateListDrawable#getCurrent()で直接得られる Drawable はGradientDrawableであることに注意しましょう。 そして取り出したGradientDrawablesetColor(int)メソッドで背景色を変えることができます。 あとは、Animator を使えば背景色をアニメーションできます。

fun animateBackgroundColor(view: View, color1: Int, color2: Int, duration: Long) {
  val background = view.background as StateListDrawable
  val animator = ValueAnimator.ofObject(ArgbEvaluator(),
      ContextCompat.getColor(view.context, color1),
      ContextCompat.getColor(view.context, color2))
  animator.duration = duration
  animator.addUpdateListener {
    (background.current as GradientDrawable).setColor(it.animatedValue as Int)
  }
  animator.start()
}