参考
前回に引き続き、HeaderView と DataBinding でいろいろやった話。
- Android Studio 3.2.1
- アーキテクチャ : MVVM
- 言語 : Kotlin
やりたいこと
NavigationDrawer の項目をタップすると、HeaderView の背景色(ConstraintLayout.background要素)が徐々に変わるアニメーションの実装です。
イメージは次の感じ。

gif にするにはちょっと duration 短かったかも( ˘ω˘)
手順
MainViewModel の作成
View にバインドするデータ(headerColorTo)を ViewModel に定義します。
class MainViewModel(application: Application): AndroidViewModel(application){ val headerColorTo = MutableLiveData<Int>() fun setRecipeType(enum: RecipeTypeEnum) { headerColorTo.value = ContextCompat.getColor(getApplication(), enum.symbolColorId) } }
この MainViewModel を HeaderView のレイアウトファイルにバインドしますが、その前に@BindingAdapterでカスタムセッターを定義します。
拡張関数でカスタムセッター定義
ConstraintLayout の親クラスである ViewGroup に、拡張関数と BindingAdapter アノテーションを使ってカスタムセッターを定義します。
メソッド名はsetBackgroundColorToとしていますが、コード上から呼ぶことはないので ViewGroup の既定のメソッドと被らなければなんでもいいです。
現在の背景色はthis.backgroundで取得します(ColorDrawable? 型)。このとき の返り値は null の可能性があるので、null チェックを行っています。
そしたら Animator オブジェクトを生成し、変化時間(duration)、UpdateListener 等を設定してstartを叩けば背景色が徐々に変化するアニメーションが出来上がります。
@BindingAdapter("bind:animate_background_color") fun ViewGroup.setBackgroundColorTo(colorTo: Int){ val backgroundDrawable = background //backgroundがnullの場合あり。 //既定の色を colorFrom 変数に格納 val colorFrom = if (backgroundDrawable != null){ (background as ColorDrawable).color } else { //nullだった場合既定の色を設定 ContextCompat.getColor(context, R.color.all_dish_color) } //色変化のAnimatorの生成 val colorAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo).also { it.setTarget(this) it.duration = 500 it.addUpdateListener { animator -> val animatedValue = animator.animatedValue as Int setBackgroundColor( Color.argb(Color.alpha(animatedValue), Color.red(animatedValue), Color.green(animatedValue), Color.blue(animatedValue))) } } colorAnimator.start() }
さて、ここで作成したbind:animate_background_colorにMainViewModel.headerColorToをバインドします。
HeaderView レイアウトの定義
真ん中のアイコン用の ImageView 一個だけです。
<layout xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewmodel" type="...MainViewModel" /> </data> <ConstraintLayout android:id="@+id/navigation_header_frame" bind:animate_background_color="@{viewmodel.headerColorTo}" ...> <ImageView android:id="@+id/header_image" .../> </ConstraintLayout> </layout>
ViewGroup を拡張してbind:animate_background_colorを定義したので、ViewGroup を継承した ConstraintLayout にもカスタムセッターが出てきます。
これで、NavigationDrawer の項目をタップしたイベントでMainViewModel#setRecipeTypeをコールするようにすれば、
MutableLiveData#setValue→ViewModel#setBackgroundColorTo
が呼ばれるので、タップした項目に応じて色変化のアニメーションが実行されます。
//念のため再掲 class MainViewModel(application: Application): AndroidViewModel(application){ val headerColorTo = MutableLiveData<Int>() //コレ↓ fun setRecipeType(enum: RecipeTypeEnum) { headerColorTo.value = ContextCompat.getColor(getApplication(), enum.symbolColorId) } }
NavigationDrawer のイベント実装
NavigationDrawer の項目をタップしたときに MainViewModel#setRecipeTypeをコールするように、イベントを実装します。
NavigationDrawer はレイアウトファイル上 Activity に配置されているのでActivity#onCreateで実装してもいいですが、UIアクション実装の原則に従って Fragment 側で実装するようにしましょう( ˘ω˘)
mainBinding.navView.setNavigationItemSelectedListener{ menuItem ->
when (menuItem.itemId){
R.id.all_navigation_menu_item ->{ binding.viewmodel?.setRecipeType(RecipeTypeEnum.ALL_DISH) }
//中略
else ->{ throw IllegalArgumentException() }
}
...
return@setNavigationItemSelectedListener true
}
それでは最後に HeaderView の Inflate の実装です。一癖あってハマったところ。
HeaderView の Inflate
前回の記事をご参照ください><
やったことまとめ
- ViewModel クラスを定義、フィールドに MutableLiveData 型の変数を宣言
- 拡張関数 + BindingAdapter でアニメーション用のカスタムセッターを定義
- HeaderView の DataBinding Layout File(xml)を定義、カスタムセッターにバインド
- NavigationDrawer のイベント実装
所感
Java でカスタムセッターを定義する場合は static クラスを定義して行う模様ですが、Kotlin では static クラスを定義できません。
ググるとシングルトン(object)クラスで実装する方法が出てきましたが、なんとなくソレジャナイ感があったのでさてどうしたものかと思っていました。
そんな中ヒットしたのがこの記事です↓
やはり Kotlin は最強ですね。ちょっと凝ったことしようとすると拡張関数どんどこ増えるけど気にしない(マテ