仿Android QQ左侧滑菜单右侧滑列表菜单——处理HorizontalScrollView和SwipeMenuListView滑动冲突
2015-10-10 17:37
796 查看
需求
最近项目需要一个仿QQ的界面,向右滑可以拉出菜单,向左滑则拉出列表菜单。初步实现
这两个侧滑控件网上都有了,这里我用的是:- 侧滑菜单XCSlideMenu(以下简称Slide),详见http://www.w2bc.com/Article/13213
- 列表侧滑SwipeMenuListView(以下简称Swipe),详见https://github.com/baoyongzhang/SwipeMenuListView
问题分析
如果单独使用这两个控件的话是没问题的,但是如果放到一起,在屏幕左右滑动时,Swipe无法正确响应。因为Slide实则是个HorizontalScrollView,两控件的滑动响应都是水平方向的,这是Android典型的事件冲突。这样的话,事件应该进行分发,根据场景分配给相应的处理者。
这里只分析下思路,对事件传递不熟悉的童鞋可以学习http://www.cnblogs.com/jqyp/archive/2012/04/25/2469758.html。
处理
分析完问题的原因,可以想到一个处理办法:用一个变量来标志当前的状态。由于Slide是父容器,所以我把标记放那里了,同时声明几种状态:
/** 普通状态 */ public static final int STATUS_NORMAL = 0; /** 左边侧滑菜单操作中 */ public static final int STATUS_SLIDE = 1; /** 列表侧滑菜单操作中 */ public static final int STATUS_SWIPE = 2; /** 列表侧滑菜单已关上但手指还未离开 */ public static final int STATUS_SWIPE_STICK = 4; /** 当前界面的状态 */ public static int sStatus = STATUS_NORMAL;
1)界面的初始状态是STATUS_NORMAL,右滑则打开Slide,左滑则拉开Swpie。
那么先在父容器里拦截右滑,改变状态标记,在Slide里加上:
private float x1; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean isIntercept = false; float x2 = ev.getX(); if (ev.getAction() == MotionEvent.ACTION_DOWN) { x1 = x2; return super.onInterceptTouchEvent(ev); } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { // 只有普通状态时右滑时才拦截事件 if (x2 > x1 && isMove(x1, x2) && sStatus == STATUS_NORMAL) { isIntercept = true; sStatus = sStatus | STATUS_SLIDE; } } x1 = x2; return isIntercept; }
x1用于记录按下时的x坐标,isMove方法用于判断手指的移动距离是不是大于某个临界值,true的话才算真正滑动了手指。isSlideOut后面会说。
运行下,发现有点成效了,原本不正常的Swipe可以拉开。然后Slide也能拉开,但是收不回去。
2)接下来处理状态STATUS_SLIDE。
Slide控件里有个isSlideOut成员变量,是标志菜单是否打开状态。可以借助这个变量,每当其改变值时,跟上我们的逻辑:
isSlideOut = true; sStatus = sStatus | STATUS_SLIDE; isSlideOut = false; sStatus = sStatus & ~STATUS_SLIDE;
继续完善Slide的onInterceptTouchEvent方法,加入了左滑的处理:
else if (ev.getAction() == MotionEvent.ACTION_MOVE) { // 只有普通状态时右滑&侧滑菜单打开时左滑才拦截事件 if (x2 > x1 && isMove(x1, x2) && sStatus == STATUS_NORMAL) { isIntercept = true; sStatus = sStatus | STATUS_SLIDE; } else if (x2 < x1 && isMove(x1, x2) && isSlideOut) { isIntercept = true; } }
同时我们想要实现跟QQ一样的,点击右边缩小的Swipe界面能关闭Slide,那么就不是MOVE事件了,修改onInterceptTouchEvent:
if (ev.getAction() == MotionEvent.ACTION_DOWN) { x1 = x2; // 只有侧滑菜单打开时点击右侧才拦截事件 if (x2 > mMenuWidth && isSlideOut) { isIntercept = true; } else { return super.onInterceptTouchEvent(ev); } }
也需要更新onTouchEvent方法:
float x2 = ev.getX(); switch (action) { case MotionEvent.ACTION_UP: ... } else if (!isMove(x1, x2) && x2 > mMenuWidth && isSlideOut) { // 侧滑菜单打开时,点击右侧将其关闭 slideInMenu(); } else{ ... }
运行下,Slide打开状态的各种事件也正确处理了。
3)再看下STATUS_SWIPE状态。
Swipe为我们提供了一个接口OnSwipeListener,包含了onSwipeStart和onSwipeEnd方法。故名思议,就是拉开或关闭Swipe的菜单时会触发的回调。那么我们只要在使用了Swipe控件的地方实现接口,跟上我们的逻辑即可:
mListView.setOnSwipeListener(new OnSwipeListener() { @Override public void onSwipeStart(int position) { XCSlideMenu.sStatus = XCSlideMenu.STATUS_SWIPE; } @Override public void onSwipeEnd(int position) { if (position < 0) { XCSlideMenu.sStatus = XCSlideMenu.STATUS_NORMAL; } } });
运行下,在Swipe某一项上左滑右滑等操作,结果是符合我们的预期的。
但是只拉开Swipe菜单,然后单击外部空白处,Swipe菜单是收回去了,但是Slide又拉不出来了。
只能回Swipe的源码查看,发现点击空白是不会回调onSwipeEnd的(坑啊……)。而且,处理点击空白这个行为时,在ACTION_DOWN方法里调用了ACTION_CANCEL,强行结束事件。这样会出现什么问题呢?我们先试一下。
为OnSwipeListener加上新方法:
void onSwipeCancel(int position);
在Swipe的onTouchEvent的ACTION_DOWN里加上:
if (mTouchView != null && mTouchView.isOpen()) { ... if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeCancel(oldPos); } return true; }
再完善回调:
mListView.setOnSwipeListener(new OnSwipeListener() { ... @Override public void onSwipeCancel(int position) { XCSlideMenu.sStatus = XCSlideMenu.STATUS_NORMAL; } });
运行后拉开Swipe菜单,然后单击空白,再右滑,Slide可以拉出。
如果拉开Swipe菜单后按住空白处再右滑,发现Swipe是收回去了,但是Slide也跟着出来了,从用户体验角度来看,这不符合我们的预期。于是要加多一个状态STATUS_SWIPE_STICK。
4)新状态STATUS_SWIPE_STICK,指的是在已展开菜单的Swipe的空白处按下,但手指不离开屏幕。此时的焦点应该还是Swipe,所以要改下mListView的回调:
public void onSwipeCancel(int position) { XCSlideMenu.sStatus = XCSlideMenu.STATUS_SWIPE_STICK; }
再为Slide的onInterceptTouchEvent添加:
} else if (ev.getAction() == MotionEvent.ACTION_UP) { if (sStatus == STATUS_SWIPE_STICK) { sStatus = STATUS_NORMAL; } }
这意味着按住空白并左右滑动,事件还是交由Swipe处理,只有手指抬起,才把状态恢复到普通,Slide才能接收事件。再次运行,符合预期效果。
至此,已把两个控件的冲突解决了^.^
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories