仿Android QQ左侧滑菜单右侧滑列表菜单——处理HorizontalScrollView和SwipeMenuListView滑动冲突

来源:互联网 发布:淘宝网腿祙裤 编辑:程序博客网 时间:2024/05/22 06:23

需求

最近项目需要一个仿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;@Overridepublic 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才能接收事件。再次运行,符合预期效果。

至此,已把两个控件的冲突解决了^.^

0 0
原创粉丝点击