ViewGroup源码-Touch事件

强转极昼
• 阅读 1451

在Android-27中查看源码:

在看完View源码的触摸事件(View源码-Touch事件)后,我们接着来看看容器类View的触摸事件。因为容器类的View都继承自ViewGroup,所以我们在ViewGroup的源码中来看看是如何处理触摸事件的。

同样的先来看ViewGroup源码中的dispatchTouchEvent方法:

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

首先,会判断是否不允许拦截,可以通过requestDisallowInterceptTouchEvent进行设置,默认允许拦截。如果允许拦截,会调用onInterceptTouchEvent,看看该View是否拦截了触摸事件,默认不拦截即可以将触摸事件向子View传递。

接下来,如果触摸事件没有取消并且没有拦截,在手指按下的时候:

final ArrayList<View> preorderedList = buildTouchDispatchChildList();
...
for (int i = childrenCount - 1; i >= 0; i--){
    ...
    newTouchTarget = getTouchTarget(child);
    if (newTouchTarget != null) {
        // Child is already receiving touch within its bounds.
        // Give it the new pointer in addition to the ones it is handling.
        newTouchTarget.pointerIdBits |= idBitsToAssign;
        break;
    }
    resetCancelNextUpFlag(child);
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        mLastTouchDownTime = ev.getDownTime();
        if (preorderedList != null) {
            // childIndex points into presorted list, find original index
            for (int j = 0; j < childrenCount; j++) {
                if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                }
            }
        } else {
            mLastTouchDownIndex = childIndex;
        }
        mLastTouchDownX = ev.getX();
        mLastTouchDownY = ev.getY();
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }
...
}
  1. 按照绘制子View的顺序,找到该ViewGoup的所有子view,并保存到ArrayList中。
  2. 根据视图顺序依次遍历子View。判断当前子view是否是TouchTarget,若是则跳出循环。否则调用dispatchTransformedTouchEvent方法,如果当前子View的dispatchTouchEvent返回为true,找到该子View在ViewGroup中的index,并将该子View作为新的TouchTarget。
  3. 清楚保存了所有子View的ArrayList。

然后在TouchTarget形成的链式结构中,处理触摸事件:

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
    // Dispatch to touch targets, excluding the new touch target if we already
    // dispatched to it.  Cancel touch targets if necessary.
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}
  1. 如果子View没有处理触摸事件,则由当前的ViewGroup处理,然后返回处理结果。因为ViewGroup是View的子类,所以还是由View的dispatchTouchEvent处理。
  2. 如果子View中处理了触摸事件,根据TouchTarget生成的链式结构,不断循环,分别调用里面View的dispatchTouchEvent方法。

从上面的分析可以看出整个ViewGroup的Touch事件的整个传递过程如下:

  1. 是否调用了requestDisallowInterceptTouchEvent方法设置不允许拦截,默认允许拦截。若允许拦截,则再调用onInterceptTouchEvent,看是否拦截。
  2. 如果触摸事件被拦截,则调用ViewGroup的dispatchTransformedTouchEvent方法,其实是调用View的dispatchTouchEvent方法。否则继续向下。
  3. 当触摸事件为MotionEvent.ACTION_DOWN时,首先获取根据绘制顺序保存了所有子View的ArrayLsit,然后再根据视图顺序去遍历子View。

    1. 如果从TouchTarget形成的链表中发现该View,则跳出循环,即找到了TouchTarget。否则继续向下。
    2. 在子View中寻找,当子View的dispatchTouchEvent方法返回为true时,则找到了新的TouchTarget,并将其添加到TouchTarget形成的链表中。
  4. 遍历TouchTarget形成的链表,然后对链表里面的子View分别调用dispatchTouchEvent,最后将处理结果返回。
点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Android事件分发-基础原理和场景分析
和其他平台类似,Android中View的布局是一个树形结构,各个ViewGroup和View是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个View的范围内,这样就不知道哪个View来响应这个事件,为了解决这一问题,就出现了事件分发机制。
刘望舒 刘望舒
4年前
Android输入系统(一)输入事件传递流程和InputManagerService的诞生
Android框架层Android输入系统Android框架层本文首发于微信公众号「刘望舒」前言很多同学可能会认为输入系统是不是和View的事件分发有些关联,确实是有些关联,只不过View事件分发只能算是输入系统事件传递的一部分。这个系列讲的输入系统主要是我们不常接触的,但还是需要去了解的那部分。1.输入事件传递流程的组成部分输入系统是外界与And
九章 九章
4年前
Android-自定义view
要自定义view,都知道有3个方法需要重写:onMeasure、onLayout、onDraw。而且这三个方法的执行是按顺序的。生命周期image.png实际开发中,比较多的自定义都是具体实现一个view的子类,实现viewgroup的子类比较少,两者基本相似,区别就是view需要实现onMeasure、onLayout、onDraw三个方法,而vie
九章 九章
4年前
hook之替换View.OnClickListener
image.png目录第一章:第二章:第三章:使用Hook修改View.OnClickListener事件\\首先,我们先分析View.setOnClickListener源码,找出合适的Hook点。publicvoidsetOnClickListener(@NullableOnClickListenerl)if(!is
可莉 可莉
4年前
2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助
2019年Android岗位BAT等大厂面试题知识点小结2019年了搜集了很多面试题,希望能对大家有所帮助1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属
Stella981 Stella981
4年前
Android中不规则形状View的布局实现
在Android中不管是View还是ViewGroup,都是方的!方的!方的!而对于非方形的,Android官方并没有给出非常好的解决方案.有的无非就是自定义View了.然而自定义View非常麻烦,需要重写很多方法,而且稍微不注意可能就会丧失一些特性或者造成一些Bug.而且即便是自定义View,其实那个自定义View还是方的!!!,自定义V
Easter79 Easter79
4年前
Spring中的设计模式
spring在容器中使用了观察者模式:一、spring事件:ApplicationEvent,该抽象类继承了EventObject类,jdk建议所有的事件都应该继承自EventObject。二、spring事件监听器:ApplicationLisener,该接口继承了EventListener接口,jdk建议所有的事件监听器都应该继承Ev
Stella981 Stella981
4年前
Android点击事件
Android点击事件备注全局实现View.OnClickListener或许需要将MainActivity设置为public注册事件btn_login.setOnClickListener(this)btn_logout.setOnClickListen
Stella981 Stella981
4年前
2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助
2019年Android岗位BAT等大厂面试题知识点小结2019年了搜集了很多面试题,希望能对大家有所帮助1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属
Stella981 Stella981
4年前
Django与drf 源码视图解析
0902自我总结Django与drf源码视图解析一.原生DjangoCBV源码分析:View"""1)as_view()是入口,得到view函数地址2)请求来了调用view函数,内部调用dispatch函数
强转极昼
强转极昼
Lv1
春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少!
文章
7
粉丝
0
获赞
0