完全理解Android TouchEvent事件分发机制(一)

本文原地址:https://blog.csdn.net/zhcswlp0625/article/details/68930797
也是我写的,但是没有转移过来,尴尬。(本人放弃csdn分享,转向简书)。

下篇文章地址
完全理解Android TouchEvent事件分发机制(二)

本文能给你带来和解决一些你模糊的Touch事件概念及用法

  • 1.掌握View及ViewGroup的TouchEvent事件分发机制
  • 2.为解决View滑动冲突及点击事件消费提供支持
  • 3.为你解决面试中的一些问题。

Touch事件分发中只有两个主角:ViewGroup和View。

Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。

Activity、ViewGroup、View都关心Touch事件,其中ViewGroup的关心的事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。

Activity和View关心的事件只有两个:dispatchTouchEvent、onTouchEvent。

只有ViewGroup可以对事件进行拦截。

在Android中Touch触摸事件主要包括点击(onClick)、长按(onLongClick)、拖拽(onDrag)、滑动(onScroll)等,

其中Touch的第一个状态是 ACTION_DOWN,表示按下了屏幕后,touch将会有后续事件,比如移动、抬起等。

一个Action_DOWN,一个ACTION_UP,许多个ACTION_MOVE,构成了Android中众多的Touch交互事件。

安卓里经常会有多个布局嵌套,View重叠,View的Visibility设置等等,还有ViewGroup包含View的情况。
这个时候点击到子View时,其实也是同时点到ViewGroup这个父控件的,那是把这个点击事件应该是怎么分发的呢(有没有遇到过listview或recyclerview的item事件或者是item中的控件是不是没反应撒)?

触摸事件分发机制涉及的三个重要方法:

 public boolean dispatchTouchEvent(MotionEvent event)

dispatchTouchEvent用来进行事件的分发。如果事件能够传递给当前的View,那么此方法一定会被调用,
返回结果受当前View或者是ViewGroup的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionEvent event)

onInterceptTouchEvent是ViewGroup提供的方法,用来判断是否拦截某个事件,如果当前View拦截了某个事件,
那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。默认返回false,返回true表示拦截。

  public boolean onTouchEvent(MotionEvent event)

onTouchEvent在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前的事件,如果不消耗,
则在同一个事件序列中,当前View无法再次接受到事件。view中默认返回true,表示消费了这个事件。

今天所使用的Demo目录结构及Activity如图所示:

[外链图片转存失败(img-dxeeN54q-1566869743609)(http://oc5bjv3gr.bkt.clouddn.com/touchdetailsesdgdf.png)]

首先我们来看一下dispatchTouchEvent(MotionEvent event)

布局activity_touch_test.xml

<?xml version="1.0" encoding="utf-8"?>
<com.shanlovana.rcimageview.touchviews.GrandPaViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_touch_test"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.shanlovana.rcimageview.TouchTestActivity">

    <com.shanlovana.rcimageview.touchviews.FatherViewGroup
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_height="match_parent">

        <com.shanlovana.rcimageview.touchviews.LogImageView
            android:layout_width="300dp"
            android:layout_height="200dp"
            android:src="@drawable/damimi"/>


    </com.shanlovana.rcimageview.touchviews.FatherViewGroup>


</com.shanlovana.rcimageview.touchviews.GrandPaViewGroup>

下面是三层布局及预览情况:

[外链图片转存失败(img-RjazP8Pr-1566869743610)(http://oc5bjv3gr.bkt.clouddn.com/sancengbujujiyulan.png)]

点击一下图片:看一下打印:

03-31 09:02:40.554 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onInterceptTouchEvent  Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  dispatchTouchEvent  Event 0
03-31 09:02:40.556 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  onTouchEvent  Event 0
03-31 09:02:40.558 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 0
03-31 09:02:40.559 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onTouchEvent  Event 0

源码中0,1,2,3,4所代表的Action

   public static final int ACTION_DOWN = 0;
    

    public static final int ACTION_UP   = 1;
    

    public static final int ACTION_MOVE  = 2;
    

    public static final int ACTION_CANCEL = 3;
    

    public static final int ACTION_OUTSIDE = 4;

为什么是这样一个从父级到子级再到父级的顺序呢?

来,follow me进入源码查看,所有的核心在于ViewGroup的dispatchTouchEvent方法:

boolean dispatchTouchEvent() {
    // 是否拦截
     final boolean intercepted;
    intercepted = onInterceptTouchEvent(ev);
    
    // final boolean canceled = resetCancelNextUpFlag(this)
                 //   || actionMasked == MotionEvent.ACTION_CANCEL;
    if( !intercepted) {
        // 如果不拦截遍历所有child,判断是否有分发
        boolean handled;
        if (child == null) {
            // 等同于handled = onTouchEvent()
            handled = super.dispatchTouchEvent();
        } else {
            // 如果有child,再调用child的分发方法
            handled = child.dispatchTouchEvent();
        }

        if(handled) {
            touchTarget = child;
            break;
        }   
    }

    if(touchTarget == null) {
        // 如果所有child中都没有消费掉事件
        // 那么就把自己作为没child的普通View
        handled = super.dispatchTouchEvent();
    }

    return handled;
}

  public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

dispatchTouchEvent方法的作用是将屏幕点击事件进行向下分发(子一级)传递到目标控件上,或者传递给自己。

如果事件被(自己或者下面某一层的子控件)处理掉了的话,就返回true,否则返回false

那问题来了,如果我没有child了,或者我就是一个View,那我的dispatchTouchEvent返回值要如何获取呢?

这种情况下就会使用父类的dispatchTouchEvent方法,
也就是调用View类中的实现,简化代码如下:

boolean dispatchTouchEvent() {
    // 实质上就是调用onTouchEvent用其返回值
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }

    if (!result && onTouchEvent(event)) {
        result = true;
    }
    return result;
}

由此可见,只要是enable=false或者没有设置过touchListener, 那么他一定会调用onTouchEvent,且dispatchTouchEvent的返回值就是onTouchEvent的返回值。

ViewGroup进行事件的分发,一直到自己或者是最底层的View,逻辑图如下。

[外链图片转存失败(img-K0iwEqb4-1566869743610)(http://oc5bjv3gr.bkt.clouddn.com/luojiouxiangtu.png)]

现在我们基本知道了事件的分发dispatchTouchEvent,最终调用了onTouchEvent方法

接着我们来理解和讲解onInterceptTouchEvent拦截方法

该方法用于拦截事件向下分发

当返回值为true时,就会拦截TouchEvent不再向下传递,直接交给自己的onTouchEvent方法处理。返回false则不拦截。

Demo中的Parent层的onInterceptTouchEvent返回值改为true。

运行一下,点View,看下输出结果:

03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onInterceptTouchEvent  Event 0
03-31 11:45:39.954 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 0
03-31 11:45:39.955 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onTouchEvent  Event 0

即当事件一层层向下传递到parent时,被他就拦截了下来然后自己消费使用。

intercepted为true,没有进入FatherViewGroup的条件,就跳过了child.dispatchTouchEvent的向下事件分发(结合我的demo看比较直观)。

最后我们来讲解 onTouchEvent方法

方法的主体内容其实是处理具体操作逻辑的,是产生一次点击还是一次横纵向的滑动等

而他的返回值才会影响整个事件分发机制,
意义在于通知父级的ViewGroup们是否已经消费找到目标Target了。

把示例中的Parent的TouchEvent返回值改为true。拦截方法不变
点一下View(小伙子,如果你不是点一下,会出现不同的结果哦),则输出日志为:

03-31 12:01:21.661 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 0
03-31 12:01:21.674 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 0
03-31 12:01:21.676 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 0
03-31 12:01:21.677 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onInterceptTouchEvent  Event 0
03-31 12:01:21.677 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  dispatchTouchEvent  Event 0
03-31 12:01:21.678 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: LogImageView  onTouchEvent  Event 0
03-31 12:01:21.681 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 0
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  dispatchTouchEvent  Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup  onInterceptTouchEvent  Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  dispatchTouchEvent  Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup  onTouchEvent  Event 1

先看Down的逻辑,对应的源码执行顺序如下

Father调用super.dispatchTouchEvent实际上是调用了onTouchEvent方法,

这里因为我们修改成了true,所以dispatchTouchEvent最终也返回true。

所以返回到GrandPa中,touchTarget 就非空了,

因此GrandPa的onTouchEvent也没有执行~

可以看出来,事件一旦被某一层消费掉,其它层就不会再消费了

到这里其实对事件分发的机制就有个大概了解了看了源码也知道里面的原理是怎么回事。

突然有事,明天接着更新。

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

推荐阅读更多精彩内容