Android开发ViewDragHelper打造不一样的recyclerview
来源:互联网 发布:编程入门视频 编辑:程序博客网 时间:2024/05/22 05:24
概述
前面我有一篇是讲到了viewdraghelper,http://blog.csdn.net/sw950729/article/details/53352587。对viewdraghelper不了解,可以看完再说。有人说viewdraghelper这个不就是个手势处理类么,怎么打造不一样的recyclerview?不不不,不要小瞧所有的手势处理,包括那啥GestureDetector也是。但是本文重点还是用viewdraghelper处理。处理啥呢?没错,就是recyclerview的侧滑删除功能。
网上侧滑的轮子不要太多,各种swipelayout。各种开源,各种嗨~。不过被众人评价“最爱作死的人”的我。不是很喜欢直接用轮子,轮子,有2种,一种是直接用别人的轮子,意味着别人的思路强加在你身上,另一种就是自己写轮子or改轮子,把你思路强加在别人身上。我更倾向于后者,所以导致,写这个布局的时候爬了好久的坑,综合了不知道几十个甚至上百个布局来改写。最后形成了自己的布局。下面我们一步步来进行分析以及实现。
侧滑删除的效果
思路
上图应该就是我们做侧滑的效果,就如QQ的一样。屏幕内是内容,右边是影藏的控件,需要通过滑动来进行打开和关闭。那么我们正常一个控件显示整个屏幕的时候我们无法滑动这个控件,但我们通过viewdraghelper可以实现整个布局的滑动。那么,我们需要考虑另外一种情况,那就是越界问题。其实viewdraghelper用一个方法可以帮助我们很好的处理越界问题。
使用布局
侧滑应该用什么布局写?自定义listview?linearlayout?framelayout?还是viewgroup?要不我来个新鲜的?自定义recyclerview如何?好像有点夸张了- - 根据效果我觉得还是通过自定义LinearLayout进行比较合理。
大致功能
1.侧滑显示侧滑菜单
2.点击侧滑区域以外的地方影藏侧滑菜单
3.侧滑时禁止上下滑动,同时上下滑动时也禁止左右滑动
4.不会同时打开2个菜单,这里有2种效果,一个是打开另一个的时候,手指松开关闭上一个打开的item。第二种则是高仿QQ,只要有侧滑菜单下次触摸直接关闭无法打开第二个item。So,我进行了高仿QQ的处理。
5.侧滑时无法进行点击事件,需要先关闭item才能进行对应的点击事件。
分析详解
主要我们需要实现上面5点,这样就可以实现一个完美的侧滑删除~~~下面我们一步步的来分析下如何实现。
两个子view
protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 2) { throw new NullPointerException("you only need two child view!"); } itemView = getChildAt(0); hiddenView = getChildAt(1); } protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); hiddenViewWidth = hiddenView.getMeasuredWidth(); }
我们需要在onFinishInflate进行对item的获取,这个就是xml映射完成之后获取对应item。然后在onsizechange进行宽度的获取。
侧滑显示侧滑菜单
这个是完完全全用viewdraghelper来实现。话不多说,我们直接看代码:
ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { public boolean tryCaptureView(View view, int arg1) { return view == itemView; } public int clampViewPositionHorizontal(View child, int left, int dx) { if (child == itemView) { if (left > 0) { return 0; } else { left = Math.max(left, -hiddenViewWidth); return left; } } return 0; } public int getViewHorizontalDragRange(View child) { return hiddenViewWidth; } public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { if (itemView == changedView) { hiddenView.offsetLeftAndRight(dx); } else { itemView.offsetLeftAndRight(dx); } invalidate(); } public void onViewReleased(View releasedChild, float xvel, float yvel) { if (releasedChild == itemView) { if (xvel == 0 && Math.abs(itemView.getLeft()) > hiddenViewWidth / 2.0f || xvel < 0) { open(); } else { close(); } } } };
hiddenwidth是影藏区域的宽度,整个布局是item的全屏宽度。我们让这个布局可控宽度为hiddenwidth+itemwidth。这样我们就可以愉快的滑动了~而且还不会越界。
影藏侧滑菜单
我们需要点击侧滑以外的任何位置,让他影藏。那这个应该如何处理呢。我们需要通过onInterceptTouchEvent来进行拦截处理。具体代码如下:
public boolean onInterceptTouchEvent(MotionEvent event) { boolean value = helper.shouldInterceptTouchEvent(event); //if you open is not the current item,close if (!SWSlipeManager.getInstance().haveOpened(this)) { SWSlipeManager.getInstance().close(); } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downIX = event.getX(); downIY = event.getY(); break; case MotionEvent.ACTION_MOVE: moveX = event.getX(); moveY = event.getY(); if (Math.abs(moveX - downIX) > 1 || Math.abs(moveY - downIY) > 1) { value = true; } break; } return value; }
SWSlipeManager这个是记录item的状态的,我们后面在说,这段具体代码就是只要有item打开了。我们就进行关闭处理。
滑动事件处理
其实就是在onViewPositionChanged里面就行处理,参数里有dx和dy。其实这个x,y的偏移量。所以我们需要在x!=0的时候就行拦截。所以onViewPositionChanged的代码改成了这样:
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { if (dx != 0) { getParent().requestDisallowInterceptTouchEvent(true); } if (itemView == changedView) { hiddenView.offsetLeftAndRight(dx); } else { itemView.offsetLeftAndRight(dx); } invalidate(); }
禁止同时打开多个item
上面我们说到了一个管理侧滑开关的管理类。下面看看我们如何进行具体的处理。先看看我们是怎么管理的:
public class SWSlipeManager { private SWSlipeLayout swSlipeLayout; private static SWSlipeManager SWSlipeManager = new SWSlipeManager(); public static SWSlipeManager getInstance() { return SWSlipeManager; } public void setSwSlipeLayout(SWSlipeLayout swSlipeLayout) { this.swSlipeLayout = swSlipeLayout; } public void clear() { swSlipeLayout = null; } public void close() { if (swSlipeLayout != null) { swSlipeLayout.close(); } } /** * if s==null means no item is open * * @return ture means open else close */ public boolean haveOpened() { return swSlipeLayout != null; } /** * if s==null means no item is open * * @return true means two item is not the same one and one item is open */ public boolean haveOpened(SWSlipeLayout s) { return swSlipeLayout != null && swSlipeLayout == s; }}
这边我们通过来进行布局是否打开来进行处理。这边我们还要继续修改OnviewPositionChanged这个方法,修改完成如下:
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { if (dx != 0) { getParent().requestDisallowInterceptTouchEvent(true); } if (itemView == changedView) { hiddenView.offsetLeftAndRight(dx); } else { itemView.offsetLeftAndRight(dx); } if (itemView.getLeft() != 0) { SWSlipeManager.getInstance().setSwSlipeLayout(SWSlipeLayout.this); } else { SWSlipeManager.getInstance().clear(); } if (itemView.getLeft() == 0 && changeStatus != Status.Close) { changeStatus = Status.Close; } else if (itemView.getLeft() == -hiddenViewWidth && changeStatus != Status.Open) { changeStatus = Status.Open; } invalidate(); }
这边我们进行对item.getleft()来处理我们的管理类,告诉它我们是打开了还是关闭了。既然是管理类,我们需要在打开的时候告诉它我们打开了。关闭的时候告诉它已经关闭了,需要清空。所以我们的打开和关闭需写成如下:
/** * slide close */ public void close() { if (helper.smoothSlideViewTo(itemView, 0, 0)) { ViewCompat.postInvalidateOnAnimation(this); } SWSlipeManager.getInstance().clear(); } /** * slide open */ public void open() { SWSlipeManager.getInstance().setSwSlipeLayout(this); if (helper.smoothSlideViewTo(itemView, -hiddenViewWidth, 0)) { ViewCompat.postInvalidateOnAnimation(this); } }
这边既然用到了ViewCompat.postInvalidateOnAnimation(this)这个方法,所以我们一定不能少了这个方法。我记得刚开始的时候我就在这边进坑里去了。
public void computeScroll() { super.computeScroll(); // start animation if (helper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } }
这样我们是不是就算彻底解决了。不不不。我们还需要在ontouchevent里面进行判断。怎能少了这一步的拦截。少了它。前功尽弃。我们看看如何处理ontouchevent的。
public boolean onTouchEvent(MotionEvent event) { if (SWSlipeManager.getInstance().haveOpened(this)) { getParent().requestDisallowInterceptTouchEvent(true); } else if (SWSlipeManager.getInstance().haveOpened()) { getParent().requestDisallowInterceptTouchEvent(true); return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = event.getX(); downY = event.getY(); break; case MotionEvent.ACTION_MOVE: float moveX = event.getX(); float moveY = event.getY(); float dx = Math.abs(moveX - downX); float dy = Math.abs(moveY - downY); if (dx > dy) { getParent().requestDisallowInterceptTouchEvent(true); } downX = moveX; downY = moveY; break; } helper.processTouchEvent(event); return true; }
事件问题
看似解决了所以的问题。but,你们难道忘了点击事件了么?如果我在侧滑的时候点击,此时进行的是item的关闭处理,还是点击事件?按照我们的写法是啥?没错,他会同时执行。所以我们需要在进行点击时候加一个判断。
text.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (SWSlipeManager.getInstance().haveOpened()) { SWSlipeManager.getInstance().close(); } else { Toast.makeText(context, position + 1 + "", 1000).show(); } } });
这样我们的布局就算解决了。下面看看我们是如何进行删除和置顶的。
text_delete.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { delete(holder.getLayoutPosition()); SWSlipeManager.getInstance().close(); } }); text_top.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { SWSlipeManager.getInstance().close(); setTop(holder.getLayoutPosition()); } });
具体代码如下:
public void delete(int position) { list.remove(position); notifyItemRemoved(position); } public void setTop(int position) { list.add(0, list.get(position)); list.remove(position + 1); notifyDataSetChanged(); }
总结
这个布局我已经上传到了我的开源布局SWPullRecyclerLayout中。地址:SWPullRecyclerLayout
老铁们,喜欢的话,随手点个star。多谢 。
我进行了为了防止QQ类似的bug来进行了优化。那么QQ的bug是什么呢?没图说了瘠薄,下面上图:
就是当执行刷新的时候,进行侧滑,刷新成功后,侧滑没归位,刷新也没归位。而我的就没问题了。每次执行刷新和加载后,执行如下几句代码:
public void OnRefreshing() { Log.i("angel", "OnRefreshing: 正在刷新");// recycler.setIsScrollRefresh(false);// recycler.setScrollTo(recycler.getTotal(), 0);// SWSlipeManager.getInstance().close(); } public void OnLoading() { Log.i("angel", "OnLoading: 正在加载");// recycler.setIsScrollLoad(false);// recycler.setScrollTo(recycler.getTotal(), 0);// SWSlipeManager.getInstance().close(); }
先后顺序可调整。
关于SWPullRecyclerLayout,大家使用中如有一切问题,可以进行反馈,我会进行优化。也可以提议,如果合理,我也会进行后续优化。
- Android开发ViewDragHelper打造不一样的recyclerview
- Android开发Diffutils打造不一样的recyclerview
- Android开发Diffutils打造不一样的recyclerview
- Android开发Diffutils打造不一样的recyclerview
- Diffutils打造不一样的recyclerview
- Android打造不一样的EmptyView
- Android打造不一样的EmptyView
- Android打造不一样的EmptyView
- Android打造不一样的EmptyView
- android开发游记:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView
- android开发游记:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView
- android开发游记:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView
- Android自定义控件---打造不一样的FlowLayout
- Android打造不一样的圆盘签到
- 打造不一样的android log日志类
- Android打造不一样的圆盘签到
- Android打造不一样的圆盘签到
- RecyclerView 不一样的列表
- DSP28335学习笔记——McBSP配置为SPI
- val和attr和prop获取数据中的区别
- android简单的加壳流程
- 学习笔记: 源码 solver.cpp 初访
- 【枚举】洛谷 P1207 [USACO1.2]双重回文数 Dual Palindromes
- Android开发ViewDragHelper打造不一样的recyclerview
- 解决高德地图key与sha1不匹配或MD5安全码未通过问题
- 杭电4801 PocKet Cube DFS
- java 获取路径的各种方法
- PHP图片处理之图片旋转和图片翻转
- System.load 和 System.loadLibrary详解
- Android开发自定义下拉框下拉列表
- 【Android】自定义Style或Theme及自定义ActionBar
- 剑指Offer面试题10 & Leetcode191