手势滑动结束Activity基本功能的实现(一)
喜欢听音乐的朋友可能都看过天天动听这款 app, 这款 app 有一个亮点就是在切换页面(fragment)的时候可以通过手势滑动来结束当前页面,这里先说一下,我为什么会这么关心这个功能呢,因为前两天 pm说我们即将开始做的这款app 也要实现页面能通过手势滑动来结束的功能,所以我就拿着这款 app 滑了一上午;但是我要实现的跟天天动听这款 app又有点不同,细心观察的朋友可能会发现,天天动听是 fragment 之间的切换,而我这里要实现的是 activity 之间的切换,不过,不管是哪种,最终效果都是一样,就是页面能随着手势的滑动而滑动,最终达到某个特定条件,结束此页面。
要实现这个功能其实也不是特别难,这里我把这个功能的实现分为了以下两个步骤:
1、识别手势滑动自定义viewgroup 的实现
2、实现自定义 viewgroup 和 activity 绑定
根据以上两个步骤,我们发现,这其中涉及到的知识点有:android 事件处理机制、自定义 view(viewgroup)的实现,activity window的知识,在开发的过程中还涉及到activity 主题的配置。android 事件处理和自定义 view 都在我前面的 blog 中有讲到,如果不了解的朋友可以去看看。下面开始按步骤来实现功能
一、自定义 viewgroup
这个 viewgroup 的功能只要是对事件的拦截,能够实现手势滑动效果;显示 activity 的内容包括 actionbar 和内容区。
1、实现测量和布局
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { /*获取默认的宽度*/ int width = getdefaultsize(0, widthmeasurespec); /*获取默认的高度*/ int height = getdefaultsize(0, heightmeasurespec); /*设置viewgroup 的宽高*/ setmeasureddimension(width, height); /*获取子 view 的宽度*/ final int contentwidth = getchildmeasurespec(widthmeasurespec, 0, width); /*获取子view 的高度*/ final int contentheight = getchildmeasurespec(heightmeasurespec, 0, height); /*设置子view 的大小*/ mcontent.measure(contentwidth, contentheight); }
@override protected void onlayout(boolean changed, int l, int t, int r, int b) { final int width = r - l; final int height = b - t; mcontent.layout(0, 0, width, height); }
因为每个 activity 都只有一个 layout,所以这里只有一个子 view,布局和测量就显得非常简单。
2、事件拦截
@override public boolean onintercepttouchevent(motionevent ev) { if (!isenable) { return false; } final int action = ev.getaction() & motioneventcompat.action_mask; if (action == motionevent.action_cancel || action == motionevent.action_up || action != motionevent.action_down && misunabletodrag) { /*结束手势的滑动,不拦截*/ endtodrag(); return false; } switch (action) { case motionevent.action_down: /*计算 x,y 的距离*/ int index = motioneventcompat.getactionindex(ev); mactivepointerid = motioneventcompat.getpointerid(ev, index); if (mactivepointerid == invalid_pointer) break; mlastmotionx = minitialmotionx = motioneventcompat.getx(ev, index); mlastmotiony = motioneventcompat.gety(ev, index); /*这里判读,如果这个触摸区域是允许滑动拦截的,则拦截事件*/ if (thistouchallowed(ev)) { misbeingdragged = false; misunabletodrag = false; } else { misunabletodrag = true; } break; case motionevent.action_move: /*继续判断是否需要拦截*/ determinedrag(ev); break; case motionevent.action_up: break; case motionevent.action_pointer_up: /*这里做了对多点触摸的处理,当有多个手指触摸的时候依然能正确的滑动*/ onsecondarypointerup(ev); break; } if (!misbeingdragged) { if (mvelocitytracker == null) { mvelocitytracker = velocitytracker.obtain(); } mvelocitytracker.addmovement(ev); } return misbeingdragged; }
事件拦截,是拦截而是其不会向子 view 分发,直接执行本级 view的 ontouchevent方法;
3、事件处理
@override public boolean ontouchevent(motionevent event) { if (!isenable) { return false; } if (!misbeingdragged && !thistouchallowed(event)) return false; final int action = event.getaction(); if (mvelocitytracker == null) { mvelocitytracker = velocitytracker.obtain(); } mvelocitytracker.addmovement(event); switch (action & motioneventcompat.action_mask) { case motionevent.action_down: /*按下则结束滚动*/ completescroll(); int index = motioneventcompat.getactionindex(event); mactivepointerid = motioneventcompat.getpointerid(event, index); mlastmotionx = minitialmotionx = event.getx(); break; case motioneventcompat.action_pointer_down: { /*有多个点按下的时候,取最后一个按下的点为有效点*/ final int indexx = motioneventcompat.getactionindex(event); mlastmotionx = motioneventcompat.getx(event, indexx); mactivepointerid = motioneventcompat.getpointerid(event, indexx); break; } case motionevent.action_move: if (!misbeingdragged) { determinedrag(event); if (misunabletodrag) return false; } /*如果已经是滑动状态,则根据手势滑动,而改变view 的位置*/ if (misbeingdragged) { // 以下代码用来判断和执行view 的滑动 final int activepointerindex = getpointerindex(event, mactivepointerid); if (mactivepointerid == invalid_pointer) break; final float x = motioneventcompat.getx(event, activepointerindex); final float deltax = mlastmotionx - x; mlastmotionx = x; float oldscrollx = getscrollx(); float scrollx = oldscrollx + deltax; final float leftbound = getleftbound(); final float rightbound = getrightbound(); if (scrollx < leftbound) { scrollx = leftbound; } else if (scrollx > rightbound) { scrollx = rightbound; } mlastmotionx += scrollx - (int) scrollx; scrollto((int) scrollx, getscrolly()); } break; case motionevent.action_up: /*如果已经是滑动状态,抬起手指,需要判断滚动的位置*/ if (misbeingdragged) { final velocitytracker velocitytracker = mvelocitytracker; velocitytracker.computecurrentvelocity(1000, mmaxmunvelocity); int initialvelocity = (int) velocitytrackercompat.getxvelocity( velocitytracker, mactivepointerid); final int scrollx = getscrollx(); final float pageoffset = (float) (-scrollx) / getcontentwidth(); final int activepointerindex = getpointerindex(event, mactivepointerid); if (mactivepointerid != invalid_pointer) { final float x = motioneventcompat.getx(event, activepointerindex); final int totaldelta = (int) (x - minitialmotionx); /*这里判断是否滚动到下一页,还是滚回原位置*/ int nextpage = determinetargetpage(pageoffset, initialvelocity, totaldelta); setcurrentiteminternal(nextpage, true, initialvelocity); } else { setcurrentiteminternal(mcuritem, true, initialvelocity); } mactivepointerid = invalid_pointer; endtodrag(); } else { // setcurrentiteminternal(0, true, 0); endtodrag(); } break; case motioneventcompat.action_pointer_up: /*这里有事多点处理*/ onsecondarypointerup(event); int pointerindex = getpointerindex(event, mactivepointerid); if (mactivepointerid == invalid_pointer) break; mlastmotionx = motioneventcompat.getx(event, pointerindex); break; } return true; }
因为这里加入了多点控制,所以代码看起来有点复杂,其实原理很简单,就是不断的判断是否符合滑动的条件。其他就不细讲了,来看看这个自定义 viewgroup 的效果
可以看到,这里我们已经实现了手势识别的 viewgroup,其实这个viewgroup如果发挥想象,它能实现很多效果,不单单是我今天要讲的效果,还可以用作侧拉菜单,或者是做 qq5.0版本侧滑效果都可以实现的。
二、侧滑 view绑定 activity
这里为了代码的简洁,还是通过一个 viewgroup 来封装了一层。
/** * created by moon.zhong on 2015/3/13. */ public class slidinglayout extends framelayout { /*侧滑view*/ private slidingview mslidingview ; /*需要侧滑结束的activity*/ private activity mactivity ; public slidinglayout(context context) { this(context, null); } public slidinglayout(context context, attributeset attrs) { this(context, attrs, 0); } public slidinglayout(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); mslidingview = new slidingview(context) ; addview(mslidingview); mslidingview.setonpagechangelistener(new slidingview.onpagechangelistener() { @override public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { if (position == 1){ log.v("zgy","========position=========") ; mactivity.finish(); } } @override public void onpageselected(int position) { } }); mactivity = (activity) context; bindactivity(mactivity) ; } /** * 侧滑view 和activity 绑定 * @param activity */ private void bindactivity(activity activity){ /*获取activity 的最*viewgroup*/ viewgroup root = (viewgroup) activity.getwindow().getdecorview(); /*获取activity 显示内容区域的viewgroup,包行actionbar*/ viewgroup child = (viewgroup) root.getchildat(0); root.removeview(child); mslidingview.setcontent(child); root.addview(this); } }
测试 activity 这事就变的非常简单了
public class secondactivity extends actionbaractivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_second); /*绑定activity*/ new slidinglayout(this) ; } }
来看看效果怎么样:
咦!能滑动结束页面,但为什么边滑走的同时看不到第一个 acitivity,而是要等结束了才能看到呢?我们猜测,应该是滑动的时候,这个 activity 还有哪里把第一个 activity 覆盖了,每个 activity 都是附在一个 window 上面,所以这里就涉及到一个 activity 的 window背景颜色问题, ok,把第二个 activity 的 window 背景设为透明
<style name="translucenttheme" parent="apptheme"> <item name="android:windowistranslucent">true</item> <item name="android:windowbackground">@android:color/transparent</item> <item name="android:windowcontentoverlay">@null</item> </style>
<activity android:name=".secondactivity" android:label="secondactivity" android:screenorientation="portrait" android:theme="@style/translucenttheme" />
再来看看效果,效果图:
完美实现!
好了,今天就到这里,下期文章就是对这个功能的进一步优化和改善,如果感兴趣,可以继续关注我!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。