文档章节

Android事件分发机制(二)

tomcater
 tomcater
发布于 2016/04/21 10:18
字数 1433
阅读 27
收藏 1

上篇Android事件分发机制(一)我们已经讲述了View的dispatchTouchEvent方法,今天我们来看一下ViewGroup的事件传递。先来看下什么是ViewGroup,ViewGroup是一组View的集合,它可以包含子View或者子ViewGroup,我们常用的一些布局都是继承自ViewGroup,当然他也是View的子类,只不过是可以包含子View和子ViewGroup。下面我们自定一个布局来看一下ViewGroup的传递机制。

public class MyLayout extends LinearLayout {  
  
    public MyLayout(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
  
}

然后引入我们的布局:

<com.example.viewgrouptouchevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:id="@+id/my_layout"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <Button  
        android:id="@+id/button1"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="Button1" />  
  
    <Button  
        android:id="@+id/button2"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="Button2" />  
  
</com.example.viewgrouptouchevent.MyLayout>

在activity中我们给两个button和布局添加监听:

myLayout.setOnTouchListener(new OnTouchListener() {  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        Log.d("TAG", "myLayout on touch");  
        return false;  
    }  
});  
button1.setOnClickListener(new OnClickListener() {  
    @Override  
    public void onClick(View v) {  
        Log.d("TAG", "You clicked button1");  
    }  
});  
button2.setOnClickListener(new OnClickListener() {  
    @Override  
    public void onClick(View v) {  
        Log.d("TAG", "You clicked button2");  
    }  
});

我们来分别点击btn1,btn2,空白区域打印结果如下:

通过结果看到,点击按钮的时候不会触发父布局的touch方法,难道是从btn分发到父布局的?查阅文档,看到ViewGroup除了dispatchTouchEvent方法还有一个onInterceptTouchEvent方法,那我们先来看下它的源码

 /** 
 * Implement this method to intercept all touch screen motion events.  This 
 * allows you to watch events as they are dispatched to your children, and 
 * take ownership of the current gesture at any point. 
 * 
 * <p>Using this function takes some care, as it has a fairly complicated 
 * interaction with {@link View#onTouchEvent(MotionEvent) 
 * View.onTouchEvent(MotionEvent)}, and using it requires implementing 
 * that method as well as this one in the correct way.  Events will be 
 * received in the following order: 
 * 
 * <ol> 
 * <li> You will receive the down event here. 
 * <li> The down event will be handled either by a child of this view 
 * group, or given to your own onTouchEvent() method to handle; this means 
 * you should implement onTouchEvent() to return true, so you will 
 * continue to see the rest of the gesture (instead of looking for 
 * a parent view to handle it).  Also, by returning true from 
 * onTouchEvent(), you will not receive any following 
 * events in onInterceptTouchEvent() and all touch processing must 
 * happen in onTouchEvent() like normal. 
 * <li> For as long as you return false from this function, each following 
 * event (up to and including the final up) will be delivered first here 
 * and then to the target's onTouchEvent(). 
 * <li> If you return true from here, you will not receive any 
 * following events: the target view will receive the same event but 
 * with the action {@link MotionEvent#ACTION_CANCEL}, and all further 
 * events will be delivered to your onTouchEvent() method and no longer 
 * appear here. 
 * </ol> 
 * 
 * @param ev The motion event being dispatched down the hierarchy. 
 * @return Return true to steal motion events from the children and have 
 * them dispatched to this ViewGroup through onTouchEvent(). 
 * The current target will receive an ACTION_CANCEL event, and no further 
 * messages will be delivered here. 
 */  
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
}

注释比较多,方法却很简单,直接返回了一个false。那我们在自定义布局里面重写一下这个方法,让他返回true试一下

public class MyLayout extends LinearLayout {  
  
    public MyLayout(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
      
    @Override  
    public boolean onInterceptTouchEvent(MotionEvent ev) {  
        return true;  
    }  
      
}

打印结果:

点击btn不会触发点击动作了,只会触发父布局的touch方法,那如果是从View到ViewGroup,那么父布局是怎么拦截的呢?我们来看一下ViewGroup的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                        if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;  
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);  
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null;  
        return true;  
    }  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }  
    return target.dispatchTouchEvent(ev);  
}

ViewGroup的dispatchTouchEvent方法要比View的dispatchTouchEvent方法复杂一些,我们还是来看重点,13行的判断条件,第一个是disallowIntercept,这个意思是禁止事件拦截,默认是false,可以通过requestDisallowInterceptTouchEvent方法对这个值进行修改,那么只要是onInterceptTouchEvent返回true就会进入下面的方法,我们上面说道onInterceptTouchEvent默认是返回false的呀,仔细看下你会发现,判断条件里是取反的!也就是返回false正好能进入。也就是可以理解为返回true代表拦截事件,false返回不拦截。

再来看进入以后的方法,进入以后呢就是对ViewGroup内的View进行遍历,直到找到被点击的对象,然后调用他的dispatchTouchEvent方法。接着就是我们上一篇的内容了,返回true就不会执行后面的代码了,可见我们重写onInterceptTouchEvent返回true就不会进入13行的判断了。如果点击空白区域,就不会在31行返回true,看44行判断target,一般都会返回null,那么继续执行super.dispatchTouchEvent(ev); 也就是调用父类View的dispatchTouchEvent(ev)了,因此我们重写的父布局的touch方法得到执行。

我们借用一张流程图来看下整个事件的传递过程:

总结一下:

Android 的事件传递机制是从ViewGroup往下传到View的,如果ViewGroup对事件进行了拦截,也就是onInterceptTouchEvent方法返回了true,事件就不会往下传递,由ViewGroup本身消费。

子View如果消费了事件,那么ViewGroup不会接收到任何事件。

 

 

© 著作权归作者所有

tomcater
粉丝 4
博文 43
码字总数 55169
作品 0
海淀
程序员
私信 提问
Android 机制篇 - 事件分发机制超详解(🔥🔥🔥🔥🔥🔥🔥🔥)

Android 虽然不是四大组件,但其并不比四大组件的地位低(涉及面的广度和深入甚至比四大组件还复杂🔥)。而View的核心知识点“事件分发机制”则是不少刚入门同学的拦路虎(1、项目中处处遇...

Pepsimaxin
2018/07/12
0
0
浅谈Android 事件分发机制(一)

Android事件分发机制是Android开发者必须了解的知识,这方面的内容很多,自己纯看文章总觉得比较抽象,自己写了个demo,理一下事件分发的流程,加深印象。 view结构 PhoneWindow 的指示通过 ...

Android高级架构探索
01/13
0
0
Android进阶知识:事件分发与滑动冲突(一)

1、前言 Android学习一段时间,需求做多了必然会遇到滑动冲突问题,比如在一个ScrollView中要嵌套一个地图View,这时候触摸移动地图或者放大缩小地图就会变得不太准确甚至没有反应,这就是遇...

Android进阶开发
04/24
0
0
安卓自定义View进阶-事件分发机制原理

安卓自定义View进阶-事件分发机制原理 之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部...

猴亮屏
2018/05/22
59
0
android事件分发机制总结

Android事件分发机制 在android 普通view(不包含ViewGroup)和activity中主要有一下两个方法处理事件: public boolean dispatchTouchEvent(MotionEvent ev) // 分发事件public boolean on...

亓斌哥哥
2014/10/08
962
1

没有更多内容

加载失败,请刷新页面

加载更多

Taro 兼容 h5 踩坑指南

最近一周在改造 公羊阅读🐏 Taro 版本适配 h5 端,过程中改改补补,好不酸爽。 本文记录📝遇到的问题,希望为有相同需求的哥们👬节约点时间。 Taro 版本:1.3.9。 client_mobile_taro...

dkvirus
今天
4
0
Spring boot 静态资源访问

0. 两个配置 spring.mvc.static-path-patternspring.resources.static-locations 1. application中需要先行的两个配置项 1.1 spring.mvc.static-path-pattern 这个配置项是告诉springboo......

moon888
今天
3
0
hash slot(虚拟桶)

在分布式集群中,如何保证相同请求落到相同的机器上,并且后面的集群机器可以尽可能的均分请求,并且当扩容或down机的情况下能对原有集群影响最小。 round robin算法:是把数据mod后直接映射...

李朝强
今天
4
0
Kafka 原理和实战

本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/bV8AhqAjQp4a_iXRfobkCQ 作者简介:郑志彬,毕业于华南理工大学计算机科学与技术(双语班)。先后从事过电子商务、开放平...

vivo互联网技术
今天
19
0
java数据类型

基本类型: 整型:Byte,short,int,long 浮点型:float,double 字符型:char 布尔型:boolean 引用类型: 类类型: 接口类型: 数组类型: Byte 1字节 八位 -128 -------- 127 short 2字节...

audience_1
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部