Androidアプリ開発

スワイプ操作でアイテムが消えないRecyclerview

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

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

この記事のテーマ


スワイプ操作でアイテムが消えないRecyclerviewを実装する

旅行や観光のお供に最適です、360度カメラで思い出を記録しよう♪

ポイント

RecyclerViewでスワイプ操作を実装する場合、ItemTouchHelperを使った実装になります。
ItemTouchHelperのスワイプ(onSwiped)は、アイテムがリストから消滅した時点でイベント発生します。
このため、選択や削除前に確認メッセージを表示する場合に違和感があります。
今回は、スワイプ操作でアイテムがリストから消滅しない実装を紹介します。

ItemTouchHelperについて

スワイプヘルパー

スワイプ操作でアイテムがリストからを消滅しない、スワイプ操作をイベント処理できるスワイプヘルパーを作成します。
リストからを消滅しないように、スワイプのアニメーションで空白を描画することで再現します。
また、スワイプ後に元に戻すためのキューを用意します。
スワイプ操作のイベント処理は、空白を描画したことをフックするようにします。

public abstract class SwipeHelper extends CustomSimpleCallback {
    public static final int         SWIPE_HEIGHT = 40;
    private final HorizontalListView
                                    horizontalListView;
    private List<SwipeArea>         areas;
    private final GestureDetector   gestureDetector;
    private int                     swipedPos = -1;
    private float                   swipeThreshold = 0.5f;
    private final Map<Integer, List<SwipeArea>>
                                    areasBuffer;
    private final Queue<Integer>    recoverQueue;
    private final int               direction;
    @SuppressLint("ClickableViewAccessibility")
    public SwipeHelper(Context context, HorizontalListView horizontalListView, int dragDirs, int swipeDirs) {
        super(dragDirs, swipeDirs);
        this.horizontalListView = horizontalListView;
        this.direction = swipeDirs;
        this.areas = new ArrayList<>();
        this.dragDirs = dragDirs;
        this.swipeDirs = swipeDirs;
        GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapConfirmed(@NonNull MotionEvent e) {
                return true;
            }
        };
        this.gestureDetector = new GestureDetector(context, gestureListener);
        View.OnTouchListener onTouchListener = new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent e) {
                if (swipedPos < 0) return false;
                Point point = new Point((int) e.getRawX(), (int) e.getRawY());
                RecyclerView.ViewHolder swipedViewHolder = horizontalListView.findViewHolderForAdapterPosition(swipedPos);
                if (swipedViewHolder != null) {
                    View swipedItem = swipedViewHolder.itemView;
                    Rect rect = new Rect();
                    swipedItem.getGlobalVisibleRect(rect);
                    if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_MOVE) {
                        if (rect.left < point.x && rect.right > point.x) {
                            gestureDetector.onTouchEvent(e);
                        } else {
                            recoverQueue.add(swipedPos);
                            swipedPos = -1;
                            recoverSwipedItem();
                        }
                    }
                }
                return false;
            }
        };
        this.horizontalListView.setOnTouchListener(onTouchListener);
        areasBuffer = new HashMap<>();
        recoverQueue = new LinkedList<Integer>() {
            @Override
            public boolean add(Integer o) {
                if (contains(o))
                    return false;
                else
                    return super.add(o);
            }
        };
        attachSwipe();
    }

    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
        viewHolder.itemView.setAlpha(1.0f);
        int pos = viewHolder.getBindingAdapterPosition();
        if (swipedPos != pos) {
            recoverQueue.add(swipedPos);
        }
        swipedPos = pos;
        if (areasBuffer.containsKey(swipedPos)) {
            areas = areasBuffer.get(swipedPos);
            if (areas != null) areas.get(0).onSwipe(pos, direction);
        } else {
            areas.clear();
        }
        areasBuffer.clear();
        swipeThreshold = 0.5f * areas.size() * SWIPE_HEIGHT;
        recoverSwipedItem();
    }

    @Override
    public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
        return swipeThreshold;
    }

    @Override
    public float getSwipeEscapeVelocity(float defaultValue) {
        return 0.1f * defaultValue;
    }

    @Override
    public float getSwipeVelocityThreshold(float defaultValue) {
        return 5.0f * defaultValue;
    }

    @Override
    public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        int pos = viewHolder.getBindingAdapterPosition();
        float translationY = dY;
        View itemView = viewHolder.itemView;
        if (pos < 0){
            swipedPos = pos;
            return;
        }
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            if ((dY < 0 && ((direction & ItemTouchHelper.UP) == ItemTouchHelper.UP) || ((direction & ItemTouchHelper.DOWN) == ItemTouchHelper.DOWN))) {
                List<SwipeArea> buffer = new ArrayList<>();
                if (!areasBuffer.containsKey(pos)){
                    instantiateUnderlaySwipe(viewHolder, buffer);
                    areasBuffer.put(pos, buffer);
                } else {
                    buffer = areasBuffer.get(pos);
                }
                translationY = dY * Objects.requireNonNull(buffer).size() * SWIPE_HEIGHT / itemView.getHeight();
                drawAreas(c, itemView, buffer, translationY);
            }
        }
        super.onChildDraw(c, recyclerView, viewHolder, dX, translationY, actionState, isCurrentlyActive);
    }
    private void drawAreas(Canvas c, View itemView, List<SwipeArea> buffer, float dY) {
        float top  = dY < 0 ? itemView.getTop()  : 0;
        float down = dY > 0 ? itemView.getBottom() : 0;
        float swipeHeight = (-1) * dY / buffer.size();
        for (SwipeArea area : buffer) {
            down = dY < 0 ? top  - swipeHeight : down;
            top  = dY > 0 ? down - swipeHeight : top;
            area.onDraw(c, new RectF(itemView.getLeft(), dY > 0 ? down : top, itemView.getRight(), dY > 0 ? top : down));
            top  = dY < 0 ? down : top;
            down = dY > 0 ? top  : down;
        }
    }
    public void attachSwipe(){
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this);
        itemTouchHelper.attachToRecyclerView(horizontalListView);
    }
    private synchronized void recoverSwipedItem(){
        while (!recoverQueue.isEmpty()){
            @SuppressWarnings("ConstantConditions")
            int pos = recoverQueue.poll();
            if (pos > -1) {
                if (horizontalListView.getAdapter() != null) {
                    horizontalListView.getAdapter().notifyItemChanged(pos);
                }
            }
        }
    }
    public abstract void instantiateUnderlaySwipe(RecyclerView.ViewHolder viewHolder, List<SwipeArea> swipeAreas);
    public static class SwipeArea {
        private final int color;
        Context context;
        private final SwipeHelperH.onDrawListener onDrawListener;
        public SwipeArea(Context context, int color, SwipeHelperH.onDrawListener onDrawListener) {
            this.color = color;
            this.context = context;
            this.onDrawListener = onDrawListener;
        }
        public void onDraw(Canvas c, RectF rect){
            Paint p = new Paint();
            p.setColor(color);
            c.drawRect(rect, p);
            p.setColor(Color.WHITE);
        }
        public void onSwipe(int pos, int direction) {
            onDrawListener.onSwipe(pos, direction);
        }
    }
    public interface onDrawListener {
        void onSwipe(int pos, int direction);
    }
}

ItemTouchHelper.SimpleCallbackではなく、操作の無効化を追加したCustomSimpleCallbackを継承します。
SwipeHelperのコンストラクタでItemTouchHelperRecyclerViewにアタッチします。
HorizontalListViewは横スクロールタイプのRecyclerViewを継承したカスタムクラスです。

CustomSimpleCallbackクラス

public class CustomSimpleCallback extends ItemTouchHelper.SimpleCallback {
    private static final boolean    DEBUG = false;
    private static final String     TAG = CustomSimpleCallback.class.getSimpleName();
    private boolean     drag = false;
    private boolean     invalid = false;
    public int          dragDirs;
    public int          swipeDirs;
    public CustomSimpleCallback(int dragDirs, int swipeDirs) {
        super(dragDirs, swipeDirs);
        this.dragDirs = dragDirs;
        this.swipeDirs = swipeDirs;
    }

    // DRAGGABLE スイッチ //
    public void setDraggable(boolean value) {
        drag = value;
    }

    // INVALID スイッチ //
    public void setInvalid(boolean value) {
        invalid = value;
    }
    public boolean getInvalid() {
        return invalid;
    }

    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        int dragFlags = drag ? dragDirs : 0;
        return makeMovementFlags(invalid ? 0 : dragFlags, invalid ? 0 : 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) {
    }
}

スワイプ操作で確認メッセージを表示する

空白を描画したタイミングでインタフェースを使って、削除確認メッセージの表示を記述します。
ドラッグ&ドロップによるアイテムを入れ替えは、onMoveonSelectedChangedclearViewでアイテム描画、onMovedでアイテム入れ替えの更新を記述します。

    private HorizontalListView      todoView;
    private HorizontalListView.Adapter
                                    todoAdapter;
    private SwipeHelperH            swipeHelperH;
    :

    todoView = view.findViewById(R.id.todoView);
    swipeHelperH = new SwipeHelperH(context, todoView, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.DOWN) {
        @SuppressLint("NotifyDataSetChanged")
        @Override
        public void instantiateUnderlaySwipe(RecyclerView.ViewHolder viewHolder, List<SwipeArea> swipeAreas) {
            swipeAreas.add(new SwipeArea(
                    context,
                    Color.TRANSPARENT,
                    (position, direction) -> {
                        // 削除確認メッセージの表示
             :  
                        todoAdapter.notifyDataSetChanged();
                    }
            ));
        }
        @Override
        public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
            return makeMovementFlags(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) {
      // アイテムの入れ替えの更新
      : 
           todoAdapter.notifyDataSetChanged();
        }
        @Override
        public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
            if (viewHolder != null) viewHolder.itemView.setAlpha(actionState == ACTION_STATE_DRAG ? 0.5f : 1.0f);
            super.onSelectedChanged(viewHolder, actionState);
        }
        @Override
        public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
            if (viewHolder.getBindingAdapterPosition() >= 0) {
                viewHolder.itemView.setAlpha(1.0f);
                setDraggable(true);
                draggable = true;
            }
            super.clearView(recyclerView, viewHolder);
        }
    };
    :
アイテムのスワイプ操作を無効にする

CustomSimpleCallbackのgetMovementFlagsでスワイプを無効にします。

今回は、ここまでです。

スワイプ操作でアイテムが消えないRecyclervieを実装しているAndroidアプリです。

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

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

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

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

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

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

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

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

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

コメント欄

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