安卓学习--scrollview和内部控件产生的滑动冲突处理

今天在同事在项目中遇到了一个有趣的bug,一个录音的功能,点击录音按钮出现录音的dialog,松手以后dialog消失,并显示录音条长度,本来一切正常,结果同事手一抖,松开手发现dialog不消失了,wtf,他捣鼓了感觉是最外层scrollview滑动事件导致的,但是不知道怎么解决,于是叫上本人一起研究,想到不久前从《android群英传》上看来的android事件分发机制,正好一显身手,根据事件分发的隧道型传递方法,只要让按钮的onTouch事件返回ture就可以啦,这样不就能拦截事件么,同事鄙视的看了我一眼说,已经是true了,呃,好吧,丢人了,为了不丢脸,只能再继续研究,下面给出自己解决这个问题的思路。

一、什么是触摸事件?

首先想解决这个问题,需要非常清楚安卓控件事件的分发机制,所以最基本的就是触摸事件,嗯,我第一看书的时候,直接就把触摸事件忽略过去了,感觉就是触摸事件啊,多么具体,但是真的碰到实际问题的时候,却发现自己根本不懂什么是触摸事件,经过再次理解终于知道,触摸事件放到代码里面理解就是MotionEvent,而MotionEvent.ACTION_DOWM,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP,可以想成事件最小单位,书里面常说的事件分发机制,其实是在分发MotionEvent.ACTION_DOWM,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP,而不是一整个MotionEvent。

二、滚动事件和触摸事件是同一个机制还是不同的机制?

如果学过安卓的滑动可以知道,滑动实现的一种方式是利用scroller,阅读scrollview源码可以看出,scrollview的滚动是scroller控制的,也就是说,触摸事件决定了滑动事件是否发生,但是一旦发生滚动事件,也就是发生了MotionEvent.ACTION_MOVE,那么带有滑动属性的控件就会自行将onInterceptTouchEvent(MotionEvent ev)的返回值变成ture,也就是以后的事件全部发送给带有滚动属性的控件,不再向下分发,也就出现了我前言中出现的问题,按钮的手势检测不管用了,因为这时候,以后的事件都会被滑动控件拦截。

//scrollview源码里面的onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
         ············(其他方法)
        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;  //这里可以看到根据条件onInterceptTouchEvent返回了true
        }
        ············(其他方法)
}

三、事件拦截的流程是什么?

常说的隧道型,当用户手指点击屏幕的时候,先通过MotionEvent.ACTION_DOWM,一个单独的事件判断事件需要分发到哪个层级的控件,之后一系列的MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP就会直接发送给对应的层级,可以想象成MotionEvent.ACTION_DOWM是一个探路者,帮助后续的事件知道自己应该去哪个层级。
另外需要注意的是,哪怕是针对同一个控件,每次发生MotionEvent.ACTION_DOWM,都会重新对要发送的层级定位,关于这个具体可以看看《安卓开发艺术探索》中事件分发的章节。

四、如何解决前言提到的问题

很简单,在监听事件里面增加一句话 v.getParent().requestDisallowInterceptTouchEvent(true);,让父控件不拦截事件,这样哪怕触发了滚动事件导致的onInterceptTouchEvent=true方法,也不会对下层造成影响。

tv_touch.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i("touch" , "--tv_touch--");
                v.getParent().requestDisallowInterceptTouchEvent(true);
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    Log.i("touch" , "--ACTION_UP-");
                }
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    Log.i("touch" , "--ACTION_DOWN-");
                }
                if (event.getAction() == MotionEvent.ACTION_MOVE) {
                    Log.i("touch" , "--ACTION_MOVE-");
                }
                return true;
            }
        });

关于v.getParent().requestDisallowInterceptTouchEvent(true);这个方法,我看了一眼源码,大概的流程是通过传递的true这个参数,来改变viewgroup里面的一个boolean标记,这个标记可以让onInterceptTouchEvent方法不走事件消耗(所谓的事件消耗其实就是不用事件做任何代码上的操作),另外,因为自己并没有完全编译源码,只是顺着源码的思路看了看大概的意思,如果这里有啥说的不对的地方,希望能指出。

你可能感兴趣的:(安卓学习--scrollview和内部控件产生的滑动冲突处理)