こんにちは、まっさん(@Tera_Msaki)です。
この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、記事の最後で紹介します)
RecyclerViewのアイテムの並び替えと削除で発生する
予期しない動きに対処する
RecyclerView は、Andoroid アプリで動的リストを作成するライブラリです。
このライブラリの特徴として、スクロール時に View を破棄せず、再利用します。
この為、リソースの有効活用、パフォーマンスの改善、応答性の向上など、
リスト型式の View を作成する際に、必須のライブラリです。
RecyclerView は、便利で強力なライブラリですが、
View を破棄せず、再利用する特徴から、
実装する上で、予期しない動きに嵌(はま)ることがあります。
今回、RecyclerView でアイテムの並び替えと削除の機能を実装した際に、
嵌った予期しない動きの対処について、説明したいと思います。
Drag状態を通知するユーザインタフェース
Drag状態 をユーザに通知する仕掛けとして、
Drag状態のアイテムを半透明にする UI がよく使用されます。
Drag状態を判別する方法としては、
SimpleCallback の onSelectedChanged を使用します。
actionState が ACTION_STATE_DRAG の場合、
Drag状態ですので、アイテムView の アルファ値を半透明(0.5 )にします。
Drag状態の解除は、SimpleCallback の clearView を使用します。
アイテムView の アルファ値を元(1.0 )に戻します。
:
customSimpleCallback = new CustomSimpleCallback(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.DOWN) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
:
}
@Override
public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
:
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
:
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ACTION_STATE_DRAG && viewHolder != null) {
viewHolder.itemView.setAlpha(0.5f);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setAlpha(1.0f);
:
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(customSimpleCallback);
itemTouchHelper.attachToRecyclerView(todoView);
:
予期しない動きの対処として、SimpleCallback ではなく、
SimpleCallback を継承して作成した CustomSimpleCallback を使用しています。
予期しない動きの対処
RecyclerView にスワイプ操作とドラッグ操作をアプリに実装した場合、
RecyclerView の View を破棄せず、再利用する特徴から、
Swipe操作によるアイテムの削除で、RecyclerViewの描画の際に、
アイテムが Drag状態になることがあります。
この動作の対策として、RecyclerView のアイテム描画中は、
ドラッグ操作を無効にすることで回避できます。
ドラッグ操作を無効にする場合、SimpleCallbackを使用しますが、
スコープ外からも、擽(くすぐ)ってあげる必要があるので、
SimpleCallback を継承した新しいクラスを作成します。
public class CustomSimpleCallback extends ItemTouchHelper.SimpleCallback {
private boolean drag = false;
private int dragDirs = 0;
private int swipeDirs = 0;
// コンストラクタ
public CustomSimpleCallback(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
this.dragDirs = dragDirs;
this.swipeDirs = swipeDirs;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = drag ? dragDirs : 0;
return makeMovementFlags(dragFlags, swipeDirs);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
public void setDraggable(boolean value) {
drag = value;
}
}
setDraggable を使用して、
getMovementFlags のドラッグ操作の向き(dragDirs)を制御することで、
ドラッグ操作の有効・無効を切り替えます。
ドラッグ操作を擽る
RecycelerView の描画中はドラッグ操作を無効にして、
描画が終わったらドラッグ操作を有効にします。
初回表示のアイテムタッチでドラッグ操作を有効 にするために、
onResumeのRecycelerViewの描画( updateTodoList )では、
初回( resume == 0 )のみドラッグ操作を有効にします。
onBindViewのアイテム描画で、
アイテム( imageView )のタッチをリスナーでフックして、
ドラッグ操作を有効にします。
初回の表示でアイテムがない場合( todoList.size() == 0 )も、
ドラッグ操作を有効にします。
onPause でドラッグ操作を有効にします。
初回以降 は、RecycelerViewの描画の最初で、
ドラッグ操作を無効( new CustomSimpleCallback )にして、
clearViewで有効にします。
:
private HorizontalListView todoView;
private HorizontalListView.Adapter todoAdapter;
private CustomSimpleCallback customSimpleCallback;
private ArrayList<Todo> todoList = new ArrayList<>();
private int resume = 0;
private boolean drag = false;
:
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
:
todoView = view.findViewById(R.id.todoView);
return view;
}
@Override
public void onResume() {
:
// データ読込
mainActivity.assistantsDatabaseHelper.selTodoList(true, resume == 0);
resume++;
}
@Override
public void onPause() {
if (!drag) {
customSimpleCallback.setDraggable(true);
drag = true;
}
:
super.onPause();
}
:
@SuppressLint("NotifyDataSetChanged")
public void updateTodoList(List<Todo> todos, boolean draggable) {
todoList = (ArrayList<Todo>) todos;
todoAdapter = new HorizontalListView.ArrayAdapter(todoList) {
@Override
public View getView(ViewGroup parent) {
return LayoutInflater.from(parent.getContext()).inflate(R.layout.item_horizontal2, parent, false);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindView(View view, Object data, int position) {
if (todoList.size() > position) {
ImageView imageView = view.findViewById(R.id.image2);
// イメージ表示
imageView.setOnTouchListener((v,event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (draggable && !drag) {
customSimpleCallback.setDraggable(true);
drag = true;
}
}
return false;
});
view.setAlpha(1.0f);
}
}
};
todoView.initialize(context);
todoView.setAdapter(todoAdapter);
customSimpleCallback = new CustomSimpleCallback(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.DOWN) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
todoAdapter.notifyItemMoved(viewHolder.getBindingAdapterPosition(), target.getBindingAdapterPosition());
return true;
}
@Override
public void onMoved(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, int fromPos, @NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
todoAdapter.notifyDataSetChanged();
// アイテムの入れ替え
:
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// アイテムの削除
:
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ACTION_STATE_DRAG && viewHolder != null) {
viewHolder.itemView.setAlpha(0.5f);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setAlpha(1.0f);
setDraggable(true);
drag = true;
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(customSimpleCallback);
itemTouchHelper.attachToRecyclerView(todoView);
if (!drag && draggable && todoList.size() == 0) {
customSimpleCallback.setDraggable(true);
drag = true;
}
}
:
左右にスクロールさせるため、RecyclerViewではなく、
RecyclerView を継承して作成したHorizontalListViewを使用しています。
◎AssistantsDatabaseHelper(DBアクセス)
:
private List<Todo> todoList = new ArrayList<>();
:
public void selTodoList(boolean load, boolean draggable) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
// DB読み込み
todoList = todoDao.selectTaskList();
:
} catch (Exception e) {
e.printStackTrace();
}
handler.post(() -> {
if (load) mainActivity.getHomeFragment().updateTodoList(todoList, draggable);
});
});
}
:
対処後の動画
予期しない動きの対処として、
RecycelerView を描画中はドラッグ操作を無効にして、
ACTION_STATE_DRAG を発生しないようにします。
Drag状態を解除する方法として、
NotifyDataSetChanged を実行する方法もあります。
今回は、ここまでです。
参考 :SimpleCallback
誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にあるコメントか、
こちらから、お願いいたします♪
ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

これからAndroidのアプリ開発やJavaでの開発を始めたい方へ
初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪

無料でJava言語を学べるのは、かなり魅力的♪
でも、応募資格は35歳以下です、、、
2022年12月から説明会が土曜日開催が追加されていますよ♪
説明会では、希望者に対してプログラミング体験もできるよ♪

未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪

カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪
コメント欄