※この記事は公開から時間が経っているため、現在の環境とは内容が異なる場合があります。
Android Studioで、データを RecyclerView に一覧表示し、そのデータを ドラッグ&ドロップで移動 したり、追加・削除 したりする方法を紹介します。
今回作成するのは、RecyclerViewに文字列データを一覧表示するシンプルなアプリです。
一覧画面では、次の操作ができます。
- 右下の +ボタン をタップすると、データを1行追加する
- リスト内の文字を編集する
- リスト右側の 移動ボタン をドラッグ&ドロップして、並び順を変更する
- リスト右側の ×(削除)ボタン をタップして、データを削除する
動きのイメージは、次のようになります。

このシンプルなアプリを題材に、RecyclerViewにデータを表示する方法や、RecyclerViewに配置したボタンで操作する方法を見ていきます。
この記事で作るもの
今回作成するアプリの完成イメージです。

画面には、文字列データが縦に並んでいます。
1行の中には、次の部品を配置します。
EditText:リストの内容を表示・編集する- 移動ボタン:ドラッグ&ドロップの目印として使う
- 削除ボタン:その行を削除する
画面全体の役割を図にすると、次のようになります。

Android Studioのインストールや基本的な使い方、ListViewを使った一覧表示については、先にこちらの記事を読んでおくと進めやすいです。



RecyclerViewとは
RecyclerView は、データを一覧表示するためのウィジェットです。
ListViewよりも自由度が高く、表示する1行分の見た目や操作を細かく作り込みやすいのが特徴です。
また、画面外に出たViewを再利用しながら表示するため、大きなデータを扱うリストにも向いています。
ただし、RecyclerViewでは次のような要素が出てくるため、最初は少し複雑に見えます。
- Adapter
- ViewHolder
- LayoutManager
- ItemTouchHelper
この記事では、まずシンプルな文字列リストを作りながら、それぞれの役割を確認していきます。
アプリの構成
今回のアプリの構成は、次のようになります。

実際の構成図では、MainActivity、Adapter、ViewHolder、row_main.xml、リソースが次のようにつながっています。

主な役割は次のとおりです。
- MainActivity:メイン画面を表示し、RecyclerViewを準備する
- activity_main.xml:メイン画面のレイアウトを定義する
- RecyclerView:一覧を表示する
- SampAdapter:データと1行分の表示を結び付ける
- SampViewHolder:1行分のViewへの参照を保持する
- row_main.xml:1行分のレイアウトを定義する
- ArrayList:一覧に表示するデータを保持する
- strings.xml / drawable:文字列やアイコンなどのリソースを管理する
RecyclerViewでは、データを直接画面に置くのではなく、AdapterとViewHolderを通して1行ずつ表示していきます。
プロジェクト作成
プロジェクトは、こちらの記事の「プロジェクト作成」と同じ流れで作成できます。
Android Studioをインストール|プロジェクト作成の章
今回のプロジェクト名は、SampRecyclerView とします。
リソース準備
まずは、アプリ内から参照する文字列やアイコンを準備します。
strings.xml
app/res/values/strings.xml を開いて、次の内容に編集します。
<resources>
<string name="app_name">SampRecyclerView</string>
<string name="contents">コンテンツ</string>
<string name="reg">登録</string>
<string name="del">削除</string>
<string name="move">移動</string>
</resources>ここで定義した name を指定して、プログラムやレイアウトから参照します。
アイコン
今回使うアイコンは、次の3つです。
- +アイコン:アイテム追加用
- ×アイコン:アイテム削除用
- 移動アイコン:ドラッグ&ドロップ用
追加用・削除用のアイコンは、前回の記事と同じようにVector Assetから追加します。
今回はさらに、移動ボタンとして使う ic_baseline_unfold_more_24.xml も追加します。

アイコンの追加方法は、こちらの記事の「アイコンを追加する」の部分も参考にしてください。

画面レイアウト
今回作成するレイアウトは、主に次の2つです。
activity_main.xml:メイン画面全体row_main.xml:RecyclerViewの1行分
activity_main.xml
activity_main.xml では、メイン画面に RecyclerView と FloatingActionButton を配置します。

activity_main.xml(クリックして表示)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_reg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:focusable="true"
android:clickable="true"
android:contentDescription="@string/reg"
android:onClick="onAddItem"
app:srcCompat="@drawable/ic_baseline_add_24"
app:tint="@color/white"
app:backgroundTint="@color/purple_200"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>このレイアウトでは、ConstraintLayout の中に次の2つだけを配置しています。
FloatingActionButton:右下の+ボタン
RecyclerView:一覧表示部分
ConstraintLayoutと+ボタン
RecyclerView は、上下左右を親レイアウトに制約して、画面いっぱいに配置しています。
FloatingActionButton は、下と右に制約して、画面右下に配置しています。

ConstraintLayout は、画面の端や他の部品との位置関係を指定してレイアウトを作る方法です。
画面サイズが変わっても位置関係を保ちやすいので、一覧画面のようなレイアウトでも使いやすいです。
row_main.xml
次に、RecyclerViewの1行分の見た目を row_main.xml に定義します。
row_main.xml(クリックして表示)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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">
<EditText
android:id="@+id/edit_contents"
android:layout_width="310dp"
android:layout_height="70dp"
android:layout_marginStart="10dp"
android:background="#00000000"
android:gravity="center_vertical"
android:inputType="textMultiLine"
android:textSize="20sp" />
<ImageButton
android:id="@+id/btn_move"
android:layout_width="40dp"
android:layout_height="70dp"
android:background="#00000000"
android:contentDescription="@string/move"
android:gravity="center_vertical"
app:srcCompat="@drawable/ic_baseline_unfold_more_24" />
<ImageButton
android:id="@+id/btn_del"
android:layout_width="40dp"
android:layout_height="70dp"
android:background="#00000000"
android:contentDescription="@string/del"
android:gravity="center_vertical"
app:srcCompat="@drawable/ic_baseline_close_24" />
</LinearLayout>この1行分のレイアウトには、次の3つを配置しています。
EditText:リストの内容を入力・編集するbtn_move:ドラッグ&ドロップで移動するためのボタンbtn_del:削除用のボタン
1行分の構成は、次の図を見るとイメージしやすいです。

アダプターとビューホルダー
RecyclerViewでは、データを直接表示するのではなく、Adapter と ViewHolder を使って表示します。
アダプターは、データとウィジェットを関連付ける橋渡し役です。

今回のアプリでは、SampAdapter というRecyclerView用のアダプターを作成します。
SampAdapter (クリックして表示)
package com.ma_chanblog.samprecyclerview;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.widget.EditText;
import android.widget.ImageButton;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class SampAdapter extends RecyclerView.Adapter<SampAdapter.SampViewHolder>{
private final List<String> arrayList;
private MainActivity activity;
// アダプターのコンストラクタ
SampAdapter(List<String> arrayList) {
this.arrayList = arrayList;
}
// ビューホルダーを生成
@NonNull
@Override
public SampViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// レイアウトファイルに対応したViewオブジェクトを生成
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_main, parent, false);
// MainActivityを取得
activity = (MainActivity) parent.getContext();
// ビューホルダーを生成してreturn
return new SampViewHolder(view);
}
// ビューホルダーにデータを割り当てる
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(SampViewHolder holder, int position) {
// EditTextにデータを設定
holder.edit_contents.setText(arrayList.get(position));
// テキストウォッチャーリスナーが既にあれば削除
if (holder.textWatcher != null) {
holder.edit_contents.removeTextChangedListener(holder.textWatcher);
}
// テキストウォッチャーを設定
holder.textWatcher = createEditTextWatcher(holder);
holder.edit_contents.addTextChangedListener(holder.textWatcher);
// 移動ボタンをタッチ
holder.btn_move.setOnTouchListener(new View.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// 長押しではなく、タッチしてすぐにドラッグ状態にする
activity.itemTouchHelper.startDrag(holder);
return true;
}
return v.onTouchEvent(event);
}
});
// 削除ボタンをクリック
holder.btn_del.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int adapterPosition = holder.getAdapterPosition();
if (adapterPosition != -1) {
arrayList.remove(adapterPosition);
notifyItemRemoved(adapterPosition);
}
}
});
}
// アイテム数を取得
@Override
public int getItemCount() {
return arrayList.size();
}
// ビューホルダー
public static class SampViewHolder extends RecyclerView.ViewHolder {
// ビューに配置されたウィジェットへの参照を保持しておくためのフィールド
public EditText edit_contents; // リストの内容
public ImageButton btn_move; // 移動ボタン
public ImageButton btn_del; // 削除ボタン
// テキストウォッチャー
public TextWatcher textWatcher;
// ビューホルダーのコンストラクタ
public SampViewHolder(View view) {
super(view);
// ウィジェットへの参照を取得
edit_contents = (EditText) view.findViewById(R.id.edit_contents);
btn_move = (ImageButton) view.findViewById(R.id.btn_move);
btn_del = (ImageButton) view.findViewById(R.id.btn_del);
}
}
// テキストウォッチャー
private TextWatcher createEditTextWatcher(final SampViewHolder viewHolder) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
// 入力されたら、List内のデータを更新
@Override
public void afterTextChanged(Editable editable) {
arrayList.set(viewHolder.getAdapterPosition(), editable.toString());
}
};
}
}SampAdapterで行っていること
SampAdapter の中では、次の処理をしています。
TextWatcher でEditTextの入力内容をArrayListに反映する
onCreateViewHolder() で1行分のViewを作る
onBindViewHolder() でデータを1行分のViewに設定する
移動ボタンに触れたときにドラッグを開始する
削除ボタンを押したときにデータを削除する
ViewHolder
ViewHolderは、RecyclerViewの1行分に配置されたウィジェットへの参照を保持するクラスです。
findViewById(R.id.edit_contents) のような参照取得を何度も行わなくて済むように、各ウィジェットへの参照をまとめて持っています。
このサンプルでは、次の参照を保持しています。
edit_contentsbtn_movebtn_deltextWatcher
TextWatcher
TextWatcher は、EditTextに入力された内容を監視するための仕組みです。
このサンプルでは、EditTextに文字が入力されるたびに、ArrayList のデータを更新しています。
@Override
public void afterTextChanged(Editable editable) {
arrayList.set(viewHolder.getAdapterPosition(), editable.toString());
}RecyclerViewではViewが再利用されるため、入力欄に表示されている文字だけでなく、元データ側にも反映しておく必要があります。
また、再利用のたびにリスナーが増えないように、すでにTextWatcherがある場合は削除してから設定し直しています。
if (holder.textWatcher != null) {
holder.edit_contents.removeTextChangedListener(holder.textWatcher);
}MainActivity
次に、メイン画面を表示する MainActivity です。
MainActivity(クリックして表示)
package com.ma_chanblog.samprecyclerview;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback;
import java.util.ArrayList;
import java.util.Collections;
public class MainActivity extends AppCompatActivity {
// データ格納用のList
private ArrayList<String> arrayList;
// アダプター
private SampAdapter adapter;
// ドラッグアンドドロップなどをするためのユーティリティクラス
ItemTouchHelper itemTouchHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// データ準備
arrayList = new ArrayList<>();
for (int i=1; i < 6; i++) {
arrayList.add("コンテンツ" + i);
}
// リサイクラービューへの参照を取得
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.mainList);
// レイアウトマネージャーを準備
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
// レイアウトマネージャーを縦スクロールに設定
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
// リサイクラービューにレイアウトマネージャーを設定
recyclerView.setLayoutManager(layoutManager);
// アダプターを生成
adapter = new SampAdapter(arrayList);
// リサイクラービューにアダプターを設定
recyclerView.setAdapter(adapter);
// ドラッグアンドドロップで移動
itemTouchHelper = new ItemTouchHelper(
new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN ,
ItemTouchHelper.LEFT){
// 長押しで移動
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
final int fromPos = viewHolder.getAdapterPosition();
final int toPos = target.getAdapterPosition();
// データを入れ替え
Collections.swap(arrayList, fromPos, toPos);
// 移動したことを通知
adapter.notifyItemMoved(fromPos, toPos);
return true;
}
// スワイプで削除
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// アイテムを削除
arrayList.remove(viewHolder.getAdapterPosition());
// 削除したことを通知
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
});
// ItemTouchHelper を RecyclerView にアタッチ
itemTouchHelper.attachToRecyclerView(recyclerView);
}
// 「+」フローティング操作ボタンがタップされたときに実行される
public void onAddItem(View view) {
// 新規のアイテムを追加
arrayList.add("コンテンツ");
// アイテムを追加したことを通知
adapter.notifyItemInserted(arrayList.size() - 1);
}
}MainActivityで行っていること
MainActivity では、次の流れでRecyclerViewを準備しています。
ArrayListに初期データを入れる- RecyclerViewへの参照を取得する
LinearLayoutManagerを設定するSampAdapterを生成する- RecyclerViewにAdapterを設定する
ItemTouchHelperを設定する
LinearLayoutManager
RecyclerViewでは、リストをどのように並べるかを LayoutManager が管理します。
今回使っているのは、縦方向に並べる LinearLayoutManager です。
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
ItemTouchHelper
ドラッグ&ドロップでの並び替えや、スワイプでの削除には、ItemTouchHelper を使っています。
itemTouchHelper = new ItemTouchHelper(
new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT) {
この部分で、上下方向の移動と、左方向のスワイプを設定しています。
onMove()
onMove() は、アイテムがドラッグで移動したときに呼ばれます。
Collections.swap(arrayList, fromPos, toPos);
adapter.notifyItemMoved(fromPos, toPos);ここでは、ArrayList 内のデータを入れ替えて、RecyclerViewに「移動した」と通知しています。
onSwiped()
onSwiped() は、アイテムがスワイプされたときに呼ばれます。
arrayList.remove(viewHolder.getAdapterPosition());
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());ここでは、ArrayList から該当データを削除し、RecyclerViewに「削除した」と通知しています。
移動ボタンからドラッグを開始する
このサンプルでは、移動ボタンに触れたタイミングで、アダプター側から次の処理を呼んでいます。
activity.itemTouchHelper.startDrag(holder);これにより、移動ボタンからドラッグ操作を開始できます。
追加・移動・削除の流れ
追加・移動・削除の処理を図にすると、次のような流れです。

アイテムを追加する
+ボタンを押すと、onAddItem() が呼ばれます。
public void onAddItem(View view) {
arrayList.add("コンテンツ");
adapter.notifyItemInserted(arrayList.size() - 1);
}ここでは、新しい文字列を arrayList に追加し、RecyclerViewに追加されたことを通知しています。
アイテムを移動する
移動ボタンを押してドラッグすると、onMove() が呼ばれます。
Collections.swap() でデータの順番を入れ替え、notifyItemMoved() でRecyclerViewに知らせます。
アイテムを削除する
×ボタンを押したときは、アダプター側でその位置のデータを削除します。
int adapterPosition = holder.getAdapterPosition();
if (adapterPosition != -1) {
arrayList.remove(adapterPosition);
notifyItemRemoved(adapterPosition);
}また、スワイプ削除では onSwiped() の中で同じように削除処理を行っています。
確認しておきたいところ
ドラッグしても移動できない
次の点を確認します。
ItemTouchHelperを作成しているかattachToRecyclerView()でRecyclerViewに設定しているか- 移動ボタンの
onTouch()でitemTouchHelper.startDrag(holder)を呼んでいるか
itemTouchHelper.attachToRecyclerView(recyclerView);
EditTextの内容がスクロール後に変わる
RecyclerViewはViewを再利用します。
そのため、EditTextに入力した値を元データの ArrayList に反映しておかないと、スクロール後に表示がずれることがあります。
このサンプルでは、TextWatcher を使って入力内容を ArrayList に反映しています。
TextWatcherが重複する
onBindViewHolder() の中でTextWatcherを追加する場合、再利用のたびにリスナーが増えてしまうことがあります。
そのため、すでに設定済みのTextWatcherがある場合は、次のように削除してから追加しています。
if (holder.textWatcher != null) {
holder.edit_contents.removeTextChangedListener(holder.textWatcher);
}
追加したアイテムが表示されない
arrayList.add() でデータを追加したあと、RecyclerViewに変更を通知しているか確認します。
adapter.notifyItemInserted(arrayList.size() - 1);
まとめ
この記事では、Android Studioで RecyclerViewを使ってデータを一覧表示し、ドラッグ&ドロップで移動、追加、削除する方法 を紹介しました。
ポイントは次のとおりです。
- RecyclerViewでは、AdapterとViewHolderを使って一覧を表示する
- 1行分の見た目は
row_main.xmlで定義する LinearLayoutManagerで縦方向のリストにできるItemTouchHelperを使うと、ドラッグ&ドロップやスワイプ削除ができる- EditTextの入力内容は、TextWatcherで元データ側にも反映する
- 追加や削除をしたときは、RecyclerViewに変更を通知する
RecyclerViewは、ListViewよりも柔軟にリスト画面を作れるウィジェットです。
最初はAdapterやViewHolderなどの用語が多く感じるかもしれませんが、1つずつ役割を分けて見ると流れが見えやすくなります。
こちらは、関連記事です。





