Android中View的事件分发机制与滑动冲突的解决方案

Android事件分发机制:

1.MotionEvent概念

在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上移动
ACTION_UP:手指从屏幕上松开的一瞬间
ACTION_CANCEL:当前手势已终止

正常情况下,一次手指触摸屏幕的行为会触发上述事件①②③所组成的一系列事件
当子View消费事件的过程中,父View突然进行拦截,则子View会收到ACTION_CANCEL这个事件。

2.View的事件分发机制

所谓的事件分发,就是指对MotionEvent事件的分发过程。当一个MotionEvent产生了后,系统需要把这个事件传递给一个具体的View来处理,这个传递过程就是分发过程。其中有三个重要的方法dispatchTouchEventonInterceptTouchEventonTouchEvent

public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件的分发。如果此事件能传递到当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法内部调用,用于判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示了是否拦截当前事件。

public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接收到事件。

如下一段伪代码可以表示三个函数之间的关系:

//事件分发给当前View
public boolean dispatchTouchEvent(MotionEvent e){
    boolean consume = false;
    //做拦截判断
    if(onInterceptTouchEvent(e)){
        //拦截该事件,然后处理该事件
        consume = onTouchEvent(e);
    }else{
        //未拦截该事件,事件被分发给子View
        consume = child.dispatchTouchEvent(e);
    }
    return consume;
}
事件分发大致流程:

当一个点击事件产生后,它会由Activity传递到PhoneWindow,PhoneWindow再传递到DecorView即顶级View。顶级View(一般是ViewGroup)接收到事件后,就开始进行事件分发。首先,顶级View的dispatchTouchEvent方法会被调用,如果这个View的onInterceptTouchEvent方法返回true,则表示它要拦截这个事件,那么此事件就会交由它处理,即调用它的onTouchEvent方法来处理事件;如果这个View的onInterceptTouchEvent方法返回false,则表示它不拦截这个事件,这时当前事件就会通过遍历的方式继续传递给它的所有子View,对应的子View的dispatchTouchEvent方法就会被调用。就这样一直传递到事件被处理为止。特殊情况:如果一个View的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法将会被调用,以此类推,如果所有元素都不处理这个事件,最终事件会回传到Activity进行处理。

事件处理大致流程:

当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。所以事件处理的优先级onTouchListener的onTouch高于onTouchEvent高于onClick。

Tips:
①某个View一旦决定拦截,那么这一整个事件序列都只能由它来处理,并且它的onInterceptTouchEvent方法不再被调用。
②ViewGroup默认不拦截任何事件。
③View没有onInterceptTouchEvent方法,一旦有事件传给它,那么它就一定会执行。
④View的onTouchEvent默认会消费事件(返回true),除非它是不可点击的。

滑动冲突解决方案:

了解了上面的事件分发机制后,我们会发现所谓的滑动冲突,无非就是父View与子View之间的事件拦截逻辑未做相应处理所造成的。所以想要解决滑动冲突问题,我们可以从父View和子View的事件拦截的处理逻辑上下手。

滑动冲突的解决方案主要分为两大类:
1. 外部拦截法(拦截的处理逻辑交由父View执行)
2. 内部拦截法(拦截的处理逻辑交由子View执行)

外部拦截法

该方法主要重写父View的onInterceptTouchEvent方法,在内部做拦截的逻辑判断。伪代码如下:

public boolean onInterceptTouchEvent(MotionEvent event){
    boolean intercepted = false;
    //从MotionEvent中获取xy坐标
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //一定要返回false,否则子View无法接受到该事件序列的后续任何事件
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            //此处做拦截判断,可根据获得的xy坐标判断用户手指的滑动方向...
            if(判断父View是否需要当前事件){
                intercepted = true;
            }else{
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            //一定要返回false,否则子View中无法接收到该事件,可能导致子View的onClick方法无法触发
            intercepted = false;
            break;
        default:
            break;
    }
    //记录本次xy坐标
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
}

该方法只需重写父View的onInterceptTouchEvent方法,实现起来比较简单,思路也符合Android中的事件分发机制的执行流程。

内部拦截法

内部拦截法主要通过重写子View中的dispatchTouchEvent方法来实现对事件的拦截并进行逻辑判断。为代码如下:

public boolean dispatchTouchEvent(MotionEvent event){
    //从MotionEvent中获取xy坐标
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //父View在后续事件序列中不准进行拦截
            parent.requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            //此处做拦截判断,可根据获得的xy坐标判断用户手指的滑动方向...
            if(判断父View是否需要当前事件){
                //准许父View对后续事件进行拦截
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
    }
    //记录本次xy坐标
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
}

通过requestDisallowInterceptTouchEvent方法,我们可以动态的设置父View能否被准许拦截后续事件。
为了能在父View在被准许拦截后成功的对后续事件进行拦截,我们还需要对父View的onInterceptTouchEvent方法做重写。伪代码如下:

public boolean onInterceptTouchEvent(MotionEvent event){
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //一定要返回false,否则子View无法接受到该事件序列的后续任何事件
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_UP:
        default:
            intercepted = true;
            break;
    }
    return intercepted;
}

可以看到,通过将除了ACTION_DOWN以外的所有事件都设置返回true,可以保证该次事件一定被父View拦截处理。

以上就是两种解决滑动冲突的常见方案。

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

推荐阅读更多精彩内容