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
の一列のレイアウトを決めます。
<?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
で代用できますよね。
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()
します。
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)
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は他のと競合してしまうかも...(僕は実際、違う名前を付けている。)
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. スワイプ
アニメーション付きスワイプを実装しましょう。
実をいうともともとスワイプを実装すると横にずれたり、シュッって消えたりするアニメーションはついてきます。色とアイコンはついてこないのでここを実装すればいいということです。
今回は左スワイプの時のみ削除にします。色は赤でアイコンは自前のゴミ箱マークです。
// ...省略
@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 (フォローしてくださいお願いします。)
いいねもお願いします。