zoukankan      html  css  js  c++  java
  • 一个滑动选中RecyclerView中Item的布局SlidingCheckLayout,手指滑过Item时多项选中。

    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);
          }
       
  • 相关阅读:
    .NET西安社区 [拥抱开源,又见 .NET] 第二次活动简报
    HttpClient在.NET Core中的正确打开方式
    西安活动 | 2019年1月13号 "拥抱开源, 又见.NET" 线下交流活动报名进行中
    基于IdentityServer4 实现.NET Core的认证授权
    dnSpy 强大的.Net反编译软件
    .NET Core中实现AOP编程
    Why DDD and layered architecture
    领域驱动设计之-前言
    load average 定义(网易面试)
    sendfile“零拷贝”和mmap内存映射
  • 原文地址:https://www.cnblogs.com/homg/p/8405008.html
Copyright © 2011-2022 走看看