みなさまこんにちは〜!
メモリアインクのすだです。
本日は、
KotlinのAdapterやViewHolderを使って
複雑なリスト表示を作成する方法を解説していきます!
この記事を読んでわかること…
・RecyclerViewについて
・AdapterとViewHolderの基本概念
・リッチなリスト表示の作り方
ListViewやRecyclerViewの基本的な使い方につきましては、以下の記事でご紹介しています!
こちらもぜひご覧ください!
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
RecyclerViewとは?
RecyclerViewは、アプリを構成する重要な部品の一つで、
Androidで大量のデータを効率よくリスト表示するために使われます。
簡単なリスト表示には従来のListViewでも対応できますが、RecyclerViewは柔軟性が高く、カスタマイズが容易なため、より複雑でリッチなリスト表示を実現したい場合に最適です。
例えば、異なるアイテムレイアウトを混在させたり、
スワイプやドラッグの操作を組み込むといった高度な機能も簡単に実装できます。
AdapterとViewHolderの基本概念
○ Adapter(アダプター)
Adapterは、「データ」と「画面に表示されるアイテム」をつなぐ架け橋のような役割を持つクラスです。
具体的には、リストで表示したいデータの内容を、画面に表示するための「ビュー(表示要素)」に変換して管理します。
つまり、「データを受け取って、それをリスト表示用に加工して並べる役割」を担います。
○ ViewHolder(ビューホルダー)
ViewHolderは、リスト内の1つ1つのアイテムに含まれる「ビューの部品」を記憶しておく役割を持つクラスです。
例えば、リストの1行に「アイコン」「タイトル」「説明文」の3つの部品がある場合、それぞれを記憶しておき、再利用します。
つまり、「リストの1行に使われる部品を箱にまとめて管理しているもの」です。
各リストアイテム内の部品(TextViewやImageViewなど)を覚えておき、リストをスクロールしたときに使い回すことで表示を高速化します。
リッチなリスト表示を作ろう
今回は、1つのリストに「垂直方向のリスト」「水平方向のリスト」を差し込む形の複雑な構成を作成してみます。
↑イメージとしてはこんな感じ。
リスト上部には垂直方向のリストが一行ずつ並び、その下の一行に水平方向のリストを挿入します。
1. レイアウトファイルを用意
まずは下記のレイアウトファイルを用意します。
①リストの土台となるRecycleView
②「垂直方向のリスト」のレイアウト
③「水平方向のリスト」の土台となるRecycleView
④「水平方向のリスト」のレイアウト
①リストの土台となるRecycleView (activity_main.xml) ▼
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mainRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
②垂直リストアイテムのレイアウト (item_vertical.xml) ▼
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/textVertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp" />
</LinearLayout>
③水平リストアイテムのレイアウト (item_horizontal_list.xml) ▼
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/horizontalRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" />
④水平リスト内のアイテムレイアウト (item_horizontal.xml) ▼
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/textHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
</LinearLayout>
2. データクラスを用意
RecyclerViewでいろんな種類のデータをスッキリ整理して表示するために、データクラスを用意します。
これを使うと、どんなデータが来ても1つの仕組みで対応できるようになります。
sealed class ListItem {
data class VerticalItem(val text: String) : ListItem()
data class HorizontalItem(val items: List<String>) : ListItem()
data class ImageItem(val imageResId: Int) : ListItem()
}
sealed class を使うことで、ListItemが必ずVerticalItem、HorizontalItemのいずれかであることを保証し、
タイプミスや無効なデータ構造を防ぎます。
3. AdapterとViewHolderを用意
続いて、
「全体のリストを管理する用のAdapter」「水平方向のリストを管理する用のAdapter」
そして それぞれにViewHolderを用意します。
①全体のリストを管理する用のAdapter ▼
class MainAdapter(private val items: List<ListItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
const val VIEW_TYPE_VERTICAL = 1
const val VIEW_TYPE_HORIZONTAL = 2
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is ListItem.VerticalItem -> VIEW_TYPE_VERTICAL
is ListItem.HorizontalItem -> VIEW_TYPE_HORIZONTAL
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
VIEW_TYPE_VERTICAL -> {
val view = inflater.inflate(R.layout.item_vertical, parent, false)
VerticalViewHolder(view)
}
VIEW_TYPE_HORIZONTAL -> {
val view = inflater.inflate(R.layout.item_horizontal_list, parent, false)
HorizontalViewHolder(view)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is VerticalViewHolder -> holder.bind((items[position] as ListItem.VerticalItem).text)
is HorizontalViewHolder -> holder.bind((items[position] as ListItem.HorizontalItem).items)
}
}
override fun getItemCount(): Int = items.size
// 垂直方向のリスト表示用 ViewHolder
class VerticalViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.textVertical)
fun bind(text: String) {
textView.text = text
}
}
// 水平方向のリスト表示用 ViewHolder
class HorizontalViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val recyclerView: RecyclerView = itemView.findViewById(R.id.horizontalRecyclerView)
fun bind(items: List<String>) {
recyclerView.layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
recyclerView.adapter = HorizontalAdapter(items)
}
}
}
override fun getItemViewType()
は、
RecyclerViewが「このリストの中で、今表示しようとしているアイテムがどの種類か」を知るための関数です。
RecyclerViewは、リストの中に複数種類のアイテム(例えば縦のリスト、横のリストなど)があるときに、その種類ごとに適切なレイアウトを使い分ける必要があります。
この関数で、その「アイテムの種類」を教えてあげます。
override fun onCreateViewHolder()
は、
RecyclerViewが新しいアイテムの見た目(ビュー)を作るときに呼び出される関数です。
「このアイテムの種類の場合は」、「このviewを使って」、「このHolderにセットするよ」 という指示を書きます。
override fun onBindViewHolder()
は、
RecyclerViewのビューにデータを関連付けるときに呼ばれる関数です。
「このHolderの場合は」、「このデータをセットするよ」 という指示を書きます。
class VerticalViewHolder()は、垂直方向のリスト表示用 ViewHolderです。
ここで実際に、リスト表示の一つ一つの要素を編集してあげましょう。
class HorizontalViewHolder()は、水平方向のリスト表示用 ViewHolderです。
ここではさらに、水平方向のリスト用のAdapterをセットしてあげます。
②水平方向のリストを管理する用のAdapter ▼
class HorizontalAdapter(private val items: List<String>) : RecyclerView.Adapter<HorizontalAdapter.HorizontalItemViewHolder>() {
class HorizontalItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.textHorizontal)
fun bind(text: String) {
textView.text = text
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HorizontalItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_horizontal, parent, false)
return HorizontalItemViewHolder(view)
}
override fun onBindViewHolder(holder: HorizontalItemViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int = items.size
}
4. RecyclerViewにAdapterをセット
最後に、任意の場所で 先ほど作ったRecyclerViewにMainAdapterをセットします。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.mainRecyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
val data = listOf(
ListItem.VerticalItem("Vertical Item 1"),
ListItem.VerticalItem("Vertical Item 2"),
ListItem.VerticalItem("Vertical Item 3"),
ListItem.VerticalItem("Vertical Item 4"),
ListItem.HorizontalItem(listOf("Horizontal 1", "Horizontal 2", "Horizontal 3", "Horizontal 4")),
)
// recyclerViewのadapterにMainAdapterをセット
recyclerView.adapter = MainAdapter(data)
}
}
recyclerView.adapter = MainAdapter(data) を書くと、
- MainAdapter が data を受け取る
→ 表示すべきデータ(例: リストアイテムや画像)をAdapterに渡します。 - RecyclerViewに「MainAdapter」を設定する
→ RecyclerViewは、「どんなデータをどう表示すればいいか」をMainAdapterから教えてもらい、画面にリストを描画します。
それでは、完成形を見てみましょう!
垂直のリストが並んだ後に、垂直リストの要素の一つとして水平リストが挿入された形になっていますね!
まとめ
おつかれさまでした!
水平方向のリスト以外にも、画像やその他要素も一つのリストに挿入できますので
ぜひぜひカスタマイズしてみてくださいね!
技術者としてのキャリアパスを次のレベルへと進めたい皆様、
未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】
自分の市場価値をさらに向上させてみませんか?
それではまた次の記事でお会いしましょう!
コメント