如何在RecyclerView中使用拖动手势进行多选?

时间:2016-12-17 14:29:32

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

这是一个非常简短的问题: 最近谷歌已经更新了其材料设计指南,显示多项选择应该与Google-Photos应用程序(here)类似:

enter image description here

我注意到即使您已经处于多选模式,您仍然可以在任何地方使用此手势。

到目前为止我所做的是处理点击项目以进行多项选择,但我如何处理Google展示的内容?

2 个答案:

答案 0 :(得分:2)

答案 1 :(得分:2)

尽管它很复杂并且有一些库可供使用,但是如果您可以为自己的目的开发一个库会更好(让我们想象一下,我们希望针对Boogle,Bookworm或Bejeweled等一种类型的游戏使用此功能...)。 / p>

因此,如果您想自己做,这里是一些入门的好技巧:

1)了解完全触摸事件(当向下,向上,向上,向下发送动作时;如何区分多点触摸和单点触摸)。

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.
        }
    };

您可以覆盖的其他方法包括:onInItemAndInTouchableViewonUponClickUponLongClickUponDragMultiUponInItemButNotInTouchable,{{ 1}}。顺便说一句,在测试时写这些方法的日志,以了解何时调用。

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

OnItemTouchListener

您还需要注意的一件事是,在回收站视图中项目的布局,这是我的,它是网格布局中的正方形项目,周围填充着彼此分开的项目(因此,仅项目视图的一部分接受触摸事件,而不是填充区域):

recyclerView.addOnItemTouchListener(onItemTouchMultiDragListener);

--------------编辑----------------

下面是一个简单的示例:https://github.com/mttdat/example-multi-drag-recycleview