こんにちは。仙台オフィスでスマホアプリの開発などを担当している、はんだです。最近はスマホアプリだけでなく、Android TVアプリも触らせてもらってます。
VideoMarketのAndroid TVアプリはLeanbackを利用して作成していますが、表示している作品一覧から複数の作品を選択させたいことがありました。 具体的には、表示されているお気に入り一覧から、選択した作品をお気に入りから除外する機能を実装するために必要でした。
このためにLeanback画面にチェックボックスを持たせたのですが、実装の際に色々と詰まることがあったので、今回はそれについて書きたいと思います。
はじめに
Android TVの公式サンプルコードの中にReferenceAppKotlinというものがあり、BrowseSupportFragment
を使った画面が含まれているので、これをベースに説明したいと思います。
こちらのブラウズ画面に、テキストだけを表示しているカードがあるのですが、これをチェックボックス付きのカードに変更したいと思います。
チェックボックスの表示
チェックボックスを表示すること自体は簡単です。presenter_menu_item.xml
を修正して、チェックボックスを含むようにします。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="@dimen/menu_item_width" android:layout_height="@dimen/menu_item_height" android:focusable="true" android:focusableInTouchMode="true" > <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/title" android:layout_width="@dimen/menu_item_width" android:layout_height="@dimen/menu_item_height" android:gravity="center" android:background="@color/gray_dark" android:textColor="@color/screen_white" /> <CheckBox android:id="@+id/checkbox" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout>
こちらのCustomMenuItemPresenter
ですが、Firebaseとの連携用のカードが表示されています。この記事では使わないので、BrowseFragment
のonCreate(savedInstanceState: Bundle?)
内のviewModel.isSignedIn.observe
部分をコメントアウトします。
代わりに、チェックボックス付きのカードと、チェックされたアイテムを保持するリストを作ります。
private val checkedItems = mutableListOf<String>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) listOf( BrowseCustomMenu( "複数選択デモ", listOf( BrowseCustomMenu.MenuItem("アイテム1") {}, BrowseCustomMenu.MenuItem("アイテム2") {}, BrowseCustomMenu.MenuItem("アイテム3") {}, BrowseCustomMenu.MenuItem("アイテム4") {}, BrowseCustomMenu.MenuItem("アイテム5") {}, ) ) ).also { viewModel.customMenuItems.postValue(it) } }
レイアウトファイルの構成を変えたのでCustomMenuItemPresenter
を修正する必要があります。
override fun onBindViewHolder( viewHolder: ViewHolder, item: Any? ) { checkNotNull(item) val binding = PresenterMenuItemBinding.bind(viewHolder.view) // binding.root.text = item.toString() コメントアウト val menuItem = item as? BrowseCustomMenu.MenuItem binding.title.text = item.toString() }
ここまでで、チェックボックスのついたカードの表示までできているはずです。
チェックをつける
さて、先ほどの修正でチェックボックスは表示されるようになりますが、カードを選択してもチェックボックスにチェックがつきません。
これについては、BrowseSupportFragment
のOnItemViewClickedListener
で選択されたアイテムのViewHolderが取得できるので、その中でチェックをトグルさせます。
サンプルコードのBrowseFragment
に既にsetOnItemViewClickedListener
があるので、ここに追記します。先ほど用意したcheckedItems
の中身もここでセットします。
setOnItemViewClickedListener { viewHolder, item, _, _ -> // viewHolderを追記 when (item) { is Video -> (略) is BrowseCustomMenu.MenuItem -> { viewHolder.view.findViewById<CheckBox>(R.id.checkbox).also { it.isChecked = !it.isChecked if (it.isChecked) { checkedItems.add(item.toString()) } else { checkedItems.remove(item.toString()) } } } } }
これで、カードを選択するとチェックがトグルするようになります。
さて、ここまででチェック機能はできたのですが、一つ問題があります。それはカードが再描画された際にチェックが消えたりずれたりすることです。カードにチェックをした後、ブラウズ画面を上下にスクロールさせると再現すると思います。
Bind時にチェック済みかどうか判定する
再描画されてもチェックが消えたりずれたりしないように、CustomMenuItemPresenter
のonBindViewHolder
内でカードがチェック済みかどうか判定し、それに応じてチェックをつけるようにします。
val binding = PresenterMenuItemBinding.bind(viewHolder.view) // binding.root.text = item.toString() コメントアウト val menuItem = item as? BrowseCustomMenu.MenuItem binding.title.text = item.toString() // コンストラクタでBrowseFragmentのcheckedItemListを渡す binding.checkbox.isChecked = checkedItemList.contains(menuItem.toString()) // 追加箇所
この例ではCustomMenuItemPresenter
のコンストラクタで先ほどのcheckedItemList
を受け取っています。
これで、上下にスクロールさせてもチェックボックスの状態が変わらなくなります。
まとめ
以上、Leanbackの画面でチェックボックスを使う際の手順を書かせていただきました。まとめると
- レイアウトファイルに
CheckBox
を追加する BrowseSupportFragment
のsetOnItemViewClickedListener
からviewHolderを取得し、チェックを付け替える- プレゼンターの
onBindViewHolder
で、チェック済みかどうか判定する
という流れになります。また、本記事ではBrowseSupportFragment
を例に説明しましたが、VerticalGridSupportFragment
でも同様に動きます。
今回は以上になります。ここまでお読みいただきありがとうございました。