您的位置:首页 > 移动开发 > Android开发

仿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才能接收事件。再次运行,符合预期效果。

至此,已把两个控件的冲突解决了^.^
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 控件