Android RecyclerView —— 实现侧滑菜单

news/2024/11/29 19:43:47/

    RecyclerView侧滑删除可以通过ItemTouchHelper来实现,但侧滑菜单栏没有原生的实现方式,我就尝试重写RecyclerView的onInterceptEvent和onTouchEvent方法来实现侧滑菜单,下面来讲下我的实现思路。文章底部有源码,已封装可直接使用。

一、实现效果图

二、实现目标

  1. 快速左滑或者将itemView侧滑至菜单栏显示过半则打开菜单栏;
  2. 快速右滑或者将itemView侧滑至菜单栏显示过未半则关闭菜单栏;
  3. 点击菜单栏按钮或点击其他itemView,关闭菜单栏;
  4. 竖直滑动RecyclerView,关闭菜单栏;
  5. 打开其他itemView的菜单栏,关闭之前itemView的菜单栏;
  6. 松手后的菜单栏滑动平缓
  7. 不影响原先RecyclerView的功能

三、思路分析

  • 理清触碰事件分发

先来简单分析下触碰事件分发,我们可以把RecyclerView看作ViewGroup,把其itemView看作View(itemView应为ViewGroup,但此处只用考虑itemView及其子View是否消费事件)。

假设ViewGroup不做拦截操作,View.onTouchEvent返回true:
ACTION_DOWN:ViewGroup.dispatchTouchEvent→ViewGroup.onInterceptTouchEvent→View.dispatchTouchEvent→View.onTouchEvent;
ACTION_MOVE:ViewGroup.dispatchTouchEvent→ViewGroup.onInterceptTouchEvent→View.dispatchTouchEvent→View.onTouchEvent;
ACTION_UP:ViewGroup.dispatchTouchEvent→ViewGroup.onInterceptTouchEvent→View.dispatchTouchEvent→View.onTouchEvent;

假设ViewGroup在onInterceptTouchEvent中对ACTION_MOVE事件做拦截操作,且View.onTouchEvent返回true:
ACTION_DOWN:ViewGroup.dispatchTouchEvent→ViewGroup.onInterceptTouchEvent→View.dispatchTouchEvent→View.onTouchEvent;
ACTION_MOVE:ViewGroup.dispatchTouchEvent→ViewGroup.onInterceptTouchEvent→ViewGroup.onTouchEvent;
再次ACTION_MOVE:ViewGroup.dispatchTouchEvent→ViewGroup.onTouchEvent;
ACTION_UP:ViewGroup.dispatchTouchEvent→ViewGroup.onTouchEvent;

详细的触碰事件体系分发可查看:事件分发机制

    综上分析,只要ACTION_DOWN事件到达RecyclerView,该事件必走onInterceptTouchEvent()方法,所以在该方法中可做一些信息准备工作。应在RecyclerView的onInterceptTouchEvent()方法做水平滑动判断拦截,一旦拦截则后续事件不会再往下分发,后续事件一到RecyclerView的dispatchTouchEvent()方法就会进入到onTouchEvent()方法。

  • 判断好RecyclerView是竖直滑动还是侧滑

    RecyclerView是水平滑动可根据多方面判断:

  1. 水平滑动距离大于竖直滑动距离,且大于系统最小滑动距离(根据事件中的坐标计算判断)
  2. 水平速度大于竖直滑动速度,且大于规定最小滑动速度(使用VelocityTracker计算判断)
  • 松开手时实现平缓滑动

    松手时使菜单栏平缓滑动到指定区,这需使用Scroller的startScroll()方法,重写RecyclerView的computeScroll()方法计算并通过itemview.scrollTo(x,y)方法移动itemView的位置,一直刷新直到itemView到达终点位置。

public methods:

voidabortAnimation()

停止动画.

booleancomputeScrollOffset()

计算出新的位置,如果返回true,则动画尚未完成.

final intgetCurrX()

返回滚动中的当前X偏移量.

final intgetCurrY()

返回滚动中的当前Y偏移量.

final intgetFinalX()

返回滚动的X轴结束位置.

final int

getFinalY()

返回滚动的Y轴结束位置.

fnal booleanisFinished()

返回Scroller是否已完成滚动.

voidstartScroll(int startX, int startY, int dx, int dy, int duration)

通过提供的起始点、要行驶的距离和滚动的时间来开始滚动.

四、代码实现

    @Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {int x = (int) e.getX();int y = (int) e.getY();addVelocityEvent(e);switch (e.getAction()){case MotionEvent.ACTION_DOWN:......//获取点击区域所在的itemViewmMoveView = (ViewGroup) findChildViewUnder(x, y);//在点击区域以外的itemView开着菜单,则关闭菜单if (mLastView != null && mLastView != mMoveView && mLastView.getScrollX() != 0){closeMenu();}//获取itemView中菜单的宽度(规定itemView中为两个子View)if (mMoveView != null && mMoveView.getChildCount() == 2){mMenuWidth = mMoveView.getChildAt(1).getWidth();}else {mMenuWidth = -1;}break;case MotionEvent.ACTION_MOVE:mVelocity.computeCurrentVelocity(1000);int velocityX = (int) Math.abs(mVelocity.getXVelocity());int velocityY = (int) Math.abs(mVelocity.getYVelocity());int moveX = Math.abs(x - mFirstX);int moveY = Math.abs(y - mFirstY);//满足如下条件其一则判定为水平滑动://1、水平速度大于竖直速度,且水平速度大于最小速度//2、水平位移大于竖直位移,且大于最小移动距离//必需条件:itemView菜单栏宽度大于0,且recyclerView处于静止状态(即并不在竖直滑动和拖拽)boolean isHorizontalMove = (Math.abs(velocityX) >= MINIMUM_VELOCITY && velocityX > velocityY || moveX > moveY && moveX > mTouchSlop) && mMenuWidth > 0 && getScrollState() == 0;if (isHorizontalMove){//设置其已处于水平滑动状态,并拦截事件mMoving = true;return true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:releaseVelocity();//itemView以及其子view触发触碰事件(点击、长按等),菜单未关闭则直接关闭if (mLastView != null && mLastView.getScrollX() != 0){mLastView.scrollTo(0,0);}break;default:break;}return super.onInterceptTouchEvent(e);}

    首先在ACTION_DOWN的时候用RecyclerView的findChildViewUnder()方法获得所点区域的itemView,mLastView为末次水平滑动的itemView,若这次点击区域不再是末次操作的itemView,且末次的itemView菜单栏还打开着则关闭它,并获取当前操作的itemView的菜单栏宽度;在ACTION_MOVE时,利用VelocityTracker计算速度和坐标点位移差进行判断是否为水平滑动,满足条件则进行拦截走onTouchEvent()方法;ACTION_UP即响应itemView点击事件,关闭未关闭的菜单栏。

    @Overridepublic boolean onTouchEvent(MotionEvent e) {int x = (int) e.getX();int y = (int) e.getY();addVelocityEvent(e);switch (e.getAction()){case MotionEvent.ACTION_DOWN:break;case MotionEvent.ACTION_MOVE://若已处于水平滑动状态,则随手指滑动,否则进行条件判断if (mMoving){int dx = mLastX - x;//让itemView在规定区域随手指移动if (mMoveView.getScrollX() + dx >= 0 && mMoveView.getScrollX() + dx <= mMenuWidth) {mMoveView.scrollBy(dx, 0);}mLastX = x;return true;}else {......//根据水平滑动条件判断,是否让itemView跟随手指滑动boolean isHorizontalMove = (Math.abs(velocityX) >= MINIMUM_VELOCITY && velocityX > velocityY|| moveX > moveY && moveX > mTouchSlop) && mMenuWidth > 0 && getScrollState() == 0;if (isHorizontalMove) {int dx = mLastX - x;//让itemView在规定区域随手指移动if (mMoveView.getScrollX() + dx >= 0 && mMoveView.getScrollX() + dx <= mMenuWidth) {mMoveView.scrollBy(dx, 0);}mLastX = x;//设置正处于水平滑动状态mMoving = true;return true;}}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (mMoving) {//先前没结束的动画终止,并直接到终点if (!mScroller.isFinished()){mScroller.abortAnimation();mLastView.scrollTo(mScroller.getFinalX(),0);}mMoving = false;//已放手,即现滑动的itemView成了末次滑动的itemViewmLastView = mMoveView;mVelocity.computeCurrentVelocity(1000);int scrollX = mLastView.getScrollX();//若速度大于正方向最小速度,则关闭菜单栏;若速度小于反方向最小速度,则打开菜单栏//若速度没到判断条件,则对菜单显示的宽度进行判断打开/关闭菜单if (mVelocity.getXVelocity() >= MINIMUM_VELOCITY){mScroller.startScroll(scrollX, 0, -scrollX, 0, Math.abs(scrollX));}else if (mVelocity.getXVelocity() <= -MINIMUM_VELOCITY){int dx = mMenuWidth - scrollX;mScroller.startScroll(scrollX, 0, dx, 0, Math.abs(dx));} else if (scrollX > mMenuWidth / 2) {int dx = mMenuWidth - scrollX;mScroller.startScroll(scrollX, 0, dx, 0, Math.abs(dx));} else {mScroller.startScroll(scrollX, 0, -scrollX, 0, Math.abs(scrollX));}invalidate();} else if (mLastView != null && mLastView.getScrollX() != 0){//若不是水平滑动状态,菜单栏开着则关闭closeMenu();}releaseVelocity();break;default:break;}return super.onTouchEvent(e);}

    因为ACTION_DOWN事件必会走onInterceptonTouchEvent()方法,所以已在其方法中做好全部处理,该方法中就不必再对ACTION_DOWN事件做处理;在ACTION_MOVE时,先进行判断是否已处于水平滑动状态,若是则直接让itemView跟随手指滑动,若不是则进行水平滑动判断;在ACTION_UP时,若处于水平滑动状态,之前的Scroller动画还没结束,则让其终止并直接到达终点值,因松手后当前这个itemView也马上要使用Scroller进行动画了,避免冲突,后续工作就是对末次itemView进行赋值,并先根据速度判断开关菜单栏,速度条件不满足则对itemView的菜单栏显露宽度进行判断,如果不是水平滑动时还有菜单栏开着,在最后放手时关闭菜单栏。

五、源码地址

    源码地址:支持侧滑菜单栏的RecyclerView

    使用规范:LayoutManager为LinearLayoutManager,且为竖直滑动,RecyclerView的itemView中需有两个子View,第二个子View即为菜单栏。

    如有问题欢迎指出。

 


http://www.ppmy.cn/news/278587.html

相关文章

Android RecyclerView 侧滑菜单、长按拖拽

SwipeRecyclerView 支持侧滑菜单、长按拖拽、Header、Footer、Loading(加载更多) 注意&#xff1a;不持支com.android.support项目。如果要使用可以下载源码自己AndroidStudio转support使用。 预览 Fix 2022.7.25.1 1.分页插入数据类型异常bug 2.扩展类及其监听修改 3.包名修…

RecyclerView侧滑删除

自定义 RecyclerView 侧滑删除&#xff0c;下面是左滑删除和右滑删除的效果图&#xff1a; CSDN下载地址&#xff1a;https://download.csdn.net/download/wuqingsen1/10782610 GitHub下载地址&#xff1a;https://github.com/wuqingsen/RecyclerViewSide 下面是主要内容&…

Android-屏幕左右侧滑(二)

第二种方式我们介绍的是使用Android源生控件android.support.v4.widget.DrawerLayout来实现屏幕的左侧滑和右侧滑&#xff08;其中包括点击侧滑和手动滑动侧滑&#xff09;&#xff0c;还可以用代码来控制打开和关闭手动侧滑: 先付上两张效果图供参考&#xff0c;如下&#xf…

android studio 侧滑菜单,Android Studio实现侧滑菜单(最佳)

在CSDN中我们会看到很多侧滑的案例,但是有的不是少这个就是少那个,很浪费时间,所以我们要仔细看看代码全不全。在多种APP里,比如QQ的主页面就有向右滑动出现菜单栏的情况,今天就来看一下如何实现这种功能。 我的工程文件布局如下: 我们可以看到,侧滑菜单主要由两个页面组…

Android BaseRecyclerViewAdapterHelper拖动和侧滑删除

1.适配器 adapter继承BaseItemDraggableAdapte public class ItemDragAdapter extends BaseItemDraggableAdapter<BsInventoryBeanSub, BaseViewHolder> {//BsInventoryBeanSub是我的Bean类public ItemDragAdapter(List<BsInventoryBeanSub> data) {super(R.layo…

android侧滑菜单的监听,Android侧滑菜单控件DrawerLayout使用详解

DrawerLayout是Android V4包下一个带有侧滑功能的布局控件,可以根据手势展开与隐藏侧边栏,也可以随着侧边栏的点击改变主界面区的内容。并且只需要按照DrawerLayout规定的布局格式进行布局,即可实现左右侧滑效果。 一、约定的抽屉布局 DrawerLayout的布局一般分为三个部分:…

Android-侧滑菜单(三)

新整理的仿QQ侧滑菜单实现的例子&#xff0c;使用android.support.v4.widget.DrawerLayout和android.support.design.widget.NavigationView实现的&#xff0c;下面先上两张效果图&#xff1a; 效果图也看到了&#xff0c;那么咱们废话不多说&#xff0c;直接上代码&#xff1a…

Compose 实现页面侧滑返回

最近研究了一下&#xff0c;使用Compose如何去做类似ios的全屏侧滑返回效果&#xff0c;下面展示成果&#xff1a; 总共需要几个功能进行搭配使用&#xff1a; 1.Accompaint的Pager模块 2.通过反射使当前窗口透明以及不透明 思路分析&#xff1a; pager类似于android原生的…