どっこと備忘録群

アウトプットしないとインプットできない私が Androidアプリ開発をメインとした備忘録を載せています。

RecyclerViewを扱う

RecyclerViewを使ってコンテンツ一覧を表示したい

実装手順

  1. RecyclerViewを配置
  2. LayoutManagerを設定
  3. RecyclerView.Adapterを継承したCustomAdapterクラスを実装
  4. RecyclerView.ViewHolderを継承したCustomViewHolderクラスを実装
  5. CustomViewHolderCustomAdapterを繋ぎ込み
  6. RecyclerViewCustomAdapterクラスを繋ぎ込み

RecyclerViewを配置

RecyclerViewを画面レイアウトに配置する。ListViewを使ったことがある人はそれと同じ要領。

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

LayoutManagerを設定

LayoutManagerを設定するす。

LayoutManagerRecyclerViewに対して、コンテンツをどのように配置するかを指定するマネージャークラスで、例えば以下のものがある。

  • 縦横にコンテンツを配置するLinearLayoutManager
  • グリッドにコンテンツを配置するGridLayoutManager
  • キーワードなどを柔軟に並べるFlexBoxLayoutManager

今回はLinearLayoutManagerを使って、縦並びのコンテンツ配置を指定する。 指定方法は2つ。

コードで設定する方法

コードからLayoutManagerを設定する方法。 LinearLayoutManagerのインスタンスを生成し、RecyclerView.setLayoutManager()で設定する。

val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)

レイアウトで設定する方法

レイアウトXMLから設定する方法。app:layoutManagerで設定するだけ。

<androidx.recyclerview.widget.RecyclerView 
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

RecyclerView.Adapterを継承したCustomAdapterクラスを実装

RecyclerViewで表示する要素を管理するAdapterクラスを実装する。 RecyclerView.Adapterを継承した、CustomAdapterクラスを作成。

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        TODO("このあと実装")
    }

    override fun getItemCount(): Int {
        TODO("このあと実装")
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        TODO("このあと実装")
    }
}

RecyclerView.ViewHolderを継承したCustomViewHolderクラスを実装

RecyclerViewで表示するコンテンツ要素部分を実装する。 RecyclerView.ViewHolderを継承した、CustomViewHolderクラスを作成する。

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

レイアウトファイルを作成する。ここではファイル名をview_holder_sample.xmlとします。 これは後でCustomViewHolderで指定する。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/text_view"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:gravity="center"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

CustomViewHolderの実装

RecyclerView.ViewHolderを継承したCustomViewHolderクラスを実装する。 サンプルとして、CustomViewHolderに表示用のTextViewへの参照を持たせておく。

class CustomViewHolder(itemView: View): ViewHolder(itemView) {
    val textView: TextView = itemView.findViewById(R.id.text_view)
}

CustomAdapterCustomViewHolderを繋ぎ込み

CustomAdapterCustomViewHolderを繋ぎ込んでいく。

繋ぎ込み1:onCreateViewCustomViewHolderを生成

onCreateViewHolderで表示したいCustomViewHolderを生成し、返す。

ここではあくまで生成だけで、実際に表示する要素を当てはめる箇所はonBindViewHolderで実装する。

以下は表示件数を10件とした時の例。

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // onCreateViewHolderで、CustomViewHolderを生成する。
        return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_sample, parent, false))
    }

    override fun getItemCount(): Int {
        return 10 // 今回はサンプルのため、10件固定で表示する。
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        TODO("このあと実装")
    }
}

繋ぎ込み2:onBindViewHolderで表示内容を当て込み

CustomViewHolderTextViewを参照し、onBindViewHolderで表示内容を当てはめていく。

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // onCreateViewHolderで、CustomViewHolderを生成する。
        return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_sample, parent, false))
    }

    override fun getItemCount(): Int {
        return 10
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        if (holder is CustomViewHolder) {
            holder.textView.text = position.toString()
        }
    }
}

RecyclerViewCustomAdapterを繋ぎ込み

画面レイアウトに配置したRecyclerViewCustomAdapterを繋ぎこむ。

val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = CustomAdapter()

RecyclerView の表示要素を動的に制御したい

データベースやAPIから取得したデータを一覧に表示する。

実装の手順

  1. CustomAdapter のコンストラクタに表示要素のリストを追加する。
  2. getItemCount() メソッドを修正する。
  3. onBindViewHolder() で View をカスタマイズする。

1. CustomAdapter のコンストラクタに表示要素のリストを追加する

表示要素を管理するリストを CustomAdapter に渡せるようにする。 今回は、ToDoリストを想定して以下の Todo クラスを用意します。

data class Todo(val id: Int, val title: String)

このデータのリストを CustomAdapter のコンストラクタで受け取る。

class CustomAdapter(private val list: List<Todo>) : RecyclerView.Adapter<ViewHolder>()

2. getItemCount() メソッドを修正する

表示要素の数を設定する。 これは、コンストラクタで受け取ったリストの要素数と同じになります。

override fun getItemCount(): Int {
    return list.size
}

3. onBindViewHolder() で View をカスタマイズする

ViewHolder に表示要素を反映する。

onBindViewHolder は表示要素の位置を引数として受け取るため、これを使ってリストの要素にアクセスする。

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    if (holder is CustomViewHolder) {
        val todo = list[position]
        holder.textView.text = todo.title
    }
}

クリックしたらその要素に対応する画面を表示したい

実装手順

  1. タップ時のインターフェースを用意する。
  2. onBindViewHolder() で View がタップされたときの処理を追加する。
  3. CustomAdapter のインスタンス生成時にリスナーを設定する。

1. タップ時のインターフェースを用意する

Viewがタップされたときに、ActivityFragment に必要な情報を渡すためのインターフェースを定義する。 ここでは、クリックされた Todo データを渡す。

interface ToDoClickListener {
    fun onClickToDo(todo: Todo)
}

このインターフェースを CustomAdapter のコンストラクタに追加する。

class CustomAdapter(
    private val list: List<Todo>,
    private val listener: ToDoClickListener
) : RecyclerView.Adapter<ViewHolder>() {

2. onBindViewHolder() で View がタップされたときの処理を追加する

View がタップされたときの処理を実装する。 Todo データへのアクセスが必要になるため、この処理も onBindViewHolder に記述する。

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    if (holder is CustomViewHolder) {
        val todo = list[position]
        holder.textView.text = todo.title
        holder.itemView.setOnClickListener {
            listener.onClickToDo(todo) // 要素がタップされたら、それに対応するTodoデータを渡す
        }
    }
}

3. CustomAdapter のインスタンス生成時にリスナーを設定する

CustomAdapter のインスタンスを生成する際に、先ほど定義したインターフェースの実装を渡す。 以下は、無名オブジェクト生成し渡す例。

val adapter = CustomAdapter(listOf(
    Todo(0, "買い物"),
    Todo(1, "掃除"),
    Todo(2, "洗濯")
), object : ToDoClickListener {
    override fun onClickToDo(todo: Todo) {
        // ここに画面に反映させる処理や、画面遷移などの必要な処理を実装する
    }
})

ドラッグ&ドロップ機能を実装したい

ItemTouchHelperを使う。

実装手順

  1. ItemTouchHelperインスタンスを実装する。
  2. ItemTouchHelperRecyclerViewにアタッチする。

1. ItemTouchHelperインスタンスを実装する

ItemTouchHelperは、ドラッグ&ドロップやスワイプといったユーザーの操作を処理するためのヘルパークラス。 ドラッグ&ドロップ機能を実装するため、SimpleCallbackの特定のメソッドをオーバーライドする。

以下サンプル。

ItemTouchHelper(object : SimpleCallback(UP or DOWN, 0) {
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        // コンテンツがドラッグされようとしている
        val fromPosition = viewHolder.adapterPosition
        val toPosition = target.adapterPosition
        recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition)
        // ドラッグしてOKなら true を、NGなら false を返す
        return true
    }

    override fun isLongPressDragEnabled(): Boolean {
        // 長押しされた。ドラッグ&ドロップを有効にする
        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        // この実装では呼ばれない
    }

    override fun onMoved(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        fromPos: Int,
        target: RecyclerView.ViewHolder,
        toPos: Int,
        x: Int,
        y: Int
    ) {
        super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
        // コンテンツが実際にドラッグ&ドロップされた
    }
})

補足

  • isLongPressDragEnabled():ユーザーがアイテムを長押ししたときにドラッグ&ドロップを開始するかどうかの制御。true:有効
  • onMove(): ドラッグ中に呼び出されるドラッグを許可するかどうかの制御。並び替えを許可しないアイテムがある場合あればfalseを返すことでドラックを無効にできる。
  • onMoved(): ドラッグ&ドロップが完了したときに呼び出ばれる。ここでデータリストを更新する。

2. ItemTouchHelperをRecyclerViewにアタッチする

実装したItemTouchHelperRecyclerViewにアタッチする。

itemTouchHelper.attachToRecyclerView(recyclerView)

参考

最終更新: 2025.9.8