この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、記事の最後で紹介します)
RecyclerViewは、Andoroidアプリで動的リストを作成するライブラリです。
このライブラリの特徴として、スクロール時にViewを破棄せず、再利用します。
リソースの有効活用、パフォーマンスの改善、応答性の向上など、リスト型式のViewを作成する際に、必須のライブラリです。
RecyclerViewは、便利なライブラリですが、Viewを再利用する特徴から、予期しない動きに嵌ることがあります。
RecyclerViewでアイテムの並び替えと削除を実装した際の予期しない動きの対処について、説明します。
Drag状態を通知するユーザインタフェース
Drag状態をユーザに通知する仕掛けとして、Drag状態のアイテムを半透明にするUIがよく使用されます。
Drag状態を判別する方法としては、SimpleCallbackのonSelectedChangedを使用します。
actionStateがACTION_STATE_DRAGの場合、アイテムのアルファ値を半透明(0.5)にします。
Drag状態の解除は、SimpleCallbackのclearViewを使用します。
アイテムのアルファ値を通常(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操作によるアイテムの削除で、アイテムが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# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、まずは無料相談から♪
未経験者からプログラマーを目指すのに最適かと、まずは無料カウンセリングから♪
カリキュラムとサポートがしっかりしています、お得なキャンペーンとかいろいろやっています♪
ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、まずは無料オンライン相談から♪
参考になったら、💛をポッチとしてね♪