ラベル RecyclerView の投稿を表示しています。 すべての投稿を表示
ラベル RecyclerView の投稿を表示しています。 すべての投稿を表示

2024年6月29日土曜日

Android RecyclerView コンテンツをドラッグ&ドロップする実装を解説

どうも。どっことです。今回はRecyclerViewを使ったコンテンツのドラッグ&ドロップの実装方法について解説します。

ドラッグ&ドロップ機能を持ったRecyclerView

RecyclerViewの一番の利点は、拡張性に富んでいることだと思います。RecyclerViewが登場する以前では、ListViewがシンプルにコンテンツ一覧を表示する機能を担っていました。実装難易度もそこまで高くなく、単に一覧表示をさせるものであれば、今でもこれで十分です。しかし、より便利に、より良いUXを提供することを考えるとListViewではもうまかないきれないです。RecyclerViewは、拡張しやすいコンポーネントとして出てきたもので、今回のような機能はまさにRecyclerViewが活きるパターンの一つだといえます。

RecyclerViewにドラッグ&ドロップを実装する

それでは実装手順を解説します。

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

順番に解説していきます。

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

まずはItemTouchHelperを実装します。

ItemTouchHelperは、今回のドラッグ&ドロップをはじめとするユーザ操作を司るヘルパークラスとなっています。今回のドラッグ&ドロップでは、SimpleCallBack#onMove,SimpleCallBack#onMoved, SimpleCallBack#isLongPressDragEnabledのコールバックに追加実装が必要となってきます。

早速サンプルを載せます。

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)
        // コンテンツがドラッグ&ドロップされた。
    }
})

まずSimpleCallBack#isLongPressDragEnabledでは、ユーザがコンテンツを長押しした時にドラッグ&ドロップを開始するかどうかを返します。trueなら開始する、falseならしないということになります。今回は簡単のため、trueを返すようにしています。

onMoveコールバックでは、ユーザのドラッグによりコンテンツをドラッグさせても問題ないかをチェックします。同じRecyclerView内に複数の種類のコンテンツが表示されている場合=並び替えをさせたくない場合はここでfalseを返すことで、そのドラッグ&ドロップを無効にすることができます。

そしてonMovedコールバックは、実際にコンテンツのドラッグ&ドロップが行われたタイミングでここの処理が呼び出されます。このタイミングで、コンテンツの要素を渡された引数の内容をもとに更新してあげるのがよいでしょう。

ItemTouchHelperをRecyclerViewにアタッチする

あとは実装したItemTouchHelperRecyclerViewにアタッチするだけです。Activity#onCreatedFragment#onViewCreatedあたりで以下を読んであげれば問題ありません。

itemTouchHelper.attachToRecyclerView(recyclerView)

まとめ

今回はRecyclerViewを使ったコンテンツのドラッグ&ドロップの実装方法について解説しました。RecyclerViewは拡張性が高く、今回以外の拡張も豊富に取り揃えているので、今後もテーマにしていきたいと思います。

参考

2024年6月23日日曜日

Android RecyclerViewを使ったコンテンツ一覧の実装方法を解説 その2

どうも。どっことです。今回はRecyclerViewをより実用的に利用する場合の実装方法について解説します。

RecyclerViewでのコンテンツ一覧のカスタマイズ

前回の解説では、RecyclerViewの必要最低限の実装について解説しました。しかしこれだけでは実用性に欠いており、より実用に近いケースが必要なことを課題として挙げていました。

今回は前回に挙げた課題、つまり以下の内容を解説していきたいと思います。

  • 表示要素を動的にする。
    • データベースやAPIから取得した情報を一覧に表示するようなケースを解説します。
  • クリックしたらその要素に対応する画面を表示したい。(クリックリスナーの制御)
    • RecyclerViewで表示しているコンテンツに対応するページに遷移させる実装を解説します。

それぞれ解説します。

RecyclerViewの表示要素を動的に制御する

実装の手順は以下の通りです。

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

順番に解説していきます。

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

CustomAdapterに、表示要素を管理するリストを渡します。今回はToDoリストを意識して以下のTodoクラスをあらかじめ用意します。

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

このデータのリストをCustomAdapterのコンストラクタで渡します。

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

getCount()メソッドを修正する

表示要素の数を設定します。表示要素は前項のリストの要素数と同じになるはずです。

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

表示要素の配列を使って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. Viewがタップされた時の処理を追加する。
  3. CustomAdapter内でViewがタップされた時の処理を設定する。

こちらも順に解説します。

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

Viewがタップされた時に、ActivityFragmentに必要な情報を渡すためのインターフェースを実装します。Todoクラスのデータを渡すように定義します。

interface ToDoClickListener {
    fun onClickToDo(todo: Todo)
}

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

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

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データを渡す
        }
    }
}

CustomAdapter内でViewがタップされた時の処理を設定する

Viewがタップされた時に、インターフェースから渡された情報を使って必要な処理をするよう実装します。例えば、渡されたTodoデータを渡す形で、詳細画面に実装するなどでしょうか。インターフェースの実装はActivityFragmentに実装しても良いですし、実装した無名オブジェクトを渡しても問題ありません。今回は無名オブジェクトを渡します。


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

まとめ

今回はRecyclerViewをより実用的に利用する場合の実装方法について解説しました。RecyclerViewによるコンテンツ表示はカスタマイズ性が高く、さまざまな利用パターンに当てはめられるので、今後もRecyclerViewによるコンテンツ表示について解説していきます。

2024年5月31日金曜日

Android RecyclerViewを使ったコンテンツ一覧の実装方法を解説

どうも。どっことです。今回はRecyclerViewを使ったコンテンツ一覧の表示方法を解説します。

RecyclerViewを使ったコンテンツ一覧の表示

コンテンツの一覧表示する実装方法はいくつかありますが、よくある一般的なものはListViewRecyclerViewです。特にRecyclerViewはリスト表示だけでなく、グリッド表示や横スクロールコンテンツにもこれ一つで対応できるので、実装パターンさえ理解できていれば非常に融通が利くコンポーネントです。今回は、そんな融通が利くRecyclerViewの実装パターンについて解説します。

RecyclerViewを使ってコンテンツ一覧表示を実装する

シンプルな表示の実装は大きく以下の流れとなります。

  1. RecyclerViewを配置
  2. LayoutManagerを設定
  3. RecyclerView.Adapterを継承したCustomAdapterクラスを実装
  4. RecyclerView.ViewHolderを継承したCustromViewHolderクラスを実装
  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に対して、コンテンツをどのように配置するかを指定してくれるマネージャークラスです。LayoutManagerには、例えば以下があります。

  • 縦横にコンテンツを配置する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("このあと実装")
    }
}

次の項目で、実際に表示するコンテンツ要素を実装するのですが、それができたらこちらのCustomAdapterクラスと繋ぎ込みをしていきます。

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

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

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

レイアウトファイルを作成します。これはあとでCustomViewHolderに渡します。ここではファイル名をview_holder_sample.xmlとしておきます。

<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クラスを実装します。細かい実装は、後述の繋ぎ込みをしながら実装します。

class CustomViewHolder(itemView: View): ViewHolder(itemView) {
    // まずはこれだけ
}

CustomAdapterCustomViewHolderを繋ぎ込み

ここまでできればもうすぐです。実装したCustomAdapterCustomViewHolderを繋ぎ込んでいきましょう。実装が必要な箇所は大きく2箇所、onCreateViewHolderonBindViewHolderです。

繋ぎ込み1: onCreateViewでCustomViewHolderを生成

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: CustomViewHolderでの表示処理を追加実装

先ほど追加したCustomViewHolderに表示用のTextViewへの参照を持たせておきましょう。

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

繋ぎ込み3: 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を繋ぎ込み

あとは、画面レイアウトに配置したRecyclerViewCustomViewHolderを繋ぎこむだけです。

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

実際に実装した結果は以下のように表示されるかと思います。

今回はひとまずこれで完成です。お疲れ様でした。

さて、上記でシンプル(=必要最低限)な実装を解説しましたが、実際にRecyclerViewを利用する場合、以下のような機能も実現したい場合がほとんどだと思います。

  • 表示要素は動的にしたい。
    • 例えばTODOリストなどに使うなら、解説した実装だと満たせません。
  • クリックしたらその要素に対応する画面を表示したい。
    • ListViewではOnItemClickListenerというインターフェースが用意されていますが、RecyclerViewは自分で定義・実装が必要になります。

まとめ

今回はRecyclerViewに関するシンプルな実装を解説しました。さまざまな使い方ができるRecyclerViewの使い方をマスターしたい人は最初の一歩として参考にしていただけると幸いです。ただ、上記の通りまだまだ実際の利用にはアプローチが足りていないので、次回は上記のようなより実用に近い機能の実装方法について解説したいと思います。

移行予定

どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。