最近、Databindingを理解するために、Google codelabs Android DataBindingで学習してみたのですが、思考停止で書かれている通りに実装したところ、Databindingは実装できたものの、どのコードと、どのコードが関連して動作しているのかが、いまいちピンときていなかったので、コード同士のつながりを確認することにしました。
この記事はその確認結果のまとめです。
Google codelabs Android DataBindingで作るもの
このCodelabsでは以下のようなものを作ります。(ほぼ完成しているものが準備されている)
LIKEボタンをポチポチするとLikesの数字が増えて、Likesが一定以上になると、プログレスバーとアイコンが変化します。
最初から準備されているコードは、DataBindingを使用せずにこの挙動を実現している状態なので、それを修正してDataBindingで実現する、というのがこのCodelabsの主旨です。
Codelabsの内容
全力でサボりますが、こちらの記事で全編内容が紹介されています。分かりにくい部分(Codelabs内のケアレスミスとか)も注釈として書かれてるので参考になりました。
コードのつながりを読み解く
読み解くというほどのものでもないかもですが、読み解きます笑
※ここで扱うコードは、Codelabsの最初に提供されるものではなく、Codelabsが完了した段階でのものです。
かなりざっくり言うと、このアプリは
・PlainOldActivity.kt
・SimpleViewModel.kt
・BindingAdapter
・plain_activity
で構成されています。
これらの中から、DataBindingの構成要素を独断と偏見で抜き出すと、こんな概略図になります。(いろいろ省略してます)
まず、plain_activity.xml内でレイアウト変数(layout variables)が定義されています。
<data>
<variable
name="viewmodel"
type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
</data>
nameはviewmodelとなっていて、
typeは**"com.example.android.databinding.basicsample.data.SimpleViewModel"**
を指定しており、これはSimpleViewModel.ktのことを指しています。
なので意味的には、「viewmodelは、SimpleViewModel.ktのことを指す」という具合になりそうです。
続いてタグ内で
**android:text="@{}"**というような表記 (layout expression、レイアウト式) が出てきます。
ここにdataタグ内で記述したレイアウト変数が入ります。
こんな感じ↓
<TextView
android:id="@+id/plain_name"
android:text="@{viewmodel.name}"
以下省略
/>
ここでdataタグ内のnameで定義した、viewmodelが**@{viewmodel.name}**という形で記述されています。
これはデータの紐付け先を示しています。
viewmodelは**SimpleViewModel.kt(com.example.android.databinding.basicsample.data.SimpleViewModel)**を型として指定していたので、「SimpleViewModel.ktからnameを引っ張って来い」というように読めます。
ただ、plain_activity.xmlからSimpleViewModel.ktへ直接紐付けされているわけでありません。
PlainOldActivity.ktは以下のようになっています。
class PlainOldActivity : AppCompatActivity() {
// Obtain ViewModel from ViewModelProviders
private val viewModel by lazy { ViewModelProviders.of(this).get(SimpleViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//➀
val binding : PlainActivityBinding =
DataBindingUtil.setContentView(this, R.layout.plain_activity)
binding.lifecycleOwner = this
//➁
binding.viewmodel = viewModel
}
}
➀はbinding先のレイアウト(plain_activity.xml)が定義されています。
➁はbinding.viewmodel = viewModelと定義されています。
binding.viewmodelの”viewmodel”は、plain_activity.xmlで定義したviewodelのことを指しています。
一方、右辺のviewModel(※Mが大文字なので”viewmodel”とは異なる)はSimpleViewModel.ktのViewModelを、 ViewModelProvidersで受け取ったものです。
なので、binding.viewmodel = viewModelは
plain_activity.xmlとSimpleViewModel.ktのViewModelの紐付けを表しています。
plain_activity.xmlとSimpleViewModel.ktが紐づけされた状態なので、
android:text="@{viewmodel.name}"や、**app:hideIfZero="@{viewmodel.likes}"****のように、レイアウトからSimpleViewModel.ktの変数(name, likes)を引用することができます。
例えばnameは、_nameが代入されており、_nameは**MutableLiveData("Ada")**として定義されているので、Nameのところに”Ada”が表示されます。
BindingAdapter
さて、残りのBindingAdapter.ktでは、plain_activity.xmlが受け取った変数を引数とするメソッドが定義されています。以下に一例を挙げます。
@BindingAdapter("app:popularityIcon")
fun popularityIcon(view: ImageView, popularity: Popularity) {
val color = getAssociatedColor(popularity, view.context)
ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))
view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}
<ImageView
app:popularityIcon="@{viewmodel.popularity}"
以下省略
/>
この場合だと**@BindingAdapter(app:popularityIcon)**という表記で、popularityIconメソッドが、plain_activity.xmlのImageViewと紐付けされています。
メソッドの引数の型は、**(ビューの型,レイアウト側から受けとる変数の型)**となっています。
上の例だと、fun popularityIcon **(view: ImageView, popularity: Popularity)**のようになっていて、渡されたpopularityの中身によってメソッドの処理結果が変わるため、ImageViewの画像表示が切り替わるようになっています。
これで、SimpleViewModel.ktからBindingAdapter.ktまでのつながりが(ふわ~っと笑)確認できました。
BindingAdapterの部分だけ抜き出した場合はこんな感じ↓
ボタンタップからUI更新の流れ
このアプリはLIKEボタンがトリガーになっていて、LIKEボタンのタップ回数によって画像や色が変わっていきますが、どういう挙動になっているのか確認するために、流れをまとめました。
(図はなんやかんや省略してます。"イメージをつかむ"程度の認識で眺めてください)
LIKEボタンのタップ
・LIKEボタンを押すと、ボタンがSimpleViewModelのonLike()メソッドと紐付いているので、_likesが増える。
↓
・_likesはlikes、popularityの二つのLiveDataに代入されるので、2つに分岐する。
likes側
➀TextViewがlikesを受け取り、String型に変換し、タップ数として表示する。
➁プログレスバー(ProgressBar)は2つの@BindingAdapterと紐づいているので分岐する。
➁-1 likesが0ならばプログレスバーを非表示、それ以外の場合は表示する。
➁-2 likesの数量に応じてプログレスバーの進捗具合を表示する。
popularity側
Transformations.mapで_likesがPopularity型へ変換され、when式でPopularityレベルを分ける。
↓
➀、➁共にSimpleViewModel.ktからpopularityを受け取り、@BindingAdapterで紐づいたメソッドへ、引数として渡す。
➀はImageViewの画像の切り替え、➁はプログレスバーの色変更をする。
感想
まとめるのにだいぶ時間がかかってしまいましたが、概略図がなんとなくイメージできるようになったので、やった意味はあったと思います。実際にたくさん使って、何か作ってみようと思います~^^