SlidingCheckLayout是一个滑动选中RecyclerView中Item的布局,手指滑过Item时多项选中。

作者:竹尘居士

github:https://github.com/homgwu/SlidingCheckLayout

示例

特性

  • SlidingCheckLayout继承自FrameLayout,使用时把RecyclerView放在SlidingCheckLayout里层。

  • 左右滑动时手指滑到某项即回调该项的Position,上下滑动时根据position,回调开始和结束的position。

  • 长按进入滑选模式。

实现方式

  • 在dispatchTouchEvent中检测长按和处理滑动的位置:

        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            if (!isSlidingEnable() || !isEnabled()) {
                return super.dispatchTouchEvent(event);
            }
            if (!isCanIntercept()) {
                return super.dispatchTouchEvent(event);
            }
            final int action = event.getActionMasked();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
    //                Log.i(TAG, "dispatchTouchEvent ACTION_DOWN mStartingCheck:" + mStartingCheck);
                    mInitDownY = mLastY = event.getY();
                    mInitDownX = mLastX = event.getX();
                    checkForLongClick(0, mInitDownX, mInitDownY);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
    //                Log.i(TAG, "dispatchTouchEvent ACTION_CANCEL||ACTION_UP mStartingCheck:" + mStartingCheck);
                    removeLongPressCallback();
                    mLastPosition = RecyclerView.NO_POSITION;
                    mIncrease = 0;
                    if (mStartingCheck) {
                        mStartingCheck = false;
                        return true;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
    //                Log.i(TAG, "dispatchTouchEvent ACTION_MOVE mStartingCheck:" + mStartingCheck);
                    float y = event.getY();
                    float x = event.getX();
                    final float yInitDiff = y - mInitDownY;
                    final float xInitDiff = x - mInitDownX;
                    mLastY = y;
                    mLastX = x;
                    if (!mStartingCheck && (Math.abs(yInitDiff) > mTouchSlop || Math.abs(xInitDiff) > mTouchSlop)) {
                        removeLongPressCallback();
                    }
                    if (mStartingCheck) {
                        checkSlidingPosition(x, y);
                        return true;
                    }
                    break;
            }
            boolean result = super.dispatchTouchEvent(event);
    //        Log.i(TAG, "dispatchTouchEvent super.dispatchTouchEvent result:" + result);
            return result;
        }
    • 为何要在dispatchTouchEvent中处理而不在onInterceptTouchEvent和onTouchEvent中处理呢,因为如果是一些复杂的界面,SlidingCheckLayout的某上层还有可以滑动的布局如RecyclerView,ViewPager,他们可能会在Down或Move你返回false,里层RecyclerView也返回false时截断事件(而你又不能直接都返回True,在没进入滑选模式时要保证里层的RecyclerView还可以响应点击等事件),那么SlidingCheckLayout会收不到后续的事件,而dispatchTouchEvent方法可以在SlidingCheckLayout不截断事件的情况下每次被调用到(询问是否要截断或分发到里层)。

    • 为何要在ACTION_UP时返回true,因为如果不在up时返回true那么这个up事件可能会被里层的RecyclerView响应成点击事件而多次处理点击的这个item。

  • 长按处理:

    检查长按和移除(长按的代码是从View长按源码中copy出来改改的),

       private void checkForLongClick(int delayOffset, float x, float y) {
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberPressedState();
            mHandler.postDelayed(mPendingCheckForLongPress,
                    sLongPressTime - delayOffset);
        }
        private void removeLongPressCallback() {
            if (mPendingCheckForLongPress != null) {
                mHandler.removeCallbacks(mPendingCheckForLongPress);
            }
        }
     

    进入长按:

        private final class CheckForLongPress implements Runnable {
            private float mX;
            private float mY;
            private boolean mOriginalPressedState;
            @Override
            public void run() {
                if ((mOriginalPressedState == isPressed()) && (mLastPosition = checkDownPosition(mX, mY)) != RecyclerView.NO_POSITION) {
                    if (mOnSlidingPositionListener != null) {
                        mOnSlidingPositionListener.onSlidingPosition(mLastPosition);
                    }
                    requestDisallowInterceptTouchEvent(true);
                    mStartingCheck = true;
                }
            }
            public void setAnchor(float x, float y) {
                mX = x;
                mY = y;
            }
            public void rememberPressedState() {
                mOriginalPressedState = isPressed();
            }
        }
     
  • 检查滑过的是哪个item,并回调position

       private void checkSlidingPosition(float x, float y) {
            View childViewUnder = mTargetRv.findChildViewUnder(x, y);
            if (mOnSlidingPositionListener == null || childViewUnder == null) return;
            int currentPosition = mTargetRv.getChildAdapterPosition(childViewUnder);
    //        Log.w(TAG, "checkSlidingPosition currentPosition:" + currentPosition + ",mLastPosition:" + mLastPosition);
            if (currentPosition == mLastPosition || currentPosition == RecyclerView.NO_POSITION) return;​
            if (mLastPosition != RecyclerView.NO_POSITION && Math.abs(currentPosition - mLastPosition) > 1) {
                if (mLastPosition > currentPosition) {
                    mOnSlidingPositionListener.onSlidingRangePosition(currentPosition, mIncrease > 0 ? mLastPosition : mLastPosition - 1);
                } else {
                    mOnSlidingPositionListener.onSlidingRangePosition(mIncrease < 0 ? mLastPosition : mLastPosition + 1, currentPosition);
                }
            } else {
                if ((mIncrease > 0 && mLastPosition > currentPosition) || (mIncrease < 0 && currentPosition > mLastPosition)) {
                    mOnSlidingPositionListener.onSlidingPosition(mLastPosition);
                }
                mOnSlidingPositionListener.onSlidingPosition(currentPosition);
            }
            mIncrease = currentPosition > mLastPosition ? 1 : -1;
            mLastPosition = currentPosition;
        }

    通过RecyclerView的findChildViewUnder方法用坐标找到对应的子View,再getChildAdapterPosition就可以得到子View的位置了。

使用方法

  1. 布局:

    <?xml version="1.0" encoding="utf-8"?>
        <com.homg.scl.SlidingCheckLayout
            android:id="@+id/scl"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/reminder_tv">
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </com.homg.scl.SlidingCheckLayout>
     
  2. 设置Listener:

     mSlidingCheckLayout.setOnSlidingPositionListener(this);
        @Override
        public void onSlidingPosition(int position) {
            MainEntity entity = mMainRvAdapter.getEntityByPosition(position);
            entity.setSelect(!entity.isSelect());
            mMainRvAdapter.notifyItemChanged(position);
        }
        @Override
        public void onSlidingRangePosition(int startPosition, int endPosition) {
            for (int i = startPosition; i <= endPosition; i++) {
                MainEntity entity = mMainRvAdapter.getEntityByPosition(i);
                entity.setSelect(!entity.isSelect());
            }
            mMainRvAdapter.notifyItemRangeChanged(startPosition, endPosition - startPosition + 1);
        }
     

一个滑动选中RecyclerView中Item的布局SlidingCheckLayout,手指滑过Item时多项选中。的更多相关文章

  1. 每次选中数组中的N条数据, 如果让每条数据被选中的次数做到平均??

    经常有这样的需求, 有一组数据, 每次展示其中的1条或N条,希望每条数据展示量可以做到平均. 一开始想依次展示每条数据并做记录,整组数据全展示一遍之后清除记录, 然后一直循环下去. 实现的过程中又觉得 ...

  2. Android解决RecyclerView中的item显示不全方案

    最近的项目中实现订单确定页面.需要使用ScrollView嵌套RecyclerView,当RecyclerView中的item数量比较多时,就会出现item只显示一部分数据,并没有将用户勾选的商品数量 ...

  3. RecyclerView中item无法充满的问题

    首先致谢:https://blog.csdn.net/yuanlvmao/article/details/51694211 咱们不是代码的生产者,只是代码的搬运工. 今天写了一个RecyclerVie ...

  4. WPF中的常用布局 栈的实现 一个关于素数的神奇性质 C# defualt关键字默认值用法 接口通俗理解 C# Json序列化和反序列化 ASP.NET CORE系列【五】webapi整理以及RESTful风格化

    WPF中的常用布局   一 写在开头1.1 写在开头微软是一家伟大的公司.评价一门技术的好坏得看具体的需求,没有哪门技术是面面俱到地好,应该抛弃对微软和微软的技术的偏见. 1.2 本文内容本文主要内容 ...

  5. RecyclerView中装饰者模式应用

    近段时间一直在加班,在赶一个项目,现在项目接近尾声,那么需要对过去一段时间工作内容进行复盘,总结下比较好的解决方案,积累一些经验,我认为的学习方式,是「理论-实践-总结-分享」,这一种很好的沉淀方式. ...

  6. 在RecyclerView中集成QQ汽泡一

    上次已经实现了QQ汽泡的自定义View的效果[http://www.cnblogs.com/webor2006/p/7726174.html],接着再将它应用到列表当中,这样才算得上跟QQ的效果匹配, ...

  7. 【从零开始撸一个App】RecyclerView的使用

    目标 前段时间打造了一款简单易用功能全面的图片上传组件,现在就来将上传的图片以图片集的形式展现到App上.出于用户体验考虑,加载新图片采用[无限]滚动模式,Android平台上我们优选Recycler ...

  8. 列举至少3种Support包中提供的布局或工具

    android.support.v7.widget.CardView 继承自FrameLayout并实现了圆角和阴影效果,常用于ListView或RecyclerView中Item布局的根节点 示例代 ...

  9. 在RecyclerView中集成QQ汽泡二

    上次已经将GooView集成到RecyclerView当中了[http://www.cnblogs.com/webor2006/p/7787511.html],但是目前还有很多问题,下面先来运行看一下 ...

随机推荐

  1. replace into 浅析之一

    一 介绍  在笔者支持业务过程中,经常遇到开发咨询replace into 的使用场景以及注意事项,这里做个总结.从功能原理,性能和注意事项上做个说明.二 原理2.1 当表中存在主键但是不存在唯一建的 ...

  2. React Native学习(一)——搭建开发环境

    第一次接触React Native,首先搭建环境,过程还算顺利,不过也遇到了些问题,这里简单记录下来.中文官网(http://reactnative.cn/docs/0.47/getting-star ...

  3. [51nod1232]完美数

    如果一个数能够被组成它的各个非0数字整除,则称它是完美数.例如:1-9都是完美数,10,11,12,101都是完美数,但是13就不是完美数(因为13不能被数字3整除). 现在给定正整数x,y,求x和y ...

  4. [bzoj1316] 树上的询问

    裸的点分治.. 及时把已经确定的询问清掉就能快不少.时间复杂度O(nlogn*p) #include<cstdio> #include<iostream> #include&l ...

  5. MYSQL数据库增量备份

    MySQL数据库增量备份,在这之前修改我们的数据库配置文件/etc/my.cnf开启bin-log日志功能即可.接下来是我参考了下网上的一些方法,自己写的,主要还是要能学到他的一些思路和方法. #fu ...

  6. c语言优化

    虽然对于优化C代码有很多有效的指导方针,但是对于彻底地了解编译器和你工作的机器依然无法取代,通常,加快程序的速度也会加大代码量.这些增加的代码也会影响一个程序的复杂度和可读性,这是不可接受的,比如你在 ...

  7. 高可用高性能分布式文件系统FastDFS实践Java程序

    在前篇 高可用高性能分布式文件系统FastDFS进阶keepalived+nginx对多tracker进行高可用热备 中已介绍搭建高可用的分布式文件系统架构. 那怎么在程序中调用,其实网上有很多栗子, ...

  8. excel数据导出新妙招

    之前在做项目的时候需要将数据库中的数据导出为excel表格一遍打印查阅,在网上找了很多插件也没有找到自己理想的好用的插件(也就是说没有找到令我满意的插件),最近在学习a标签的相关知识时理解到,a标签不 ...

  9. 浅谈MySQL集群高可用架构

    前言 高可用架构对于互联网服务基本是标配,无论是应用服务还是数据库服务都需要做到高可用.对于一个系统而言,可能包含很多模块,比如前端应用,缓存,数据库,搜索,消息队列等,每个模块都需要做到高可用,才能 ...

  10. linux下删除.svn的方法

    find ./ -type d -name ".svn" | xargs rm -rf