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

# 1. はじめに

## 1.1 お断り
**このサイトではQiita内の投稿をいくつかリンクさせていただいております。不愉快だと感じた投稿者様はコメントしていただければ幸いです。対応させていただきます。**
## 1.2 目的
``RecyclerView``はとても便利です。僕は``ListView``の代わりに使っているのですがその実装方法とスワイプの実装を忘れてしまうのでここにメモをします。スワイプのアニメーションは``Gmail``のメール削除のスワイプをまねます。

# 2. RecyclerView本体
この実装内容は[「RecyclerViewの基本」by@naoi様](https://qiita.com/naoi/items/f8a19d6278147e98bbc2)と同じです。とても分かりやすく解説されています。``RecyclerView``の仕組みは少し複雑なので図も覚えとくといいと思いますよ。

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

**今回、入れるデータは画像と文字列にしましょう。**
## 2.1 一列のレイアウトを決める (adapter_row.xml)
``RecyclerView``の一列のレイアウトを決めます。

```xml: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``で代用できますよね。

```java: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``本体は指定しませんが一行のなかの``View``を``findViewByID()``します。

```java: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)

```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は他のと競合してしまうかも...(僕は実際、違う名前を付けている。)**

```java: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様](https://qiita.com/shts/items/042f57c3451b9375db1d)とそのGithubからいただきました。無断ですみません。

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

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

```java
        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の基本](https://qiita.com/naoi/items/f8a19d6278147e98bbc2) 
RecyclerView本体についてほぼ同じようにまねさせていただたきました。

[RecyclerViewでドラッグアンドドロップの移動とスワイプの削除](https://qiita.com/naoi/items/f8004f906db16d6b38da)
スワイプのイメージをつかみました。

[Android のリストをスワイプして削除する #あれどうやるの？](https://qiita.com/shts/items/042f57c3451b9375db1d)
スワイプの処理についてJavaに書き直させていただきました。
    →画像と詳しい処理は付属の[GitHub](https://github.com/shts/SwipeToDismissSample)からです。

[Android で RecyclerView を使って横スワイプで要素を削除する方法のメモ](https://qiita.com/Hachimori/items/82eac873286ec2aa22fb)
JavaでAdapterをどうやて更新するかを参考にしました。

[RecyclerViewのアイテムをスワイプで削除する方法](https://qiita.com/kkt_yu/items/baacbee06a8868c08c94)
投稿外での追加の処理について参考にしました。

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

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