#〇前置き
前回、前々回の続編です。
とにかく簡単にViewModelまとめ
とにかく簡単にLiveDataまとめ
ViewModel,LiveDataを会得している人はそのままどうぞ。
#〇DataBindingって何?
JetPackのコンポーネントの一つ。
簡単に書くとViewModelの内容をレイアウトファイル(activity_mail.xml等)に結び付けて、UIの操作をアクティビティ、やフラグメントから完全に分離できる。
つまりMVVMができる!!
#〇参考URL
・[公式]
https://developer.android.com/topic/libraries/data-binding/index.html
・Data Binding — Lessons Learnt
https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719
・Android Data Binding codelab
https://codelabs.developers.google.com/codelabs/android-databinding/#0
コードラボのサンプルは手順を追っていてとてもわかりやすかったです。
#〇プログラム
###・GitHub
https://github.com/KIRIN3git/ViewModel-LiveData-DataBinding
###build.gradle[app]
DataBindingには以下が必要。
'kotlin-kapt'は公式にはなかったのですが、Adapterを使用するときにないとエラーが出てしまいました。
apply plugin: 'kotlin-kapt'
android {
dataBinding {
enabled = true
}
}
###User.kt
名前と年齢を保持するだけのデータクラスを作成。
data class User(val name: String, val age: Int)
###UserViewModel.kt
前回と違いViewModelの更新対象をクラス型(User)にしてみました。
別にnameとageをViewModelに定義して1つ1つMutableLiveDataしても同じ結果です。
change関数を作成して、呼ばれると監視対象のuserの中身が変わります。
せっかくなのでTransformationsを使用しています。
これは、LiveDataの更新を受けて他のLiveDataに連動させるものです。
今回はuserのageの更新を受けて変数logoの値が変わります。
これによってlogoを参照しているところが切り替わり、最終的には画像が切り替わるというピタゴラスイッチ的な感じです。
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Transformations
import android.arch.lifecycle.ViewModel
class UserViewModel : ViewModel() {
// LiveViewを作成
val user: MutableLiveData<User> by lazy {
MutableLiveData<User>()
}
// 初期値を設定
init{
user.value = User("SUZUKI",45)
}
// ボタンクリック時の値を変更する関数
fun change(){
user.value = User("OHTANI",25)
}
// LiveDataの更新を他のLiveDataの更新に依存させる
val logo: LiveData<LogoMark> = Transformations.map(user) {
when {
user.value!!.age > 40 -> LogoMark.LOGO1
else -> LogoMark.LOGO2
}
}
}
enum class LogoMark {
LOGO1,
LOGO2
}
###MainActivity.kt
アクティビティを見てみると、データの詳細に一切触れず、スッキリしていることがわかります。
ViewModelとDataBindingのインスタンスを作成して、結び付けています。
import android.arch.lifecycle.ViewModelProviders
import android.databinding.DataBindingUtil
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kirin3.jp.viewmodel_livedata_databinding.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModelのインスタンスを作成
val viewModel: UserViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
// DataBindingのインスタンスを作成(onCreateの外でもよい)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// ViewModelのインスタンスを作成を設定
binding.viewModel = viewModel
// ライフサイクル所有者を設定
binding.lifecycleOwner = this
}
}
###activity_main.xml
代わりに大きく変わるのがレイアウトファイルになります。
作法があって<Layout>で囲まれていること、<data>で連携するViewModelを指定することがあります。
また変数を表示するには
android:text="@{viewModel.user.name}"/>
関数を実行するには
android:onClick="@{(v) -> viewModel.change()}"
といった独自の書き方が必要になります。
他にも
android:visibility="@{viewModel.flag ? View.VISIBLE : View.GONE}"
とか
android:transitionName='@{"image_" + id}'
とか、色んな書き方ができますが、まとめてあるサイトが見つからないので、見かけたものはメモしています。
この中でも複雑なのが<ImageView>に設定した
app:logoIcon="@{viewModel.logo}"
です。
これはDataBindingにはBindingAdapterというメソッド群が用意されていて、独自に拡張してレイアウトファイルから呼び出すことができ、その時のレイアウトの挙動を設定できるものです。
logoIconという名前は私が勝手につけたものになります。
<?xml version="1.0" encoding="utf-8"?>
<!-- <layout>で囲まないと駄目 -->
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- どのViewModelと結び付けるか宣言 -->
<data>
<variable
name="viewModel"
type="kirin3.jp.viewmodel_livedata_databinding.UserViewModel"/>
<import type="android.view.View"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/text1"
android:layout_margin="10dp"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.user.name}"/>
<TextView
android:id="@+id/text2"
android:layout_margin="10dp"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.user.age)}"/>
</LinearLayout>
<ImageView
android:id="@+id/imageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="60dp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:logoIcon="@{viewModel.logo}"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
android:text="Change"
android:onClick="@{(v) -> viewModel.change()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
###BindingAdapters.kt
BindingAdapterの設定です。
ここではLiveViewで設定した変数logoを受け取って、表示する画像を選択しています。
他にもBindingAdapterはたくさんあり、Viewに様々な変更を行うことができます。
adapters一覧
import android.databinding.BindingAdapter
import android.support.v4.content.ContextCompat
import android.widget.ImageView
@BindingAdapter("app:logoIcon")
fun logoIcon(view: ImageView, logo: LogoMark) {
when(logo) {
LogoMark.LOGO1 -> {
view.setImageDrawable(ContextCompat.getDrawable(view.context, R.drawable.bat))
}
else -> {
view.setImageDrawable(ContextCompat.getDrawable(view.context, R.drawable.ball))
}
}
}
CHANGEボタンを押すと
情報が切り替わり、年齢に応じて画像も切り替わる。
横にしてアクティビティが再起動してもViewModelの情報が生きていてDatabindingで即座に反映
#〇まとめ
これで、ViewModel,LiveData,DataBindingのAndroid MVVM三種の神器のまとめができたかと思います。
次まとめるのはDagger2かなぁ。