RecycleView的左滑实现

最终的效果图是这样的


要实现这样的一个效果,用到的关键技术:
自定义view的基本知识+事件处理+其它知识

一.右边的操作view

1.数据的组装

我们可以把右边的操作选项抽象出来数据对象即可,对于老司机的你们一看就懂。

public class SwipeMenuItem {private static final int TITLE_SIZE = 20;//spprivate static final int WIDTH = 80;//dpprivate int id;private Context mContext;private String title;private Drawable icon;private Drawable background;private int titleColor;private int titleSize;private int width;public SwipeMenuItem(Context context) {mContext = context;//设置默认值DisplayMetrics dm = mContext.getResources().getDisplayMetrics();titleColor = Color.WHITE;titleSize = TITLE_SIZE;width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WIDTH, dm);}}

2. SwipeMenuView的简单扩展(自定义view的一种吧)

public class SwipeMenuView extends LinearLayout implements View.OnClickListener {private SwipeMenuLayout mLayout;private SwipeMenu mMenu;private OnMenuItemClickListener mOnMenuItemClickListener;private int position;public int getPosition() {return position;}public void setPosition(int position) {this.position = position;}public SwipeMenuView(SwipeMenu menu) {super(menu.getContext());setOrientation(LinearLayout.HORIZONTAL);mMenu = menu;List<SwipeMenuItem> items = mMenu.getMenuItems();int id = 0;for (SwipeMenuItem item : items) {addItem(item, id++);}}private void addItem(SwipeMenuItem item, int id) {LayoutParams params = new LayoutParams(item.getWidth(),LayoutParams.MATCH_PARENT);LinearLayout parent = new LinearLayout(getContext());parent.setId(id);parent.setGravity(Gravity.CENTER);parent.setOrientation(LinearLayout.VERTICAL);parent.setLayoutParams(params);parent.setBackgroundDrawable(item.getBackground());parent.setOnClickListener(this);addView(parent);if (item.getIcon() != null) {parent.addView(createIcon(item));}if (!TextUtils.isEmpty(item.getTitle())) {parent.addView(createTitle(item));}}private ImageView createIcon(SwipeMenuItem item) {ImageView iv = new ImageView(getContext());iv.setImageDrawable(item.getIcon());return iv;}private TextView createTitle(SwipeMenuItem item) {TextView tv = new TextView(getContext());tv.setText(item.getTitle());tv.setGravity(Gravity.CENTER);tv.setTextSize(item.getTitleSize());tv.setTextColor(item.getTitleColor());return tv;}@Overridepublic void onClick(View v) {if (mOnMenuItemClickListener != null && mLayout.isOpen()) {mOnMenuItemClickListener.onMenuItemClick(position, mMenu, v.getId());}}public interface OnMenuItemClickListener {void onMenuItemClick(int position, SwipeMenu menu, int index);}public void setOnMenuItemClickListener(OnMenuItemClickListener mOnMenuItemClickListener) {this.mOnMenuItemClickListener = mOnMenuItemClickListener;}public void setLayout(SwipeMenuLayout mLayout) {this.mLayout = mLayout;}
} 

说白了就是继承LinearLayout 加了一个回调接口,对于老司机的你们一看又懂了。对于SwipeMenuLayout是什么,我们后面会讲的,别着急吗?嘻嘻

二.RecyclerView.Adapter的处理

  • 我们本着在不影响用户原有的adapter的基础上尽量不改或者少改。
    对于RecyclerView的Adapter 我们都是继承RecyclerView.Adapter。
  • 主要是重写onCreateViewHolder和onBindViewHolder方法。
  • 对于onBindViewHolder方法完美不错任何处理,也没有必要做。
  • 主要是onCreateViewHolder方法,这个方法返回是一条item的布局ui,对于我们这个效果在不改动优惠正常的view布局的情况下,我们可以这么做呢????
  • 咦! 我们可以在原来的基础上再套一层FrameLayout. 是的,没错,老司机!!
  @Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//根据数据创建右边的操作viewSwipeMenuView menuView = swipeMenuBuilder.create();//包装用户的item布局SwipeMenuLayout swipeMenuLayout = SwapWrapperUtils.wrap(parent, R.layout.item, menuView, new BounceInterpolator(), new LinearInterpolator());MyViewHolder holder = new MyViewHolder(swipeMenuLayout);setListener(parent, holder, viewType);return holder;}

SwapWrapperUtils.wrap 这个方法这里就不说了就是LayoutInflater加载布局。

三.SwipeMenuLayout-view的设计

1. 继承自FrameLayout

讲用户的itemview这里我们叫Contentview,以及操作view我们叫MenuView,添加到这个FrameLayout上

   setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));addView(mContentView);addView(mMenuView);
2.设置初始状态

我们要测量menuview的宽,高度就是Contentview的高。
我们要布局menuview,在Contentview的右侧。
如图:

   @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//测量mMenuView的宽,高为mContentView的高mMenuView.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {mContentView.layout(0, 0, getMeasuredWidth(),mContentView.getMeasuredHeight());//在mContentView的右侧mMenuView.layout(getMeasuredWidth(), 0,getMeasuredWidth() + mMenuView.getMeasuredWidth(),mContentView.getMeasuredHeight());}
3.控制滑动

在android中根据滑动来控制view有好多种,这里我们用layout方法
主要就是在recycleview滑动时找到其中一条的位置position在ontouch方法中合适的时机将事件传到该view上。什么时候触发这个方法呢
,下文会说recycleview的处理事件。
我们写一个方法将事件传递到此view上来控制menuView和contentView

    public void  onSwipe(MotionEvent event) {mGestureDetector.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = (int) event.getX();isFling = false;break;case MotionEvent.ACTION_MOVE://按下去-当前的位置int dis = (int) (mDownX - event.getX());//menuView打开状态dis+mMenuView宽if (state == STATE_OPEN) {dis += mMenuView.getWidth();}swipe(dis);break;case MotionEvent.ACTION_UP://快速滑动,或者超过了mMenuView宽的一半则打开,否则关闭if (isFling || (mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {smoothOpenMenu();} else {smoothCloseMenu();}break;}}/*** 更改位置* @param dis dis*/private void swipe(int dis) {//mContentView的最大为mMenuView的宽if (dis > mMenuView.getWidth()) {dis = mMenuView.getWidth();}//mContentView-left的最小值为0即正常值if (dis < 0) {dis = 0;}//设置完mContentView的left就可以得出right以及mMenuView的left和right了//主要是left,right//left 最大值为-mMenuView.getWidth()mContentView.layout(-dis, mContentView.getTop(),mContentView.getWidth() - dis, getMeasuredHeight());mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(),mContentView.getWidth() + mMenuView.getWidth() - dis,mMenuView.getBottom());}
4.打开与关闭

借助computeScroll方法来不停的layout设置位置,代码都对于位置的计算有注释,生怕解释不清楚。

@Overridepublic void computeScroll() {//让mMenuView打开if (state == STATE_OPEN) {//是否停止了滑动if (mOpenScroller.computeScrollOffset()) {swipe(mOpenScroller.getCurrX());//重绘UIpostInvalidate();}} else {//让mMenuView关闭//mContentView的if (mCloseScroller.computeScrollOffset()) {//mBaseX为当前的mContentView的left,可以结合swipe(mBaseX - mCloseScroller.getCurrX());postInvalidate();}}}/*** 平滑的关闭mMenuView*/public void smoothCloseMenu() {state = STATE_CLOSE;mBaseX = -mContentView.getLeft();//关闭是我们要让mContentView的慢慢的减小,//mCloseScroller.getCurrX()的范围是(0,mBaseX)mCloseScroller.startScroll(0, 0, mBaseX, 0, DURATION);postInvalidate();}/*** 平滑的打开mMenuView*/public void smoothOpenMenu() {state = STATE_OPEN;//其实我们这里是用到了Scroller类产生的值(当然借助Interpolator来实现不同的值渐变,从而实现不同的效果)//打开的时候mContentView的left从当前的-mContentView.getLeft()到mMenuView.getWidth()//在computeScroll方法中 swipe(mOpenScroller.getCurrX());即可//mOpenScroller.getCurrX()的范围是(-mContentView.getLeft(),mMenuView.getWidth())//-mContentView.getLeft()为正值mOpenScroller.startScroll(-mContentView.getLeft(), 0,mMenuView.getWidth(), 0, DURATION);postInvalidate();}

四.RecyclerView的事件处理

首先我们要明白一点就是:我们要不影响用户原来的item的点击与长按等事件。

我们肯定要重新事件的拦截与处理方法。即onInterceptTouchEvent
与onTouchEvent方法。我们需要在这2个方法里做如下的处理。

  1. 找到按下去的那一条
  2. 什么时候拦截各种down,move,up事件
  3. 处理各种down,move,up事件
1.找到按下去的那一条
//找到当前点击坐标下的所处于SwapRecyclerView的位置int mFirstPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();int count = getChildCount();for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() == View.VISIBLE) {child.getHitRect(mTouchFrame);//判断是否点击到该控件上if (mTouchFrame.contains(x, y)) {mTouchPosition = mFirstPosition + i;break;}}}

找到了pos位置就可以 View view = getChildAt(mTouchPosition - mFirstPosition);
来获取那个view了,就可以进行事件的处理了。
child.getHitRect方法 ,我们看下sdkapi的注释:

  /**找到控件占据的矩形区域的矩形坐标* Hit rectangle in parent's coordinates*返回的矩形   控件占据的矩形区域* @param outRect The hit rectangle of the view.*/public void getHitRect(Rect outRect) {if (hasIdentityMatrix() || mAttachInfo == null) {outRect.set(mLeft, mTop, mRight, mBottom);} else {final RectF tmpRect = mAttachInfo.mTmpTransformRect;tmpRect.set(0, 0, getWidth(), getHeight());getMatrix().mapRect(tmpRect); // TODO: mRenderNode.mapRect(tmpRect)outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,(int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);}}
2.onInterceptTouchEvent 拦截 onTouch的处理 的搞基生活
down拦截的时候:
  1. menuView处于打开且点击的不在menu区域
  2. 达到了滑动的临界值
  3. 这些情况都要交要我们处理,需要拦截(reutrn true),交给ontouch方法
  //找到了if (mTouchPosition != -1) {//通过position得到item的viewHolder,并判断合法性View view = getChildAt(mTouchPosition - mFirstPosition);RecyclerView.ViewHolder viewHolder = getChildViewHolder(view);if (viewHolder.itemView instanceof SwipeMenuLayout) {//menuView处于打开且点击的不在menu区域if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getmMenuView(), event)) {//拦截事件,交给自己的onTouch方法处理.return true;}mTouchView = (SwipeMenuLayout) view;} else {throw new RuntimeException("viewHolder.itemView  must be SwipeMenuLayout layout");}//将事件交给SwipeMenuLayout处理down事件mTouchView.onSwipe(event);}//down事件,如果没有打开menu,则不拦截,仍然交给系统return handled;

然后在onTouchEven方法里处理down:

 case MotionEvent.ACTION_DOWN://如果当前是处于打开的且用户按下去正好是打开menu的那行if (mTouchPosition == oldPos && mTouchView != null&& mTouchView.isOpen()) {mTouchState = TOUCH_STATE_X;mTouchView.onSwipe(event);return true;} else {//如果不是直接关闭if (mTouchView != null && mTouchView.isOpen()) {mTouchView.smoothCloseMenu();mTouchView = null;return super.onTouchEvent(event);}}break;
move拦截的时候:

达到滑动的临界值就可以拦截了return true了。
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

    case MotionEvent.ACTION_MOVE:float dy = Math.abs((event.getY() - mDownY));float dx = Math.abs((event.getX() - mDownX));//达到了滑动的临界值if (Math.abs(dy) > mTouchSlop || Math.abs(dx) > mTouchSlop) {if (mTouchState == TOUCH_STATE_NONE) {if (Math.abs(dy) > mTouchSlop) {//上下滑动的mTouchState = TOUCH_STATE_Y;} else if (dx > mTouchSlop) {//左右滑动的mTouchState = TOUCH_STATE_X;if (mOnSwipeListener != null) {mOnSwipeListener.onSwipeStart(mTouchPosition);}}}return true;//拦截事件,交给自己的onTouch方法处理.}```
然后在onTouchEven方法里处理move:如果是左右我们才处理,否则拜拜了您。``` case MotionEvent.ACTION_MOVE://左右滑动交给mTouchView处理,事件消费了if (mTouchState == TOUCH_STATE_X) {if (mTouchView != null) {mTouchView.onSwipe(event);}event.setAction(MotionEvent.ACTION_CANCEL);super.onTouchEvent(event);return true;}break;
up事件的处理

最后up事件就简单了不需要拦截,无非就是TOUCH_STATE_X状态交给我们之前的SwipeMenuLayout处理打开还是关闭,以及 将一些变量的恢复为初始化状态。
到此整个实现就完了。

这里只分析一些核心的关键技术,其它的都能看懂。

代码下载地址:

https://github.com/ta893115871/SwapMenuRecyclerView

RecycleView的左滑实现相关推荐

  1. android 左滑右滑,Android仿滴答清单左滑右滑效果

    直接上效果图 记录仿写滴答清单App 过程中的技术点 本文分为以下章节,读者可按需阅读: 1.自定义RecycleItemTouchHelper 2.实现滴答清单左滑右滑效果 3.RecycleVie ...

  2. 微信小程序左滑删除效果的实现完整源码附效果图

    效果图: 功能描述,小程序列表左滑删除功能的实现完整源代码实现: <view wx:for='{{friends}}' wx:key="" wx:if='{{groupTyp ...

  3. 带你实现开发者头条(二) 实现左滑菜单

    title: 带你实现开发者头条(二) 实现左滑菜单 tags: 左滑菜单,android 自带侧滑,DrawerLayout grammar_cjkRuby: true --- 今天开始模仿开发者头 ...

  4. 高仿微信实现左滑显示删除button功能

    在实际项目中删除列表中的某一项是很常见的功能.传统的做法能够使用长按监听器等,而如今流行的做法是左滑弹出删除button,微信,QQ等都是这么做的,以下做一个演示样例,代码例如以下: 主页面MainA ...

  5. android listview左滑删除

    之前,自己使用listview一直是长按删除,不过发现qq的消息和ios的都是侧滑删除,觉得效果很好,于是自己就想做一个侧滑删除.在网上找了些资料,有很多不是我理想的侧滑删除,最后还是找到了一个不错的 ...

  6. html仿微信滑动删除,使用Vue实现移动端左滑删除效果附源码

    左滑删除在移动端是很常见的一种操作,常见于删除购物车中的商品,删除收藏夹中文章等等场景.我们只需要手指按住要删除的对象,然后轻轻向左滑动,便会出现删除按钮,然后点击删除按钮即可删除对象. 点击下载源码 ...

  7. ios 开发设置左滑退出_苹果铃声怎么设置自己的歌?教你用手机快速搞定!

    苹果手机铃声怎么设置为自己喜欢的歌曲?由于iOS系统的封闭性,想要给苹果手机更换铃声不像在安卓手机一样那么方便,看到网上很多教程也是需要使用电脑才能完成铃声的设置,有没有不复杂的方法来直接帮我们更换手 ...

  8. Android源码解析--SwipeMenuListView仿QQ聊天左滑

    版权声明:本文为博主原创文章,转载请标明出处. https://blog.csdn.net/lyhhj/article/details/50612714 绪论: 好久没写博客了,最近比较懒,不想写博客 ...

  9. html左滑效果图,前端福利——左滑右滑,绝对是你见过的最简单的写法 - 你猜猜看...

    上个月使用bootstrap和seajs搭建了前端通用框架,就是为了使代码分块话,js和css直接通过配置就可调用,这样既方便了以后的开发,又方便了效率! 先看下框架图形吧 example就是手机端经 ...

最新文章

  1. C#ListView控件添加Checkbox复选框并获取选中的数目,检查checkbox是否勾选
  2. 分析 JDK 源码丨Java Thread
  3. mysql show full processlist;_mysql show full processlist 详解
  4. OS X开发:NSProgressIndicator进度指示器控件
  5. php类中引函数变量,一个非线性差分方程的隐函数解
  6. 学习pytorch: 深度学习入门建议
  7. maven错误相关(整理中)
  8. excel公式里用html,Excel公式中{}是什么意思?要如何用?
  9. windows ghost备份
  10. ESP8266学习笔记(3)——GPIO接口使用
  11. 开发质量问题复盘总结-pua性质的标题
  12. java 多线程 数据重复,java 多线程 出现数据重复调用有关问题
  13. 计算机网络通信协议常见问题
  14. Latex表格线宽修改方法以及内容左对齐。
  15. HTML5基本结构及标签
  16. 75 道 JavaScript 面试题
  17. 弱口令实验室招新赛Writeup
  18. 我国改革开放和现代化建设中一些实际问题的思考
  19. 汽车模具设计与制造技术
  20. teamviewer linux重启服务,Teamviewer在Linux下无法启动?

热门文章

  1. 第六章_循环神经网络(RNN)
  2. Centos7安装NVIDIA的驱动的坑
  3. 如何在秋招中拿到offer?
  4. spring mvc 中自定义404页面在IE中无法显示favicon.ico问题的解决方法。
  5. base64与图片互换
  6. 全面掌握ping命令(三) ping命令防火墙设置
  7. ArchLinux pacman 提高俩倍下载速度方法
  8. VC多线程编程(转)
  9. TypeScript -脚本编程语言
  10. matlab如果不想立即在,科学计算与MATLAB 1.5