【问题标题】:How to multi-select using drag gesture in RecyclerView ?如何在 RecyclerView 中使用拖动手势进行多选?
【发布时间】:2023-03-08 13:40:02
【问题描述】:

这是一个非常简短的问题: 最近谷歌更新了它的材料设计指南,表明多选项目应该像在谷歌照片应用程序(here)上一样:

我注意到即使你已经处于多选模式,你仍然可以在任何你想要的地方使用这个手势。

到目前为止,我所做的是处理单击项目以进行多选,但我该如何做 Google 显示的操作?

【问题讨论】:

    标签: android android-recyclerview material-design multi-select


    【解决方案1】:
    【解决方案2】:

    虽然它很复杂,并且有一些库可供使用,但如果您可以根据自己的目的开发一个库会更好(假设我们希望为 Boogle、Bookworm 或 Bejeweled 等一种类型的游戏使用此功能......)。

    所以,如果您想自己制作,这里有一些不错的提示:

    1) 完全理解触摸事件(当action down, move, up, ...将被发送;如何区分多点触摸和单点触摸)。

    2) 很好的区分onTouch和onIntercepTouch。

    3) 对 RecyclerView.OnItemTouchListener 做一些研究,因为我们要使用它。

    4) 研究的时候放一些日志知道流事件,学习触摸事件的时候不要调试。

    接下来,你开始的小例子:

    abstract public class OnItemTouchMultiDragListener implements RecyclerView.OnItemTouchListener {
            private static final int MAX_CLICK_DURATION = 200;
    
            private boolean isIn;   // Is touching still at the same item.
            private String tagTouchableView;    // View catches touch event in each item of recycler view.
    
            private int countIn;    // How many times touch event in item.
            private long timeDown;  // Time touch down on touchable view.
            protected int startPos; // Position of item to start touching.
            protected int lastPos;  // Position of item last touching in.
            protected int endPos;   // Position of item to end touching (touch up).
    
            private boolean isTouchDownAtTouchableView; // If touch down event is in a touchable view.
    
            public OnItemTouchMultiDragListener(String tagTouchableView){
                this.tagTouchableView = tagTouchableView;
            }
    
            @Override
            public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
    
                if(motionEvent.getPointerCount() > 1){
    
                    // Touch with many fingers, don't handle.
                    return false;
                }
    
                int action = motionEvent.getAction();
                if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL){
    
                    View v = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
                    RecyclerView.ViewHolder holder = null;
                    endPos = -1;
    
                    if(v != null) {
    
                        View vTouchable = v.findViewWithTag(tagTouchableView);
    
                        // If up in that item too, since we only in one item.
                        if (vTouchable != null && isInView(motionEvent, vTouchable)) {
    
                            holder = recyclerView.getChildViewHolder(v);
                            endPos = holder.getAdapterPosition();
                        }
                    }
    
                    // If touch down/ move only in one item.
                    if(countIn == 1 && isTouchDownAtTouchableView){
                        if(holder != null && endPos != -1) {
                            if (isPossiblyClick()) {
                                onClickUp(endPos, holder);
                            } else {
                                onLongClickUp(endPos, holder);
                            }
                        }
                    }else if (countIn > 1){
                        onDragMultiUp(lastPos, holder);
                    }else {
                        onUp();
                    }
    
                    // Reset touch status.
                    isIn = false;
                    isTouchDownAtTouchableView = false;
                    countIn = 0;
    
                    return false;
                }
    
                View v = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
    
                if(v != null) {
    
                    View vTouchable = v.findViewWithTag(tagTouchableView);
    
                    if(vTouchable != null && isInView(motionEvent, vTouchable)) {
    
                        RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(v);
                        int pos = holder.getAdapterPosition();
    
                        if(isIn && pos == lastPos || pos == -1){
                            // Still in the same pos or can not determine what the pos is.
                            return false;
                        }
    
                        timeDown = Calendar.getInstance().getTimeInMillis();
    
                        isIn = true;
    
                        RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(v);
                        int pos = holder.getAdapterPosition();
    
                        if(action == MotionEvent.ACTION_DOWN){
                            onDownTouchableView(pos);
                            isTouchDownAtTouchableView = true;
                        }else if(isTouchDownAtTouchableView && action == MotionEvent.ACTION_MOVE){
                            onMoveTouchableView(pos);
                        }
    
                        onInItemAndInTouchableView(motionEvent, pos);
    
                        if(countIn == 0){
                            startPos = pos;
                        }
    
                        lastPos = pos;
                        countIn ++;
                    }else {
                        isIn = false;
                        onInItemButNotInTouchable();
                    }
                }else {
                    isIn = false;
                    onOutItem();
                }
    
                return false;
            }
    
            @Override
            public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { }
    
            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean b) { }
    
            private boolean isPossiblyClick() {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - timeDown;
                return clickDuration < MAX_CLICK_DURATION;
            }
    
            private boolean isInView(MotionEvent ev, View... views) {
                Rect rect = new Rect();
                for (View v : views) {
                    v.getGlobalVisibleRect(rect);
                    Log.d("","");
    
                    if (rect.contains((int) ev.getRawX(), (int) ev.getRawY()))
                        return true;
                }
                return false;
            }
    
            public void onInItemAndInTouchableView(MotionEvent motionEvent, int pos){}
            abstract public void onDownTouchableView(int pos);
            abstract public void onMoveTouchableView(int pos);
            public void onUp(){}
            public void onClickUp(int pos, RecyclerView.ViewHolder holder){}
            public void onLongClickUp(int pos, RecyclerView.ViewHolder holder){}
            public void onDragMultiUp(int endPos, RecyclerView.ViewHolder holder){}
            public void onInItemButNotInTouchable(){}
            public void onOutItem(){}
    
            public int getStartPos() {
                return startPos;
            }
    
            public int getEndPos() {
                return endPos;
            }
        }
    

    要使用它,请执行以下操作:

    private OnItemTouchMultiDragListener onItemTouchMultiDragListener = new OnItemTouchMultiDragListener("touchable") {
    
            @Override
            public void onDownTouchableView(int pos) {
                // Write log here.
                // This is an abstract method, you must implement.
            }
    
            @Override
            public void onMoveTouchableView(int pos) {
                // Write log here.
                // This is an abstract method, you must implement.
            }
        };
    

    您可以覆盖的其他方法是:onInItemAndInTouchableViewonUponClickUponLongClickUponDragMultiUponInItemButNotInTouchableonOutItem。顺便说一句,在测试时将这些方法写入日志以了解何时调用。

    当然,在实例化OnItemTouchListener 的实例之后,我们必须将其添加到回收器视图中,例如:

    recyclerView.addOnItemTouchListener(onItemTouchMultiDragListener);
    

    您还应该注意的另一件事是回收站视图中项目的布局,这是我的,它是网格布局中的方形项目,周围有填充以将项目彼此分开(因此只有项目视图的一部分接受触摸事件,而不是填充区域):

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="90dp"
        android:layout_height="90dp"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
       <!-- Please note on the tag "touchable" which we must pass as param in constructor to let our listener know which part of the item will be listening to touch event -->
        <View
            android:tag="touchable"
            android:id="@+id/v_background"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@drawable/bkg_corner_gray"
            app:layout_constraintWidth_percent="0.78846153846"
            app:layout_constraintDimensionRatio="1"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
    
        <TextView
            android:id="@+id/tv_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="1"
            android:textSize="35sp"
            android:textColor="@color/black"
            app:layout_constraintStart_toStartOf="@id/v_background"
            app:layout_constraintEnd_toEndOf="@id/v_background"
            app:layout_constraintTop_toTopOf="@id/v_background"
            app:layout_constraintBottom_toBottomOf="@id/v_background"/>
    
    </android.support.constraint.ConstraintLayout>
    

    更新(2019 年 11 月 25 日)

    这是一个简单的例子:https://github.com/mttdat/example-multi-drag-recycleview

    【讨论】:

    • 哇。努力做得很好。有没有可以试用您的代码的示例?只是我已经继续前进了......你的努力得到了 +1。
    • 很高兴你喜欢它。有兴趣的话,我会尽快创建一个例子并通过 GitHub 分享。
    • 我不再需要它,但我只是好奇它的效果如何,我感谢那些花时间帮助他人的人
    • 工作,谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多