Help us understand the problem. What is going on with this article?

[Java]RecyclerViewの使用方法とアニメーション付きスワイプ処理の実装をメモ。

1. はじめに

1.1 お断り

このサイトではQiita内の投稿をいくつかリンクさせていただいております。不愉快だと感じた投稿者様はコメントしていただければ幸いです。対応させていただきます。

1.2 目的

RecyclerViewはとても便利です。僕はListViewの代わりに使っているのですがその実装方法とスワイプの実装を忘れてしまうのでここにメモをします。スワイプのアニメーションはGmailのメール削除のスワイプをまねます。

2. RecyclerView本体

この実装内容は「RecyclerViewの基本」by@naoi様と同じです。とても分かりやすく解説されています。RecyclerViewの仕組みは少し複雑なので図も覚えとくといいと思いますよ。

ここでは以下の四つを制作します。
- adapter_row.xml
- DataModel.java
- ViewHolder.java
- Adapter.java

今回、入れるデータは画像と文字列にしましょう。

2.1 一列のレイアウトを決める (adapter_row.xml)

RecyclerViewの一列のレイアウトを決めます。

adapter_row
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/imageView_adapter_show_bitmap"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/textView_adapter_show_string"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</android.support.constraint.ConstraintLayout>

2.2 構造体を作る (DataModel.java)

Javaに構造体はありませんが、Classで代用できますよね。

DataModel.java
public class DataModel {
    private Bitmap mBitmap;
    private String mString;

    public Bitmap getBitmap () {
        return mBitmap;
    }

    public String getString () {
        return mString;
    }

    public void setBitmap (Bitmap mBitmap) {
        this.mBitmap = mBitmap;
    }

    public void setString (String mString) {
        this.mString = mString;
    }
}

メンバ変数にmを付ける習慣って一般的にはないんですかね...

余談ですが「get~」とか「set~」っていうのはgetter/setterていうらしいですが、AndroidStudioで、publicまで打つとメンバ変数のgetter/setterを作ってくれるそう。ちょっと名前変えたらそれで完成だからありがたい。

2.3 一行内のViewを取得 (ViewHolder.java)

ViewHolderを作ります。ここでxml本体は指定しませんが一行のなかのViewfindViewByID()します。

ViewHolder.java
public class ViewHolder extends RecyclerView.ViewHolder {
    public ImageView mImageView;
    public TextView mTextView;

    // コンストラクタ
    public ViewHolder (@NonNull View itemView) {
        super(itemView);
        mImageView = itemView.findViewById(R.id.imageView_adapter_show_bitmap);
        mTextView = itemView.findViewById(R.id.textView_adapter_show_string);
    }
}

コンストラクタはRecyclerView.ViewHolderを継承すると引数にitemViewを追加するように要求されます。

2.4 データを配置する (Adapter.java)

Adapter.java
public class Adapter extends RecyclerView.Adapter<ViewHolder> {

    private List<DataModel> insertDataList;

    public Adapter (List<DataModel> list) {
        this.insertDataList = list;
    }

    @NonNull
    @Override
    public NoteViewHolder onCreateViewHolder (@NonNull ViewGroup viewGroup, int i) {
        View inflate = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.adapter_row, viewGroup, false);
        return new ViewHolder(inflate);
    }

    @Override
    public void onBindViewHolder (@NonNull ViewHolder viewHolder, int i) {
        noteViewHolder.mImageView.setImageBitmap(insertDataList.get(i).getBitmap());
        noteViewHolder.mTextView.setText(insertDataList.get(i).getString());
    }

    @Override
    public int getItemCount () {
        return insertDataList.size();
    }
}

3.使い方

Activity等での使い方は以下の通りです。
Adapterは他のと競合してしまうかも...(僕は実際、違う名前を付けている。)

MainActivity.java
public class RegisterNoteActivity extends AppCompatActivity {
    RecyclerView mRecyclerViewList;

    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerViewList = findViewById(R.id.recyclerView_main);

        LinearLayoutManager manager = new LinearLayoutManager(this);
        mRecyclerViewList.setHasFixedSize(false);
        mRecyclerViewList.setLayoutManager(manager);
        mRecyclerViewList.setAdapter(new Adapter(createData()));
    }

    private List<NoteDataModel> createData() {
        List<NoteDataModel> list = new ArrayList<>();
        for (int index = 0; index < 100; index++) {
            // 一行分のデータ
            NoteDataModel rowData = new NoteDataModel();
            rowData.setBitmap(/*なんかの画像*/);
            rowData.setmString("この行は" + (index + 1) + "回目の繰り返しです。");
            list.add(rowData);
        }
        return list;
    }
}

4. スワイプ

アニメーション付きスワイプを実装しましょう。
実をいうともともとスワイプを実装すると横にずれたり、シュッって消えたりするアニメーションはついてきます。色とアイコンはついてこないのでここを実装すればいいということです。
今回は左スワイプの時のみ削除にします。色は赤でアイコンは自前のゴミ箱マークです。

MainActivity.java
    // ...省略    
    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register_note);
        // ...省略...
        mRecyclerViewList.setAdapter(new Adapter(createData())); // さっきの
        // ここから追加
        final Drawable deleteIcon = ContextCompat.getDrawable(this, R.drawable.ic_action_delete);
        ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {

            @Override
            public boolean onMove (@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
                return false;
            }

            @Override
            public void onSwiped (@NonNull RecyclerView.ViewHolder viewHolder, int i) {
                int swipedPosition = viewHolder.getAdapterPosition();
                Adapter adapter = (Adapter) mRecyclerView.getAdapter();
                // 登録とかするんだったらなにかのリストから削除をする処理はここ
                // 削除されたことを知らせて反映させる。
                adapter.notifyItemRemoved(swipedPosition);
            }

            @Override
            public void onChildDraw (@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
                View itemView = viewHolder.itemView;

                // キャンセルされた時
                if (dX == 0f && !isCurrentlyActive) {
                    clearCanvas(c, itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
                    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, false);
                    return;
                }

                ColorDrawable background = new ColorDrawable();
                background .setColor(Color.parseColor("#f44336"));
                background.setBounds(itemView.getRight() + (int)dX, itemView.getTop(),  itemView.getRight(), itemView.getBottom());
                background.draw(c);

                int deleteIconTop = itemView.getTop() + (itemView.getHeight() - deleteIcon.getIntrinsicHeight()) / 2;
                int deleteIconMargin = (itemView.getHeight() - deleteIcon.getIntrinsicHeight()) / 2;
                int deleteIconLeft = itemView.getRight() - deleteIconMargin - deleteIcon.getIntrinsicWidth();
                int deleteIconRight = itemView.getRight() - deleteIconMargin;
                int deleteIconBottom = deleteIconTop +  deleteIcon.getIntrinsicHeight();

                deleteIcon.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom);
                deleteIcon.draw(c);
            }
        };
        new ItemTouchHelper(callback).attachToRecyclerView(mRecyclerView);

    }

    private void clearCanvas(Canvas c, int left, int top, int right, int bottom) {
        Paint paint = new Paint();
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        c.drawRect(left, top, right, bottom, paint);
    }
    // 省略...

アイコンの画像や計算、色コード、処理思想は「Android のリストをスワイプして削除する #あれどうやるの?」by@shts様とそのGithubからいただきました。無断ですみません。

スワイプが完了したときに発火するのはonSwipedですが、それに対してキャンセルしたとしても途中に毎フレームごとに呼ばれるのがonChildDrawです。onMoveはドラッグアンドドロップも許可したとき用だそうですがfalseを返しとけばいいのかな?

追記ですが右スワイプと左スワイプ両方を許可するのは以下の通りにやればいいみたいです。

        ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {

            @Override
            public boolean onMove (@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
                return false;
            }

            @Override
            public void onSwiped (@NonNull RecyclerView.ViewHolder viewHolder, int i) {

            }

            @Override
            public void onChildDraw (@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
                if (dX < 0) {
                    // 左スワイプのとき
                } else {
                    // 右スワイプのとき
                }
            }
        };

隠れてぱっと見では見えないけど、一行目の最後、ItemTouchHelper.LEFT | ItemTouchHelper.RIGHTになってるので気を付けてください。
onChildDrawは上記通りなのですが、onSwipedでの処理の分け方はわからないです。(iを使うのかな?)

5.まとめ

まずは参考にさせていただいたサイトの投稿者様誠にありがとうございました。少し雑ではありますがこちらでまとめて紹介させていただきます。

RecyclerViewの基本
RecyclerView本体についてほぼ同じようにまねさせていただたきました。

RecyclerViewでドラッグアンドドロップの移動とスワイプの削除
スワイプのイメージをつかみました。

Android のリストをスワイプして削除する #あれどうやるの?
スワイプの処理についてJavaに書き直させていただきました。
→画像と詳しい処理は付属のGitHubからです。

Android で RecyclerView を使って横スワイプで要素を削除する方法のメモ
JavaでAdapterをどうやて更新するかを参考にしました。

RecyclerViewのアイテムをスワイプで削除する方法
投稿外での追加の処理について参考にしました。

皆さんありがとうございました。
こうまでして他人のコードを取りまくっていくことでしか処理を生み出せない自分が情けないですが、だからこそだれかのためになれればなと思います。

Twitter: https://twitter.com/Cyber_Hacnosuke (フォローしてくださいお願いします。)
いいねもお願いします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away