本稿ではData bindingの実装手順を備忘録として残しておきます。
SeekBarとViewModel、TextViewを結びつけて、SeekBarの変化を画面表示に反映する機能を実装します。
使用する主な道具としては、SeekBar
・Data Binding
・LiveData
になります。
Githubに実装がおいてあります。説明上コードを適宜省略していますので、詳しくはこちらのレポジトリを参照してください。また、説明と対応するコミットへのリンクを掲載していますので、実装を追記する位置はこちらを参照してください。
環境
macOS 10.15.3
Android Studio 3.5.1
Kotlin plugin 1.3.61-release-Studio3.5-1
実装手順
以降、package名を example.android.android_data_binding_sampleとして進めます。
ビルド設定
新規プロジェクトをEmpty Activityを選んで作ったら、まず、app/build.gradleでData Binding用のビルド設定を行います。
android {
...
dataBinding {
enabled = true
}
}
ViewModel
続いて、ViewModelを実装します。
class MainViewModel : ViewModel() {
/**
* シークバーの値を格納する
* */
private val _seekBarValue = MutableLiveData(50)
/**
* シークバーの値を表示用に整形した文字列
* */
val seekBarValueString: LiveData<String> = Transformations.map(_seekBarValue) {
"value: $it"
}
/**
* シークバーの値が変化した時に呼び出される
* */
fun onSeekBarValueChanged(value: Int) {
_seekBarValue.postValue(value)
}
}
MutableLiveDataのイニシャライザなどでビルドエラーになる場合は、app/build.gradleにライブラリの依存関係を追加します。
dependencies {
...
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
...
}
レイアウトファイル
次に、activity_main.xmlを編集します。
<layout>
タグで既存の記述を囲みます。
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
先程実装したViewModelの宣言を行います。
<layout>
<data>
<variable
name="viewModel"
type="example.android.android_data_binding_sample.MainViewModel" />
</data>
...
</layout>
最後に、SeekBarと、SeekBarの値を表示するTextViewを宣言します。ついでにConstraintLayoutからLinearLayoutに変更します。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
...
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:progress="50" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.seekBarValueString}"
android:textAlignment="center" />
</LinearLayout>
</layout>
ここでのポイントは、TextViewのtext
属性をviewModelのseeekBarValueString
プロパティに紐づけている箇所です。
<TextView
android:text="@{viewModel.seekBarValueString}"
/>
TextViewのtext
を直接ViewModelのLiveData<String>
型のプロパティに紐付けています。
MainActivity
MainActivityの編集に移ります。
まず、ViewModelのプロパティを追加します。
class MainActivity : AppCompatActivity() {
private val viewModel by lazy {
ViewModelProvider(this).get(MainViewModel::class.java)
}
...
この際、ViewModelProviderのインポートを追加します。
次に、バインディングオブジェクトのプロパティをMainActivityに追加します。自動生成されるこの型の名称は、対応するレイアウトファイルのファイル名をパスカルケースにし、 Bindingを末尾につけたものになります。
例:レイアウトファイル名がactivity_foo.xml
ならActivityFooBinding
になる
import example.android.android_data_binding_sample.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
...
private lateinit var binding: ActivityMainBinding
...
続いて、onCreate()
内に、data bindingの設定を追記していきます。
プロジェクト生成時にデフォルトで書かれるsetContentView()
をDataBindingUtilのそれに差し替え、バインディングクラスをインスタンス化します。
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
...
この際に、DataBindingUtil
のインポート文も追加します。
LiveDataオブジェクトをバインディングクラスとともに使用するため、MainActivityをライフサイクル所有者に指定します。
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this//追記
...
続いて、先程定義したMainViewModelをバインディングクラスと共に使用するため、バインディングクラスのプロパティに指定します。
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.viewModel = viewModel//追記
...
最後に、SeekBarの変化への対応を設定します。
...
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
this@MainActivity.viewModel.onSeekBarValueChanged(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
}
これで実行してみます。
シークバーを動かすと、TextViewの値が変わるのが確認できたでしょうか。
まとめ
ViewModelの(LiveDataの)プロパティをレイアウトファイル上のTextViewと紐付け、さらにSeekBarの変化をViewModelに伝える設定を行いました。
その結果、SeekBarの変化に応じてTextViewが変化するようになりました。
ユーザーがSeekBarを操作すると、次のようにメソッド呼び出し・プロパティの変化が生じます:
(MainActivity)
SeekBar.OnSeekBarChangeListener.onProgressChanged()の呼び出し
(MainViewModel)
viewModel.onSeekBarValueChanged()の呼び出し
_seekBarValue.postValue()の呼び出し
seekBarValueStringのvalueプロパティが更新される
(activity_main.xml)
seekBarValueStringに紐付けられたTextViewのtextプロパティが更新される
以上になります。
ここまでお読みいただきありがとうございます。お疲れ様でした。