最是人间留不住,朱颜辞镜花辞树。这篇文章主要讲述Android开发ViewDragHelper打造不一样的recyclerview相关的知识,希望能为你提供帮助。
概述 前面我有一篇是讲到了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();
}
【Android开发ViewDragHelper打造不一样的recyclerview】先后顺序可调整。
关于SWPullRecyclerLayout, 大家使用中如有一切问题, 可以进行反馈, 我会进行优化。也可以提议, 如果合理, 我也会进行后续优化。
推荐阅读
- 命令行运行Android Robotium自动化用例或单元测试用例
- 关于 APP定制的选择,亲力亲为和外包谁能更胜一筹
- Android中字体颜色的设置
- $apply方法(触发脏检查机制)
- mybatis generator自动生成model,mapper等文件
- Android 自定义Activity的Dialog
- office2013 激活工具Microsoft Toolkit怎样用?_其它办公
- PPT中自动换片时间与持续时间有啥区别?_PowerPoint专区
- 怎样在ppt中给SmartArt图形添加动画效果?添加动画效果的办法_PowerPoint专区