Android 扩展-View的滑动冲突(一)

  最近楼主在看任玉刚老师的《Android开发艺术探索》,看到了View的滑动冲突,感觉收获比较大。楼主之前也是做过View的滑动冲突,相对来说,对于View的事件分发机制比较熟悉,所以在View的滑动冲突这一块,做起来可能比较轻松的,但是实际上还是有很多的坑。
  之所以写这个篇文章,是因为看到任玉刚老师介绍了比较系统的解决方法,对于之前的我,全靠自己的摸索,感触比较深刻。
  View的滑动冲突有很多的情况,我们这里不可能一一的介绍,只是介绍几种比较典型的情况。

1.问题引出


  这个滑动冲突的情况,主要是:外部的View可以左右滑动,但是内部的View可以上下滑动。典型的是ViewParger+Fragment配合使用,同时Fragment的内容还有一个ListView或者RecyclerView,ViewParger可以左右滑动,ListView可以上下滑动,这种情况就会出现滑动冲突的问题。虽然ViewParger内部帮助我们处理了这个问题,但是我们这里就当ViewParger没有帮助我们处理这个问题。

2.解决方法

(1).分析思路

  要想找到正确的解决方法,我们必须正确的理解这种情况。这种情况是两个不同方向的滑动的冲突,如果我们能正确的判断出用户手指滑动方向,这个问题就相当于解决了一半。那怎么判断手指滑动的方向呢?
  滑动方向的判断方式有很多种:比如,我们通过x方向与y方向上夹角的大小来判断,也可以通过x方向滑动的距离和y方向上滑动的距离等等。这里楼主用x方向滑动的距离和y方向上滑动的距离来判断的。
我们这样来假设情况,假如,x方向上滑动的距离比y方向上滑动的距离大,那么我们认为的是当前的用户在水平上方向滑动,反之,则是在竖直方向上滑动。
  问题来了,方向是判断出来了,怎么解决我们的问题呢?在事件分发机制中,我们知道有三个方向,分别是dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。这里简单的介绍一下这三个方法的作用。

A.dispatchTouchEvent方法

  这个方法通常来对事件分发,当一个事件传递到一个ViewGroup中时,这个方法里面会判断是否分发事件到它的子View里面。如果在ACTION_DOWN事件时,当前的ViewGroup拦截事件,那么在这个方法里面,将不再分发同一个事件序列(从ACTION_DOWN到ACTION_UP表示一个事件序列)到子View,而是统一的分发到这个ViewGroup中去;而如果当前的ViewGroup没有拦截这个事件,那么这个事件将分发到当前ViewGroup的子View中去。

B.onInterceptTouchEvent方法

  这个方法通常用来判断当前的ViewGroup或者是View是否拦截当前的事件,如果拦截的话,那么就返回true,反之返回false就行了。

C.onTouchEvent方法

  这个方法就是正式的处理事件的方法,如果要想处理一个事件的话,必须返回true。因为返回false的话,会被认为当前的View不处理事件,于是会将事件传递给当前View的父View。
  我们知道了,怎么拦截事件,就可以开始我们下面的表演了。

(2).外部拦截法

  经过上面我们知道了,我们可以通过onInterceptTouchEvent方法来拦截事件。外部拦截法的思想是:当一个事件传递过来时,父ViewGroup先判断是这个事件是否需要拦截,如果需要拦截的话,那么我们就在这个方法里面返回true就行了;如果不需要拦截的话,那么就返回false,父ViewGroup会通过dispatchToucEvent方法将事件分发到它的子View中去。
  外部拦截法比较符合Android的事件分发机制。
  代码模板:

public boolean onInterceptTouchEvent(MotionEvent ev) {
        //判断是否拦截事件
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercepted = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                //父ViewGroup需要拦截当前的事件
                if (isIntercept(x, y)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;
                break;
            }
        }
        mLastInterceptX = x;
        mLastInterceptY = y;
        return intercepted;
    }

  上面就是外部拦截法的基本模板,针对不同的需求,我们只需要修改isIntercepted方法就行了。但是在这里还是需要注意几点:

A.在ACTION_DOWN事件里面,我们只能返回false。因为如果返回true的话,那么表示当前的ViewGroup方法需要拦截这个事件序列,所以之后的ACTION_MOVE和ACTION_UP事件不会传递到当前的ViewGroup的子View里面去。
B.在ACTION_MOVE事件里面,我们根据我们具体的需求来判断是否拦截。
C.在ACTION_UP事件里面,我们最好不要拦截,因为这个事件本身就没有多大的意义。

  关于ACTION_UP的讨论,这里有一个特殊情况,假设父ViewGroup拦截了ACTION_UP事件,同时子View设置了OnClickListener事件,那么UP事件被父ViewGroup拦截,会导致子View的onClick无法响应。但是父ViewGroup比较特殊,一旦开始拦截任何一个事件时,那么后续的事件都会交给它来处理,而ACTION_UP作为最后一个事件也肯定会传递给父ViewGroup,即便父ViewGroup的onInterceptTouchEvent方法在ACTION_UP事件里面返回了false。

(1).内部拦截法

  内部拦截法是指父ViewGroup不拦截任何的事件,全部事件都传递给子View来处理。如果子View需要处理此事件的话,就直接将此事件消耗掉就行了,否则就交给父ViewGroup来处理,这种方法和Android中的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常的工作,使用起来较外部拦截法比较复杂。
  代码模板:

public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                //请求父ViewGroup不要拦截DOWN事件
                //个人觉得这一步是多余的,第一,如果父ViewGroup拦截DOWN事件,那么当前的事件序列根本不能传递给这里来,更不要说进入switch语句调用这段代码
                //第二,前面已经说了,父ViewGroup不拦截任何事件,那么这里请求父ViewGroup不拦截事件,岂不是多余的吗?
                //最后说一句,个人感觉,不知道正确与否
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                //如果父ViewGroup要拦截此事件的话,那么请求父ViewGroup来拦截事件
                //拦截是从下一个事件(MOVE或UP)开始,当前这个MOVE事件不会拦截的
                if (isParentIntercept(x, y)) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
        }
        mLastInterceptX = x;
        mLastInterceptY = y;
        return super.dispatchTouchEvent(ev);
    }

  上面的代码模板就是内部拦截法的基本实现,当面对不同的需求时,只需要改变isParentIntercept方法就行了。这里需要注意的是:
  A.通过parent的requestDisallowInterceptTouchEvent方法来申请父ViewGroup拦截我们的事件,其实是从下一个事件开始拦截的,只要当前的事件还没有到结束。
  B.一旦事件被父ViewGroup拦截的话,那么在当前的事件序列中,之后的事件不会再传递到当前的View中来,而是直接在父ViewGroup中消耗掉。
  C.父ViewGroup不能拦截ACTION_DOWN事件,因为一旦ACTION_DOWN事件被父ViewGroup拦截的话,内部拦截法根本不会发挥效果。因为ACTION_DOWN事件被拦截的话,那么当前的事件序列中所有的事件都不会传递到子View中去,所以我们在子View中设置的规则根本没有效果;其次,ACTION_DOWN事件不受requestDisallowInterceptTouchEvent方法的影响,在每次ACTION_DOWN事件产生时,FLAG_DISALLOW_INTERCEPT变量都会被重置。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容