[笨笨的方法] 实现IOS列表的滑动删除效果
2014-04-04 10:17
731 查看
一、背景
在做项目的时候,有一个需求,在两级列表中,实现类似于IOS的滑动删除效果,大体如下图:
![](http://img.blog.csdn.net/20140404093134890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdWVyeXVlcnl1ZXJ5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
但有两点不太一样的地方:上层界面,是随手势滑动的;下层界面在上层被滑走后露出来。
老大让我实现这个功能时,我想这个功能应该很简单啊,我就准备这样来做了:
1.写一个对应每行的View类,本身支持滑动,这个应该不难写。
2.让ExpandableListVIew的使用上述的View作为childView。
这样很简单就实现了嘛。
万万没想到的是,这个类写好了,我把它放在一个ListView中先试了一下,每行左右滑动没有问题,但是每一行点不了!即使设置了onItemClickListener,也监听不到事件!
我回想了一下,发现我把这个问题想简单了:一个支持滑动的类,必然会重写onTouch()来处理对它的触摸事件,但是把这个View放到List里,就会带来这样的问题,onTouch都被View消耗(consume)了,ListView的点击事件就无法触发了。
我们这儿用到的触摸动作分为:DOWN, MOVE和UP,Android中触摸动作是层层传递的,并在某一层被消耗掉。
ListView的点击需要接收一个DOWN和一个UP,这样构成单击;而我自己的View,需要先接收一个DOWN,后续才会接收到MOVE和UP,这就形成了一个矛盾:把DOWN给List,我的View就滑不动;把DOWN给View,List就点不了,听上去真的有点蛋蛋的忧伤...
二、怎么解决这个问题呢?
通过看原来我们代码的实现和自己在网上查找方法,找到两种解决办法:
1. 参照SwipeListView(https://github.com/47deg/android-swipelistview)这一开源工程,它为ListView实现了滑动功能,解决方案是,对ListView和Item的onInterceptTouchEvent和onTouch事件进行了很详细地动作判定和操作(把笔者给看晕了),但笔者需要的是ExpandableListView(啊魂淡),而且看起代码来也有点力不从心,所以干脆就放弃了,有兴趣的同学可以直接使用,或参照修改后使用。
2. 受到一段代码的启发(这段代码的功能是,通过点击在ListView上的x,y,获得ListView对应的position),楼主就想,能不能通过点击在ExpandableListView上的x,y,获得ExpandableListView对应的groupPosition和childPosition呢,能获得这两个position,点击事件不就很easy了么?
三、动起手来骚年们
1. 通过x,y获得groupPosition和childPosition,新建一个类ExpandableListView2继承于ExpandableListView,并在其中添加方法:
2.什么时候获得x和y呢,当然要在ExpandableListView2的onInterceptTouchEvent中了
onInterceptTouchEvent的作用是intercept touchEvent,就是让父布局来决定,是否截断touchEvent向下传递。
以我们这个问题来说,父布局是不需要截断事件的,我们只在里面记录事件的x和y就可以了,所以代码是这样的:
3.真正的点击事件
/***************************************** 至此ExpandableListView2准备就绪,只欠东风 *****************************************/
4.东风在哪?谁来调用ExpandableListView2的performLastClick()? 当然是我们可滑动的View了!
滑动功能我们就不过多关注了,核心代码如下:
其中,ACTION_DOWN时,记录点击事件的x,y,起始时间等;ACTION_MOVE时,滑动上层界面。
重点在于ACTION_UP/ACTION_CANCEL,在这儿,记录了滑动距离、速度和时间,交给了finishSlide()方法去处理,finishSlide()如下:
finishSlide()主要对三种情况做判断:单击、快速的滑动(fling)和其它情况。
单击我们调用父(ExpandableListView2)的performLastClick()。
滑动,我们根据方向和速度,决定是向左划还是向右划开上层界面。
其它情况下,我们根据上层界面距离哪边更近,让它自己完成划动。
其中一些临街值的确定(都是很科学的)
至此,这问题算是得到了解决,大体总结一下:
我们定制了两个View :
1. ExpandableListView2,可以自己记录最后一次点击的groupPosition/childPosition,并提供一个点击功能。
2. 可滑动的ItemView,可以处理滑动、点击事件,如果判定为点击事件,则交给ExpandableListView2处理。
这种方法算是一种比较笨的方法,ExpandableListView2和ItemView之间耦合比较大,必须要配合使用,但也是无奈。
如果大家有更好更优雅的解决方案,不妨提出来共享,谢谢!
最后上一张效果图
![](http://img.blog.csdn.net/20140404102052656?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdWVyeXVlcnl1ZXJ5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
相关代码下载链接:http://download.csdn.net/detail/ueryueryuery/7144855
没分的同学可以发邮件至ueryueryuery@163.com,说明需要哪份代码,LOL
在做项目的时候,有一个需求,在两级列表中,实现类似于IOS的滑动删除效果,大体如下图:
但有两点不太一样的地方:上层界面,是随手势滑动的;下层界面在上层被滑走后露出来。
老大让我实现这个功能时,我想这个功能应该很简单啊,我就准备这样来做了:
1.写一个对应每行的View类,本身支持滑动,这个应该不难写。
2.让ExpandableListVIew的使用上述的View作为childView。
这样很简单就实现了嘛。
万万没想到的是,这个类写好了,我把它放在一个ListView中先试了一下,每行左右滑动没有问题,但是每一行点不了!即使设置了onItemClickListener,也监听不到事件!
我回想了一下,发现我把这个问题想简单了:一个支持滑动的类,必然会重写onTouch()来处理对它的触摸事件,但是把这个View放到List里,就会带来这样的问题,onTouch都被View消耗(consume)了,ListView的点击事件就无法触发了。
我们这儿用到的触摸动作分为:DOWN, MOVE和UP,Android中触摸动作是层层传递的,并在某一层被消耗掉。
ListView的点击需要接收一个DOWN和一个UP,这样构成单击;而我自己的View,需要先接收一个DOWN,后续才会接收到MOVE和UP,这就形成了一个矛盾:把DOWN给List,我的View就滑不动;把DOWN给View,List就点不了,听上去真的有点蛋蛋的忧伤...
二、怎么解决这个问题呢?
通过看原来我们代码的实现和自己在网上查找方法,找到两种解决办法:
1. 参照SwipeListView(https://github.com/47deg/android-swipelistview)这一开源工程,它为ListView实现了滑动功能,解决方案是,对ListView和Item的onInterceptTouchEvent和onTouch事件进行了很详细地动作判定和操作(把笔者给看晕了),但笔者需要的是ExpandableListView(啊魂淡),而且看起代码来也有点力不从心,所以干脆就放弃了,有兴趣的同学可以直接使用,或参照修改后使用。
2. 受到一段代码的启发(这段代码的功能是,通过点击在ListView上的x,y,获得ListView对应的position),楼主就想,能不能通过点击在ExpandableListView上的x,y,获得ExpandableListView对应的groupPosition和childPosition呢,能获得这两个position,点击事件不就很easy了么?
三、动起手来骚年们
1. 通过x,y获得groupPosition和childPosition,新建一个类ExpandableListView2继承于ExpandableListView,并在其中添加方法:
/** * 通过position,找到对应的groupPosition和childPosition */ private Positions getPositionsByPosition(ExpandableListAdapter adapter, int position) { Positions result = new Positions(); if (position >= 0) { int p = position + 1; for (int group = 0; group < adapter.getGroupCount(); group++) { // 减去组 if (p - 1 <= 0) { result.groupPos = group; result.childPos = -1; break; } else { p = p - 1; } // 减去组成员 int childrenCount = isGroupExpanded(group) ? adapter .getChildrenCount(group) : 0; if (p - childrenCount <= 0) { result.groupPos = group; result.childPos = p - 1; break; } else { p = p - childrenCount; } } } return result; } /** * 用于保存group和child的position的容器类 */ public class Positions { private int groupPos = -1; private int childPos = -1; public boolean isGroup() { return groupPos != -1 && childPos == -1; } public boolean isChild() { return groupPos != -1 && childPos != -1; } @Override public String toString() { return "(" + groupPos + ", " + childPos + ")"; } }
2.什么时候获得x和y呢,当然要在ExpandableListView2的onInterceptTouchEvent中了
onInterceptTouchEvent的作用是intercept touchEvent,就是让父布局来决定,是否截断touchEvent向下传递。
以我们这个问题来说,父布局是不需要截断事件的,我们只在里面记录事件的x和y就可以了,所以代码是这样的:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 取得group/child position if (ev.getAction() == MotionEvent.ACTION_DOWN && getExpandableListAdapter() != null) { int x = (int) ev.getX(); int y = (int) ev.getY(); // 位置保存下来 mLastPosition = pointToPosition(x, y); mAdapter = (ExpandableListAdapter) getExpandableListAdapter(); mLastGroupAndChildPosition = getPositionsByPosition(mAdapter, mLastPosition); Log.d("UERY", "mLastPosition=" + mLastPosition + " (group,child)=" + mLastGroupAndChildPosition); } return super.onInterceptTouchEvent(ev); }每对ExpandableListView2进行点击,一个Positions对象就会被记录下来(保存于mLastGroupAndChildPosition),这个Positions对象中的group/child position,将作为我们处理Item点击事件的依据!
3.真正的点击事件
/** * 执行一次点击事件,position取上次ACTION_DOWN所点到的位置 */ public void performLastClick() { if (mLastPosition != -1 && mLastGroupAndChildPosition.isChild()) { if (mOnChildClickListener != null) { mOnChildClickListener.onChildClick(this, getChildAt(mLastPosition), mLastGroupAndChildPosition.groupPos, mLastGroupAndChildPosition.childPos, 0); // TODO id is // invalid } } }mOnChildClickListener就是我们通过ExpandableListView.setOnChildClickListener()设置进来的监听器。
/***************************************** 至此ExpandableListView2准备就绪,只欠东风 *****************************************/
4.东风在哪?谁来调用ExpandableListView2的performLastClick()? 当然是我们可滑动的View了!
滑动功能我们就不过多关注了,核心代码如下:
// 上次划动的X private float mLastX; // 本次划动开始的X private float mStartX; // 本次划动开始的时间 private long mStartTime; @Override public boolean onTouch(View v, MotionEvent event) { // get touch event for upper layer switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { // 开始手动划动 mManualSliding = true; mAutoSliding = false; mLastX = event.getRawX(); mStartX = event.getRawX(); mStartTime = System.currentTimeMillis(); return true; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { final int action = event.getAction(); // 完成划动 float endX = event.getRawX(); long costTime = System.currentTimeMillis() - mStartTime; // 划动距离 int flingDistance = (int) (endX - mStartX); // 划动速度 float velocityX = flingDistance / 0.001f / costTime; mManualSliding = false; finishSlide(action, flingDistance, costTime, velocityX); return true; } case MotionEvent.ACTION_MOVE: { if (mManualSliding) { float nowRawX = event.getRawX(); float xDiff = nowRawX - mLastX; mLastX = nowRawX; LayoutParams lp = (LayoutParams) v.getLayoutParams(); int newRightMargin = (int) (lp.rightMargin - xDiff); if (newRightMargin < 0) { newRightMargin = 0; } else if (newRightMargin > mMaxSlideDistance) { newRightMargin = mMaxSlideDistance; } lp.setMargins((int) -newRightMargin, 0, (int) newRightMargin, 0); v.setLayoutParams(lp); } return true; } default: break; } return super.onTouchEvent(event); }
其中,ACTION_DOWN时,记录点击事件的x,y,起始时间等;ACTION_MOVE时,滑动上层界面。
重点在于ACTION_UP/ACTION_CANCEL,在这儿,记录了滑动距离、速度和时间,交给了finishSlide()方法去处理,finishSlide()如下:
/** * 处理划动动作事件完成 * * @param startX * @param endX */ private void finishSlide(int action, int flingDistance, long costTime, float velocityX) { /* * action System.out.println("action: " + action); * System.out.println("flingDistance: " + flingDistance); * System.out.println("velocityX: " + velocityX); * System.out.println("costTime: " + costTime); * System.out.println(" "); */ if (action == MotionEvent.ACTION_UP) { if (Math.abs(flingDistance) <= mTouchSlop && costTime < DOUBLE_TAP_TIMEOUT) { // 判定为单击 mParent.performLastClick(); } else if (Math.abs(velocityX) >= mMinimumFlingVelocity && Math.abs(velocityX) <= mMaximumFlingVelocity) { // 判定为fling if (velocityX < 0) { autoSlide2Left(); } else { autoSlide2Right(); } } } // 手动拖动&其它情况 LayoutParams lp = (LayoutParams) mUpperLayer.getLayoutParams(); int rightMargin = lp.rightMargin; if ((flingDistance < 0 && rightMargin >= mMaxSlideDistance / 3) || (flingDistance > 0 && rightMargin > mMaxSlideDistance / 3 * 2)) { // 1.意图向左划,且已划出超过下层视图宽度1/3; // 2.意图向右划,但未超出下层视图宽度1/3; // 做左划处理 autoSlide2Left(); } else { // 其它情况做右划处理 autoSlide2Right(); } }
finishSlide()主要对三种情况做判断:单击、快速的滑动(fling)和其它情况。
单击我们调用父(ExpandableListView2)的performLastClick()。
滑动,我们根据方向和速度,决定是向左划还是向右划开上层界面。
其它情况下,我们根据上层界面距离哪边更近,让它自己完成划动。
其中一些临街值的确定(都是很科学的)
// 取得触摸事件判定临界值 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
至此,这问题算是得到了解决,大体总结一下:
我们定制了两个View :
1. ExpandableListView2,可以自己记录最后一次点击的groupPosition/childPosition,并提供一个点击功能。
2. 可滑动的ItemView,可以处理滑动、点击事件,如果判定为点击事件,则交给ExpandableListView2处理。
这种方法算是一种比较笨的方法,ExpandableListView2和ItemView之间耦合比较大,必须要配合使用,但也是无奈。
如果大家有更好更优雅的解决方案,不妨提出来共享,谢谢!
最后上一张效果图
相关代码下载链接:http://download.csdn.net/detail/ueryueryuery/7144855
没分的同学可以发邮件至ueryueryuery@163.com,说明需要哪份代码,LOL
相关文章推荐
- Android Design ItemTouchHelper实现酷炫列表的移行和滑动删除效果
- Ionic 2 实现列表滑动删除按钮的方法
- ExpandableListView 和SwipeLayout 实现双层列表 ,childitem 滑动删除效果
- Android ListView 侧滑效果实现(滑动展开、滑动删除)
- jQuery实现仿腾讯视频列表分页效果的方法
- JQuery.HoverDir库基本使用方法,实现图片滑动动画效果
- Android初级,实现网易云音乐歌曲列表界面效果,播放界面效果,ListView,ViewPager方法详解
- SwipeListView 详解 实现微信,QQ等滑动删除效果
- 详解SwipeListView框架实现微信\QQ滑动删除效果
- jQuery实现仿腾讯视频列表分页效果的方法
- Android 使用NineOldAndroids实现绚丽的ListView左右滑动删除Item效果
- IOS 开发使用UITableView 实现滑动 删除等多个按钮
- 【IOS】实现IOS版的抽屉效果(点击,拖拽滑动)
- 高仿微信对话列表滑动删除效果
- Android 使用NineOldAndroids实现绚丽的ListView左右滑动删除Item效果
- Android 仿微信对话列表滑动删除效果
- Android 仿微信对话列表滑动删除效果
- 精致的H5 列表侧滑组件。H5页面侧滑删除、修改的功能效果实现!
- Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果
- 仿ios短信列表滑动出现删除按钮