Androidアプリ開発

RecyclerViewの並び替えと削除 (Drag&Swipe)

この記事は約18分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


RecyclerViewのアイテムの並び替えと削除で発生する予期しない動きに対処する

RecyclerViewは、Andoroidアプリで動的リストを作成するライブラリです。
このライブラリの特徴として、スクロール時にViewを破棄せず、再利用します。
リソースの有効活用、パフォーマンスの改善、応答性の向上など、リスト型式のViewを作成する際に、必須のライブラリです。

RecyclerViewは、便利なライブラリですが、Viewを再利用する特徴から、予期しない動きに嵌ることがあります。
RecyclerViewでアイテムの並び替えと削除を実装した際の予期しない動きの対処について、説明します。

予期しない動き(Swipeでアイテム削除すると、アイテムがDrag状態になる現象)の対処前

Drag状態を通知するユーザインタフェース

Drag状態をユーザに通知する仕掛けとして、Drag状態のアイテムを半透明にするUIがよく使用されます。
Drag状態を判別する方法としては、SimpleCallbackonSelectedChangedを使用します。
actionStateACTION_STATE_DRAGの場合、アイテムのアルファ値を半透明(0.5)にします。
Drag状態の解除は、SimpleCallbackclearViewを使用します。
アイテムのアルファ値を通常(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の描画中はドラッグ操作を無効に、描画が終わったらドラッグ操作を有効にします。

初回表示のタッチでドラッグ操作を有効にするために、onResumeRecycelerViewの描画(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を使用します。 

customSimpleCallback.setDraggableは、1回だけ実行するのがポイントです。 

◎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を実行する方法もあります。

予期しない動き(Swipeでアイテム削除すると、アイテムがDrag状態になる現象)の対処後

今回は、ここまでです。

参考 : SimpleCallback

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちら
から、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適かと、まずは無料相談から♪

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

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

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

参考になったら、💛をポッチとしてね♪

スポンサーリンク
msakiをフォローする
スポンサーリンク

コメント欄

タイトルとURLをコピーしました