Android仿微信列表滑动删除 如何实现滑动列表SwipeListView
接,本篇主要讲如何实现滑动列表swipelistview。
上篇完成了滑动控件swipeitemview,这个控件是一个自定义的viewgroup,作为列表的一个item,为列表提供一些方法让这个swipeitemview能滑动其视图内容,同时滑动过程中会有顺滑的动画效果。而本篇讲的swipelistview则是这个列表的具体实现了。当然啦,这个swipelistview继承自listview,为了实现我们需要的功能,重点就是重写listview的ontouchevent()以及onintercepttouchevent()这个方法了。先说ontouchevent():
@override public boolean ontouchevent(motionevent ev) { //if user had not set mswipeitemviewid, not handle any touch event if(mswipeitemviewid == -1) return super.ontouchevent(ev); if(mcancelmotionevent && ev.getaction() == motionevent.action_down) { //ev.setaction(motionevent.action_cancel); logutil.log("swipelistview.ontouchevent(), cancel action_down"); hideshowingitem(); return true; } else if(mcancelmotionevent && ev.getaction() == motionevent.action_move) { if(mswipeitemview.getcurrentscrollx() > 0) { mswipeitemview.computescroll(); //mswipeitemview.scrollby(-1, 0); } return true; } else if(mcancelmotionevent && ev.getaction() == motionevent.action_up) { mcancelmotionevent = false; return true; } switch(ev.getaction()) { case motionevent.action_down: { logutil.log("action_down"); if(mtracker == null) { mtracker = velocitytracker.obtain(); } else { mtracker.clear(); } mactiondownx = ev.getx(); mactiondowny = ev.gety(); mlastmotionx = ev.getx(); mlastmotiony = ev.gety(); }break; case motionevent.action_move: { //if the scroll distance at x-axis or y-axis less than the //touch_slop, do not handle the event motionevent.action_move if(math.abs(ev.getx() - mactiondownx) < touch_slop || math.abs(ev.gety() - mactiondowny) < touch_slop) break; float curx = ev.getx(); float cury = ev.gety(); int distancex = (int)(mlastmotionx - curx); int distancey = (int)(mlastmotiony - cury); if(mscrolldirection == direction_unknow && math.abs(distancey) <= math.abs(distancex)) mscrolldirection = direction_horizontal; else if(mscrolldirection == direction_unknow && math.abs(distancey) > math.abs(distancex)) mscrolldirection = direction_vertical; //if listview is scrolling vertical, do not handle the touch event if(mscrolldirection == direction_vertical) break; int lastpos = pointtoposition((int)mactiondownx, (int)mactiondowny); int firstvisibleitempos = getfirstvisibleposition() - getheaderviewscount(); int factpos = lastpos - firstvisibleitempos; mitemview = getchildat(factpos); if(mitemview != null) { mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid); if(mswipeitemview.getslidingview() != null && mswipeitemview.getscrollx() <= mswipeitemview.getslidingview().getwidth() && mswipeitemview.getscrollx() >= 0) { if(mswipeitemview.getscrollx() + distancex > mswipeitemview.getslidingview().getwidth()) distancex = mswipeitemview.getslidingview().getwidth() - mswipeitemview.getscrollx(); else if(mswipeitemview.getscrollx() + distancex < 0) distancex = -mswipeitemview.getscrollx(); mswipeitemview.scrollby(distancex, 0); } mlastshowingpos = lastpos; ev.setaction(motionevent.action_cancel); } mlastmotionx = curx; mlastmotiony = cury; }break; case motionevent.action_up: { logutil.log("action_up"); if(mtracker != null) { mtracker.clear(); mtracker.recycle(); mtracker = null; } //reset the mscrolldirection to direction_unknow mscrolldirection = direction_unknow; //reset the mcancelmotionevent to false mcancelmotionevent = false; //ensure if the showing item need open or hide if(mlastshowingpos != -1) ensureifitemopenorhide(); }break; case motionevent.action_cancel: { hideshowingitem(); }break; } return super.ontouchevent(ev); }
上面代码,首先分析用户滑开一个item的操作,这个操作以action_down起始,一系列的action_move,以action_up作为结束,所以,在action_down事件里面,我们先记录下最开始的事件位置mactiondownx和mactiondowny;然后再action_move事件里面,我们先要进行判断,这个判断分两步,第一步,判断这个action_move事件下,当前事件的位置curx和cury在x轴上以及y轴上和action_down里面记录的位置的距离是否已经超过touch_slop的值,这个值是android用来判断是否应该进行一次滑动的阈值,第二步,我们要进一步判断用户是纵向滑动这整个列表还是左右滑动某个item,这里的逻辑判断就简单点处理,若是超过touch_slop阈值的情况下,x轴方向上距离大于y轴的,我们就认为用户是左右滑动单个item,反之则是纵向滑动整个列表,这里我们用三种状态区分,direction_unknow表示当前的滑动操作还没有进行判断左右滑动还是纵向滑动,direction_horizontal表示当前滑动操作判定为左右滑动,direction_vertical表示判定为纵向滑动,一旦滑动操作被判定了,则在action_up处理前,我们都认为用户是做同一方向的滑动;action_up事件里面,重置滑动操作状态为direction_unknow以便下一次的判定,以及这次action_up事件处理的时候,如果当前滑开的item的位置mlastshowingpos不为-1,则表示当前是一次滑开的操作,这次仔细想想,用户可能在并没有完全滑开这个item的状态下手就离开屏幕了,这时候我们就应该要判断此时这个被滑动的item是应该完全打开又或者是关闭,这里的逻辑判断是item已经滑开的距离超过它本身宽度的一半的话,就完全打开它,否则就关闭它,ensureifitemopenorhide()的具体代码如下:
private void ensureifitemopenorhide() { if(mlastshowingpos != -1) { int firstvisibleitempos = getfirstvisibleposition() - getheaderviewscount(); int factpos = mlastshowingpos - firstvisibleitempos; mitemview = getchildat(factpos); if(mitemview != null) { mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid); if(mswipeitemview.getslidingview() != null && mswipeitemview.getscrollx() >= mswipeitemview.getslidingview().getwidth() / 2) { openshowingitem(); } else if(mswipeitemview.getslidingview() != null) { hideshowingitem(); } } } }
那第一次的用户滑动操作的逻辑判定我们就算处理完了。接下来是第二次的,为什么说第二次呢,第一次用户滑开了某单个的item,使其处于打开的状态下,再一次触摸屏幕,这次我们则要再一次进行判定,其一,如果action_down发生的位置在item滑开显示出来的button的范围内,表示当前用户是点击这个button,这样我们就不做额外处理,直接交由列表默认的逻辑处理;其二,如果action_down发生的位置不在item滑开后显示出来的button范围内,怎表示当前用户只是操作列表的其他范围,这里我们就关闭当前打开了的item,并取消后续的touch事件,这里的话,我们就要截获这个actioin_down事件了,需要重写listview的onintercepttouchevent()方法,代码如下:
public boolean onintercepttouchevent(motionevent ev) { //if user had not set mswipeitemviewid, not handle any touch event if(mswipeitemviewid == -1) return super.onintercepttouchevent(ev); if(mlastshowingpos != -1 && ev.getaction() == motionevent.action_down && !isclickchildview(ev)) { logutil.log("swipelistview.onintercepttouchevent(), intercept action_down"); mcancelmotionevent = true; return true; } else if(mlastshowingpos == -1 && ev.getaction() == motionevent.action_down) { return true; } return super.onintercepttouchevent(ev); }
上面的mcancelmotionevent是用来在ontouchevent()里面判断是否需要取消后续touch事件的标志,那期间,如何判断当前的action_down事件是否发生在button的范围内呢,使用如下代码:
private boolean isclickchildview(motionevent event) { if(mlastshowingpos != -1) { int firstvisibleitempos = getfirstvisibleposition() - getheaderviewscount(); int factpos = mlastshowingpos - firstvisibleitempos; mitemview = getchildat(factpos); if(mitemview != null) { mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid); view slidingview = mswipeitemview.getslidingview(); if(slidingview != null) { int[] slidingviewlocation = new int[2]; slidingview.getlocationonscreen(slidingviewlocation); int left = slidingviewlocation[0]; int right = slidingviewlocation[0] + slidingview.getwidth(); int top = slidingviewlocation[1]; int bottom = slidingviewlocation[1] + slidingview.getheight(); return (event.getrawx() > left && event.getrawx() < right && event.getrawy() > top && event.getrawy() < bottom); } } } return false; }
剩下的,就是如何打开或者关闭一个item了,代码如下:
private void openshowingitem() { if(mlastshowingpos != -1) { int firstvisibleitempos = getfirstvisibleposition() - getheaderviewscount(); int factpos = mlastshowingpos - firstvisibleitempos; mitemview = getchildat(factpos); if(mitemview != null) { mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid); if(mswipeitemview.getslidingview() != null) mswipeitemview.scrolltowithanimation( mswipeitemview.getslidingview().getwidth(), 0); } } } private void hideshowingitem() { if(mlastshowingpos != -1) { int firstvisibleitempos = getfirstvisibleposition() - getheaderviewscount(); int factpos = mlastshowingpos - firstvisibleitempos; mitemview = getchildat(factpos); if(mitemview != null) { mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid); mswipeitemview.scrolltowithanimation(0, 0); } mlastshowingpos = -1; } }
上面的scrolltowithanimation()方法就是上一篇博客里面我们实现了的移动item并使其带有动画效果的方法了。
这样,整个仿微信滑动删除操作的总体实现方案就解释完毕了,具体一些细节的话可以查看这个工程的源码,源码我已经上传到了我的github主页上:https://github.com/youngleeforeverboy/slidinglistviewplus。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: ASP.NET数据绑定的记忆碎片实现代码
下一篇: Spring中实现定时调度的几种方法