【Android】RecyclerViewをドラッグ&ドロップで移動、追加・削除

更新日: 公開日:

t f B! P L

Android Studioで、データをRecyclerViewに一覧表示し、そのデータをドラッグ&ドロップで移動したり、追加・削除する方法を紹介します。

Android RecyclerViewをドラッグ&ドロップ

今回作成するのは、RecyclerViewにデータを一覧表示する画面です。

一覧表示画面 右下の[]ボタンをタップするとデータが1行追加されます。

リストをタップするとデータを編集できます。

リスト右側の移動ボタンをドラッグ&ドロップすると、データを移動できます。

リスト右側の ×(削除) ボタンをタップすると、データが削除されます。

Android RecyclerViewをドラッグ&ドロップ

このシンプルなアプリを題材に、データをRecyclerViewに表示する方法やRecyclerViewに配置したボタンによる操作方法を紹介します。

Android Studioのインストール方法と簡単な使い方については、こちらの記事で紹介しています。

Android Studioをインストール

Androidアプリを開発するソフト、Android Studioをパソコンにインストールする方法とデバッグ環境を構築する方法(AVD作成、実機接続)をたくさんの図を使って詳しく紹介します。

Android Studio ListViewと削除ボタン

Android Studio の簡単な使い方

Android Studioの使い方を簡単に紹介します。ファイルやプロジェクトの構成、アクティビティとレイアウトの関係、ソースコードの記述内容やトースト、ログ出力、デバッグ方法など、まずはざっくりと紹介します。

構成

RecyclerView(リサイクラービュー)は、ListViewよりも自由度の高いリストを生成するためのウィジェットです。

ビューをリサイクルしながらリストを処理するため、大きなデータを扱うことができます。

今回のプロジェクトのオブジェクト構成は、このようになっています。

RecyclerViewをドラッグ&ドロップ

メインアクティビティ(MainActivity)は、メイン画面のレイアウト(activity_main.xml)を使ってメイン画面を表示します。

アダプターは、1行分のレイアウト(row_main.xml)内の各ウィジェットへの参照を保持するビューホルダーにデータを割り当て、RecyclerViewに設定します。

表示に使用する文字列などは、リソースに定義しているstrings.xmlなどを参照します。

追加ボタンの「+」や削除ボタンなどの「×」アイコンは、drawable に追加して使用します。

順を追って、詳しく説明していきます。

プロジェクト作成

Android Studio の簡単な使い方」のプロジェクト作成で紹介した手順で、プロジェクトを作成します。

プロジェクト名は、 「SampRecyclerView」 としました。

リソース準備

アプリ内から参照する次のリソースを準備します。

strings.xml

アプリ内で使用する文字列の定義をします。

app/res/vlues/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を指定して、プログラムやレイアウトから参照します。

アイコン

アイコンは、前回の記事「【Android Studio】SQLiteデータベースをListViewに一覧表示&削除ボタンでDelete!」と同じように「+」と「×」アイコンを追加します。

今回はさらに、移動ボタンとして使用するアイコン(ic_baseline_unfold_more_24.xml)も追加します。

移動アイコン

追加方法については、記事内のアイコンの部分を参照してください。

Android Studio ListViewと削除ボタン

Android Studio SQLiteデータベースに保存する方法 (TextView・EditText)

Android Studio の簡単なサンプルを通して、SQLiteデータベースにデータを保存したり、更新したり、その結果を画面に表示して確認する方法を紹介します。 画面の画像と実際のソースコードを解説しながら、初心者向けに簡単な言葉で分かりやすく説明しています。

画面レイアウト

activity_main.xml

app/res/values/activity_main.xmlに、メイン画面のレイアウトを設定します。

Android RecyclerViewでドラッグ&ドロップ
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に、RecyclerViewとFloatingActionButtonのみ配置しています。

row_main.xml

row_main.xmlでリストの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>

リストの内容を入力するEditTextと移動用、削除用の ImageButton を配置しています。

アダプターとビューホルダー

アダプター(Adapter)とは、データとウィジェットを関連付け、橋渡しをしてくれるオブジェクトです。

今回のアプリでは、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の中では、インナークラスとして、ビューホルダー(ViewHolder)を定義し、そのビューホルダーの生成やデータの割り当て、ボタンにタッチしたときの処理などを記述しています。

ビューホルダー

ビューホルダーは、アダプターで利用するビューを保持するクラスです。

findViewById(R.id.edit_contents)といったコストのかかるウィジェットの参照取得を何度も行わなくていいように、個々のウィジェットへの参照を保持しておくクラスで、処理は記述しません。

テキストウォッチャー

EditTextに入力した内容がスクロールしても消えないようにしているのが、テキストウォッチャーを使った処理の部分です。

EditTextにテキストウォッチャーのリスナーを設定し、文字が入力されるたびにListを更新しています。

ただ、そのままリスナーを追加してしまうと、スクロールしてリサイクルされるたびに、リスナーが追加されていってしまいます。

2重、3重にリスナーが追加されてしまうことで、データの更新処理がおかしくなってしまうので、既にリスナーがある場合は削除してから追加しなおす処理としています。

アクティビティ

メイン画面を表示する 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(adapter.getItemCount()+1);
    }
}

MainActivityでは、レイアウトを管理するレイアウトマネージャーをリサイクラービューに設定しています。

レイアウトマネージャーの種類はいくつかありますが、今回はリストを縦・横に並べるLinearLayoutManagerを使用しています。

アダプターを生成してリサイクラービューに設定します。

ドラッグアンドドロップによる並び替えの処理は、ItemTouchHelper.SimpleCallbackを使って行っています。

ItemTouchHelperとは、RecyclerViewでドラッグアンドドロップなどの処理をするためのユーティリティクラスです。

onMove()とonSwiped()メソッドをオーバーライドしています。

リストを長押ししたときに、onMove()メソッドが呼ばれ、ドラッグアンドドロップによる移動ができます。

(移動アイコンがタッチされたときの処理は、アダプターからitemTouchHelper.startDrag()を呼んでいます。)

リストをスワイプした時と削除ボタンをタップした時に、リストが削除されます。

参考にさせていただいたサイト

こちらのサイトを参考にさせていただきました。ありがとうございます。
アンドロイドで RecyclerView のアイテムを「優しく」ドラッグ操作する
[Android] RecyclerViewとItemTouchHelperでドラッグ&ドロップ

まとめ

Android Studioで、RecyclerViewを使ってデータを一覧表示する方法を紹介しました。

RecyclerViewをドラッグ&ドロップして簡単にデータの並べ替えができる機能はとっても便利だと思います。

RecyclerViewは、他にもいろいろカスタマイズができるようなので、また試してみたいと思います。

このブログを検索

カテゴリ

プロフィール


こんにちは!

まあちゃん&うさたん のママです。


ラズベリーパイを使った簡単な電子工作やプログラミングに挑戦しています。


よかったら、詳しいプロフィールも見てください。


QooQ