LoginSignup
6
5

More than 3 years have passed since last update.

DataBinding で ListViewを表示

Posted at

はじめに

DataBindingとListViewを使って簡単なリスト表示をやってみたときのメモ。

やったこと

1. DataBindingの有効化

Gradle(app)に設定を追記。

app/build.gradle
android {
    dataBinding {
        enabled true
    }
}

2. Modelの作成

ListViewに表示するデータを保持、整形するためのModelを作成。

model/Person.kt
class Person(
    private val firstName: String,
    private val lastName: String,
    private val age: Int
) {
    fun getFullName() = "$firstName $lastName"
    fun getDisplayAge() = "$age 歳"
}

Untitled Diagram (2).png

3. ViewModelの作成

ModelとViewをつなぎ合わせるためのViewModelを作成。
ほとんどの属性値、メソッドはビューとのバインド用。

viewmodel/MainViewModel.kt
class MainViewModel : ViewModel() {
    private val dataList by lazy { ArrayList<Person>() }
    private val _items = MutableLiveData<List<Person>>()
    val items: LiveData<List<Person>> = _items
    var firstName = MutableLiveData("")
    var lastName = MutableLiveData("")
    var age = MutableLiveData("")
    var isEnabled = MediatorLiveData<Boolean>()

    private fun isAllEntered() =
        firstName.value!!.isNotBlank() &&
        lastName.value!!.isNotBlank() &&
        age.value!!.isNotBlank()

    init {
        isEnabled.addSource(firstName) { isEnabled.postValue(isAllEntered()) }
        isEnabled.addSource(lastName) { isEnabled.postValue(isAllEntered()) }
        isEnabled.addSource(age) { isEnabled.postValue(isAllEntered()) }
    }

    fun onAddClick() {
        val fnVal = firstName.value ?: ""
        val lnVal = lastName.value ?: ""
        val ageVal = if (age.value!!.isEmpty()) 0 else Integer.parseInt(age.value!!)
        dataList.add(Person(fnVal, lnVal, ageVal))
        _items.value = dataList
    }

    fun getOnItemClickListener(): AdapterView.OnItemClickListener {
        return AdapterView.OnItemClickListener { adapterView, view, i, l ->
            dataList.removeAt(i)
            _items.value = dataList
        }
    }
}

4. カスタムバインディングアダプタの作成

カスタムバインディングアダプタを作成することで、
レイアウトファイルに対してカスタム属性とメソッドをバインドさせることができる。

util/DataBindAdapter.kt
@BindingAdapter("onItemClickListener")
fun setOnItemClickListener(listView: ListView, listener: AdapterView.OnItemClickListener) {
    listView.onItemClickListener = listener
}

メソッドに対して@BindingAdapter(属性)を付与することによって
レイアウトファイルに任意の属性が設定でき、バインドができる。

<ListView
app:onItemClickListener="@{viewModel.getOnItemClickListener()}">
</ListView>

属性値であるviewModel.getOnItemClickListener()
setOnItemClickListenerの第2引数に渡される。
ここでは、ListViewに対してクリック時のリスナーを登録。

5. レイアウトファイルの作成

例によってデータバインディング用のレイアウトに変換したのち、
バインドさせたい属性とViewModelの属性値を
@{~}(単方向)、@={~}(双方向)でバインドさせていく。

・メインアクティビティ

layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="viewModel"
            type="com.example.mymvvmlistview.viewmodel.MainViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/etLastName"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="3.0"
                android:text="@={viewModel.lastName}"
                android:ems="10"
                android:hint="苗字"
                android:inputType="textPersonName"
                android:autofillHints="" />

            <EditText
                android:id="@+id/etFirstName"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="3.0"
                android:text="@={viewModel.firstName}"
                android:ems="10"
                android:hint="名前"
                android:inputType="textPersonName"
                android:autofillHints="" />

            <EditText
                android:id="@+id/editText3"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="2"
                android:text="@={viewModel.age}"
                android:ems="10"
                android:hint="年齢"
                android:inputType="number"
                android:autofillHints="" />

            <Button
                android:id="@+id/btAdd"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="2"
                android:onClick="@{() -> viewModel.onAddClick()}"
                android:text="追加"
                android:enabled="@{viewModel.isEnabled()}"/>
        </LinearLayout>
        <ListView
            android:id="@+id/lvItem"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:onItemClickListener="@{viewModel.getOnItemClickListener()}">
        </ListView>
    </LinearLayout>
</layout>

・ListView用のカスタムセル

layout/list_item_cell.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="person"
            type="com.example.mymvvmlistview.model.Person" />
        <variable
            name="viewModel"
            type="com.example.mymvvmlistview.viewmodel.MainViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp">
        <TextView
            android:id="@+id/tvName"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="30dp"
            android:layout_weight="0.7"
            android:gravity="center"
            android:textAlignment="center"
            android:textSize="18sp"
            tools:text="@{person.fullName}" />

        <TextView
            android:id="@+id/tvAge"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginEnd="30dp"
            android:layout_weight="0.3"
            android:gravity="center"
            android:textAlignment="center"
            android:textSize="18sp"
            tools:text="@{person.displayAge}" />
    </LinearLayout>
</layout>

6. ListViewのアダプタを作成

ListViewで使用するカスタムアダプタを作成する。

MyCustomAdapter.kt
class MyCustomAdapter(
    private val context: Context,
    private var dataList: List<Person>
) : BaseAdapter() {
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val binding =
            if (convertView == null) {
                val inflater = LayoutInflater.from(context)
                // https://developer.android.com/topic/libraries/data-binding/expressions?hl=ja
                val tBinding: ListItemCellBinding =
                    ListItemCellBinding.inflate(inflater, parent, false)
                // tagにインスタンスをセット(convertViewが存在する場合に使い回すため)
                tBinding.root.tag = tBinding
                tBinding
            } else {
                convertView.tag as ListItemCellBinding
            }

        binding.person = getItem(position) as Person
        // 即時バインド
        binding.executePendingBindings()

        return binding.root
    }

    fun updateItems(newItems: List<Person>) {
        dataList = newItems
        // 変更の通知
        notifyDataSetChanged()
    }

    override fun getItem(position: Int): Any {
        return dataList[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getCount(): Int {
        return dataList.size
    }
}

7. Activityの修正

ui/MainActivity.kt
class MainActivity : AppCompatActivity() {
    private val mViewModel: MainViewModel by lazy {
        ViewModelProvider(this).get(MainViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
            this, R.layout.activity_main
        )

        with(binding) {
            viewModel = mViewModel
            lvItem.adapter = MyCustomAdapter(applicationContext, ArrayList(0))
            lifecycleOwner = this@MainActivity
            (viewModel as MainViewModel).items.observe(this@MainActivity, Observer { item ->
                val adapter = lvItem.adapter as MyCustomAdapter
                adapter.updateItems(item)
            })
        }
    }
}

8. 動作確認

双方向バインディングにより、画面上の「苗字」「名前」「年齢」の値を監視し、
なにかしらの入力値が入るまで追加ボタンを押すことができないようにした。

スクリーンショット 2020-04-04 16.48.12.png

スクリーンショット 2020-04-04 17.30.28.png

dg4r2-hb6b9.gif

参考

先人の知恵をお借りしました。
ありがとうございます。:bow_tone1:
Android DataBinding 〜ListViewで使う〜
【Android】LiveData+DataBinding+ViewModelでListView作成
How can I use Android DataBinding in a listview and still use a ViewHolder pattern?

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5