DataBindingでRecyclerView.Adapterルーチンを取り除く







RecyclerViewは、ほとんどすべてのアプリケーションのメインUI要素です。 アダプターとViewHoldersの作成は、多くの場合日常的すぎて、十分な定型コードが含まれています。 この記事では、DataBindingとMVVMパターンを使用して抽象アダプターを作成し、ViewHolders、inflate、手動バインディング、その他のルーチンを完全に忘れることができる方法を示します。







ViewHolder



個々のビューとデータバインディングへのリンクを保存するために、テーブル内のセルのタイプごとに個別のViewHolderを記述することに慣れています。







DataBindingは、通常ViewHoldersで記述するコードをオンザフライで生成するため、それらの必要はなく、完成したバインディングオブジェクトを格納する1つの実装を簡単に使用できます。







abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val binding = DataBindingUtil.bind<ViewDataBinding>(view) } }
      
      





ViewDataBindingは、生成されたすべてのDataBindingクラスの基本的な抽象クラスであり、bindメソッドのテンプレートパラメーターを渡しますが、DataBindingUtil自体は、使用するレイアウトと結果として使用する実装を理解します。







ViewModelAdapter



ViewHolderを扱ったので、最終的に基本アダプターから何を望むかを決定する必要があります。 MVVMアーキテクチャ内のアダプターに必要なのは、オブジェクトのリスト(ViewModel)を提供し、このクラスのリスト内のデータに使用するマークアップを教えて、これに必要なロジックをまったく気にしないことです。







DataBindingはデータバインディングのロジックを使用しますが、これはまったく別の記事であり、インターネット上にはすでに十分な記事があります。







アダプターの構成のロジックを記述しましょう。







 abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { data class CellInfo(val layoutId: Int, val bindingId: Int) protected val items = LinkedList<Any>() private val cellMap = Hashtable<Class<out Any>, CellInfo>() protected fun cell(clazz: Class<out Any>, @LayoutRes layoutId: Int, bindingId: Int) { cellMap[clazz] = CellInfo(layoutId, bindingId) } protected fun getCellInfo(viewModel: Any): CellInfo { cellMap.entries .filter { it.key == viewModel.javaClass } .first { return it.value } throw Exception("Cell info for class ${viewModel.javaClass.name} not found.") } }
      
      





テーブルオブジェクトのクラスごとに、layoutIdとbindingIdのペアを格納します。









RecyclerView.Adapterの抽象関数を実装するためにのみ残ります:







 abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { override fun getItemCount(): Int = items.size override fun getItemViewType(position: Int): Int { return getCellInfo(items[position]).layoutId } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent?.context) val view = inflater.inflate(viewType, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder?, position: Int) { if (holder != null) { val cellInfo = getCellInfo(items[position]) if (cellInfo.bindingId != 0) holder.binding.setVariable(cellInfo.bindingId, items[position]) } } }
      
      







これについては、ViewModelAdapterの基本的なロジック全体について説明しますが、1つの問題が残っています-セルのクリックの処理です。 通常、このロジックは「アクティビティ」で説明されていますが、ロジックを階層に変換することは好きではありません。そうしないとできない限り、アダプターに直接実装しますが、必要に応じて実装できます。







クリック処理を実装するには、sharedObjectなどの概念をViewModelAdapterに追加します。ViewModelAdapterは、テーブルのすべてのセルにバインドするオブジェクトです(指定されたbindingIDのマークアップに何も見つからない場合は不要です)。







 abstract class ViewModelAdapter : RecyclerView.Adapter<ViewModelAdapter.ViewHolder>() { private val sharedObjects = Hashtable<Int, Any>() protected fun sharedObject(sharedObject: Any, bindingId: Int) { sharedObjects[bindingId] = sharedObject } override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent?.context) val view = inflater.inflate(viewType, parent, false) val viewHolder = ViewHolder(view) sharedObjects.forEach { viewHolder.binding.setVariable(it.key, it.value) } return viewHolder } }
      
      





それでは、最終的にどのように機能するかを見てみましょう。

例として、サイドメニューにアダプターを実装しました(Material Designから離れる必要がない場合は、標準ライブラリのNavigationViewを使用します)。







 object NavigationAdapter : ViewModelAdapter() { init { cell(NavigationHeaderViewModel::class.java, R.layout.cell_navigation_header, BR.vm) cell(NavigationItemViewModel::class.java, R.layout.cell_navigation_item, BR.vm) cell(NavigationSubheaderViewModel::class.java, R.layout.cell_navigation_subheader, BR.vm) sharedObject(this, BR.adapter) } override fun reload(refreshLayout: SwipeRefreshLayout?) { items.clear() items.add(NavigationHeaderViewModel) items.add(NavigationItemViewModel(R.drawable.ic_inbox_black_24dp, "Inbox")) items.add(NavigationItemViewModel(R.drawable.ic_star_black_24dp, "Starred")) items.add(NavigationItemViewModel(R.drawable.ic_send_black_24dp, "Sent mail")) items.add(NavigationItemViewModel(R.drawable.ic_drafts_black_24dp, "Drafts")) items.add(NavigationSubheaderViewModel("Subheader")) items.add(NavigationItemViewModel(R.drawable.ic_mail_black_24dp, "All mail")) items.add(NavigationItemViewModel(R.drawable.ic_delete_black_24dp, "Trash")) items.add(NavigationItemViewModel(R.drawable.ic_report_black_24dp, "Spam")) notifyDataSetChanged() } fun itemSelected(view: View, model: NavigationItemViewModel) { Toast.makeText(view.context, "${model.title} selected!", Toast.LENGTH_SHORT).show() } }
      
      





レイアウトの例として:cell_navigation_item.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="vm" type="com.github.akvast.mvvm.ui.vm.NavigationItemViewModel" /> <variable name="adapter" type="com.github.akvast.mvvm.ui.adapter.NavigationAdapter" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" android:onClick="@{v -> adapter.itemSelected(v, vm)}"> <ImageView android:layout_width="24dp" android:layout_height="24dp" android:layout_gravity="center_vertical" android:layout_marginLeft="16dp" android:src="@{vm.icon}" android:tint="@{@color/grey_600}" /> <TextView style="@style/TextAppearance.AppCompat.Body2" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:minHeight="48dp" android:paddingBottom="12dp" android:paddingLeft="72dp" android:paddingRight="16dp" android:paddingTop="12dp" android:text="@{vm.title}" tools:text="Item title" /> </FrameLayout> </layout>
      
      





ご覧のとおり、すべてが非常に単純であり、不必要なロジックはありません。 1つの関数を呼び出すことで、好きなだけセル型を宣言できます。 UIのデータを手動でバインドすることを忘れることができます。







このアダプターは、いくつかの大規模プロジェクトで6か月間の戦闘テストに成功しています。







コメントでご質問にお答えさせていただきます。







便利なリンク



GitHubの完全なコードとサンプルプロジェクト

Javaで記述されたViewModelAdapter

公式のDataBindingドキュメント

KotlinでのDataBindingおよびその他のライブラリの使用の構成








All Articles