こんにちは、まっさん(@Tera_Msaki)です。
この記事は 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 の場合、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# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪
未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪
カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪
ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪
参考になったら、💛をポッチとしてね♪
コメント欄