RecyclerView の使い方。ドラッグ&ドロップで並び替え、スワイプで削除する。【Android】
RecyclerView の基本的な使い方を確認したので、備忘録として残しています。
ItemTouchHelper を使用して RecyclerView のアイテムをドラッグ&ドロップで並べ替え、スワイプで削除する方法についても確認しました。
試作品は以下のような感じです↓↓
RecyclerView で表示するアイテムには、複数のレイアウトを指定できます。
上記試作品でも、3種類のレイアウトファイルを使用しています。
- TextView が2つあるレイアウト(ViewType1 の下にあるアイテム)
- TextView が1つだけのレイアウト(ViewType2 の下にあるアイテム)
- アイテムを区切るセクションタイトルのレイアウト(「ViewType1 」「ViewType2」 というテキストを表示しているアイテム)
上記試作品のソースコードは、GitHub にアップしています。
目次
RecyclerView の基本
RecyclerView を使ってデータセットをリスト表示するためには、以下のクラスを実装します。
RecyclerView.Adapter |
|
RecyclerView.ViewHolder |
|
RecyclerView.LayoutManager |
|
- Adapter によって RecyclerView に表示するデータを定義する
- RecyclerView の1行ごとの参照は、Adapter -> ViewHolder に保持されている
- RecyclerView のレイアウト(水平・垂直・グリッド表示など)は、LayoutManager によって管理される
レイアウトファイルの作成
RecyclerView を配置
1 2 3 4 5 6 7 |
<android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="450dp" android:background="@color/colorPrimaryDark" /> |
RecyclerView のアイテム用のレイアウトを作成
試作品では3つのレイアウトファイルを作成しています。以下は1例になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true" android:background="@drawable/recyclerview_background"> <ImageView android:id="@+id/thumbnail_image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:paddingStart="10dp" android:layout_alignParentStart="true" android:layout_alignParentTop="true"/> <TextView android:id="@+id/title_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:textColor="@color/defaultTextColor" android:layout_toRightOf="@+id/thumbnail_image_view"/> <ImageView android:id="@+id/hamburger_image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingEnd="10dp" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:src="@mipmap/baseline_menu_white_24"/> </RelativeLayout> |
Adapter の実装
RecyclerView に表示するデータとViewを結びつける Adapter を実装します。
アイテム行への参照を保持する ViewHolder も、Adapter の中で定義しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
class SampleAdapter (var myDataset : ArrayList<SampleData>, var activity: MainActivity) : RecyclerView.Adapter<SampleAdapter.SampleViewHolder>(){ /* ViewHolder の実装 */ open class SampleViewHolder(v : View) : RecyclerView.ViewHolder(v) { var titleTextView : TextView = v.findViewById(R.id.title_text_view) } open class DefaultSampleViewHolder(v : View, activity: MainActivity) : SampleViewHolder(v) { var thumbnailImageView : ImageView = v.findViewById(R.id.thumbnail_image_view) var hamburgerImageView : ImageView = v.findViewById(R.id.hamburger_image_view) init { //省略... } } class SubSampleViewHolder(v : View, activity: MainActivity) : DefaultSampleViewHolder(v, activity){ var subTitleTextView : TextView = v.findViewById(R.id.subtitle_text_view) } // ViewType に応じてレイアウトを決定し、View(ViewHolder)を作成する。 override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): SampleViewHolder { //viewType に応じてレイアウトを変更している。 return when (viewType){ RecyclerViewCustomViewType.SECTION.ordinal -> { val v = LayoutInflater.from(viewGroup.context) .inflate(R.layout.sample_recyclerview_row_view_section, viewGroup, false) SampleViewHolder(v) } RecyclerViewCustomViewType.DEFAULT.ordinal -> { val v = LayoutInflater.from(viewGroup.context) .inflate(R.layout.sample_recyclerview_row_view_default, viewGroup, false) DefaultSampleViewHolder(v, activity) } else -> { val v = LayoutInflater.from(viewGroup.context) .inflate(R.layout.sample_recyclerview_row_view_sub, viewGroup, false) SubSampleViewHolder(v, activity) } } } //表示するView(ViewHolder)に、データを紐づける。 override fun onBindViewHolder(holder : SampleViewHolder, position: Int){ holder.titleTextView.text = myDataset[position].title if (holder is DefaultSampleViewHolder) holder.thumbnailImageView.setImageResource(myDataset[position].imageResource) if (holder is SubSampleViewHolder) holder.subTitleTextView.text = myDataset[position].subTitle } // データの数を返す override fun getItemCount(): Int = myDataset.size // アイテムのViewTypeを返す override fun getItemViewType(position: Int): Int { return when(myDataset[position].viewType){ RecyclerViewCustomViewType.SECTION -> RecyclerViewCustomViewType.SECTION.ordinal RecyclerViewCustomViewType.SUB -> RecyclerViewCustomViewType.SUB.ordinal else -> RecyclerViewCustomViewType.DEFAULT.ordinal } } } |
RecycerView の設定をする
RecyclerView に Adapter を渡したり、LayoutManager を設定したりします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class MainActivity : AppCompatActivity() { private lateinit var recyclerView : RecyclerView private lateinit var viewAdapter : RecyclerView.Adapter<*> private lateinit var viewManager : RecyclerView.LayoutManager private lateinit var myDataSet : ArrayList<SampleData> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // テストデータの作成 myDataSet = createDataSet() // RecyclerView の設定 viewManager = LinearLayoutManager(this) viewAdapter = SampleAdapter(myDataSet, this) recyclerView = findViewById<RecyclerView>(R.id.recycler_view).apply{ setHasFixedSize(true) layoutManager = viewManager adapter = viewAdapter } |
ドラッグ & ドロップ、スワイプでリスト操作をする
RecyclerView でドラッグ&ドロップやスワイプ操作を行うには、ItemTouchHelper クラスを利用します。
ItemTouchHelper |
|
|
|
(今回はこちらを利用しました。) |
SimpleCallback を実装する
ItemTouchHelper のコンストラクタに渡す必要がある Callback を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
private fun getRecyclerViewSimpleCallBack() = // 引数で、上下のドラッグ、および左方向のスワイプを有効にしている。 object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) { // ドラッグしたときに呼ばれる override fun onMove(p0: RecyclerView, p1: RecyclerView.ViewHolder, p2: RecyclerView.ViewHolder): Boolean { // どこからどこへアイテムが移動するのか val fromPosition = p1.adapterPosition val toPosition = p2.adapterPosition /* * ドラッグ時、viewType が異なるアイテムを超えるときに、 * notifyItemMoved を呼び出すと、ドラッグ操作がキャンセルされてしまう。 * (ドラッグは同じviewTypeを持つアイテム間で行う必要がある模様) * * 同じ ViewType アイテムを超える時だけ notifyItemMoved を呼び出す。 * */ if (p1.itemViewType == p2.itemViewType) { // Adapter の持つ実データセットを操作している myDataSet.add(toPosition, myDataSet.removeAt(fromPosition)) // Adapter にアイテムが移動したことを通知 viewAdapter.notifyItemMoved(fromPosition, toPosition) } return true } // スワイプしたとき override fun onSwiped(p0: RecyclerView.ViewHolder, p1: Int) { p0.let{ // 実データセットからアイテムを削除 myDataSet.removeAt(p0.adapterPosition) // adapter に通知 viewAdapter.notifyItemRemoved(p0.adapterPosition) } } |
ItemTouchHelper の設定
先ほどのMainActivityに処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class MainActivity : AppCompatActivity() { private lateinit var recyclerView : RecyclerView private lateinit var viewAdapter : RecyclerView.Adapter<*> private lateinit var viewManager : RecyclerView.LayoutManager private lateinit var myDataSet : ArrayList<SampleData> // 追加 private lateinit var itemTouchHelper : ItemTouchHelper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) /* 省略 */ // RecyclerView のドラッグ、スワイプ操作に関する設定 itemTouchHelper = ItemTouchHelper(getRecyclerViewSimpleCallBack()) itemTouchHelper.attachToRecyclerView(recyclerView) } |
おわりに
結構省略してソースコードをのせています。。。
全体像は GitHub の方で確認していただければと思います!!!