Android的事件分发机制的实验(亲测)

网上关于Android触摸事件的分发机制的文章有很多,但是我怀疑很多文章是错误的,所以今天亲自测试了一下事件的分发的流程,摸索出一些规律,如果对本文实验结果有所质疑欢迎大家提出并指正,实验记录如下:

申明:如需转载请声明出处,请尊重他人劳动,谢谢合作


实验图如下:



大家可以看到这个Activity中有四个View 从里到外分别是view_1(relativeLayout),view_2(relativeLayout),

view_3(relativeLayout),view_4(Button).

实验方法:

覆盖掉View 1 2 3 的dispatchTouchEvent()、onInterceptTouchEvent(), onTouchEvent()函数,同时覆盖Button的onTouchEvent和dispatchTouchEvent()函数,在每一个函数中输出一句Log来记录实验结果;主要代码如下
MyButton.java
package com.example.androidges;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button{

	int id = 0;
	
	public MyButton(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	
	public MyButton(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}


	public MyButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}


	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		boolean b = super.onTouchEvent(event);
		//b = false;
		System.out.println("view_" + id +" onTouchEvent " + event.getAction() + ":" + b); 
		return b;
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		boolean b = super.dispatchTouchEvent(event);
		System.out.println("view_" + id +" dispatchTouchEvent " + event.getAction()+ ":" + b);
		return b;
	}
	
}

MyLayout.java
package com.example.androidges;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

public class MyLayout extends RelativeLayout{

	int id = 0;
	
	public MyLayout(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	
	public MyLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}


	public MyLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}


	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		boolean b = super.dispatchTouchEvent(event);
		//if(id == 2) b = false;
		System.out.println( "view_" + id + " dispatchTouchEvent " + event.getAction()+ ":" + b);
		return b;
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		boolean b = super.onInterceptTouchEvent(event);
		//if(id == 3) b = true;
		System.out.println("view_" + id + " onInterceptTouchEvent " + event.getAction()+ ":" + b);
		return b;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		boolean b = super.onTouchEvent(event);
		//if(id == 3) b = true;
		System.out.println("view_" + id + " onTouchEvent " + event.getAction()+ ":" + b);
		return b;
	}
	
	
}

说明一下:id是用来标记View用的,方便在Log中查看当前是View 1234中的哪一个。

下面开始做实验了。

实验用例:
①:点击Button区域,让View_3拦截下事件,并且消费事件,其他一切正常,LOG输出如下:

分析:可以看到事件先到达最外层View_1 然后判断他是否要拦截本次事件,View_1表示不拦截,然后事件分发给View_2,询问2要不要拦截下来,2表示不要拦截,然后接着往下传递给View_3,3表示我要拦截,3拦截下来后
消费了此次ACTION_DOWN事件(图中的0表示down事件,1表示up事件哈),onTouchEvent返回了一个true,这个时候接着调用View_3的dispatchEvent函数,返回一个true值,代表向View_3的上一层View请求后续事件也请传递给我(至于View_2理不理会View_3的请求,还得看View_2的心情,后面我们将会看到View_2心情不好的时候,逻辑会是怎么样的),这里由于View_2是正常的实现(没有被人为篡改返回值),所以View_2也就向View_1代为转达了View_3后续事件的请求,View_1又再次像dec_view(最外层的rootView)转达了请传达后续事件的请求,就这样后续的ACTION_UP事件(图中的1)也就传递了进来,并且一路顺畅的到达了View_3,被View_3消费了。
②:点击Button区域,让View_3拦截下本次点击事件,但是View_3淘气不消费本次事件(onTouch返回false),其他一切正常:


可以看到前面三条Log的流程基本和实验①是一致的,View_3表示了拦截DOWN事件,但是他淘气了一下,表示不消费本次事件(onTouchEvent返回了false);View_4表示很无奈,你既然不消费为什么还要拦截,占着茅坑不拉屎,所以你会发现你的Button的OnClick方法根本就不执行,View_4被屏蔽掉了,这个时候系统采取什么策略呢?首先系统会根据View_3不消费本次事件的事实,将View_3的dispatchTouchEvent返回false作为惩罚,不把后续事件传递给你,然后系统就会把这个DOWN事件往上抛给View_3的父View_2和爷爷View_1,看看他们是不是想吃View_3的剩饭(onTouchEvent函数是不是消费本次事件),实验②中爸爸和爷爷都表示对剩饭不感兴趣,所以他们的onTouchEvent都返回了false,系统也同时将父亲和爷爷的dispatchTouchEvent函数返回false,表示不需要再给他们传递后续事件了,这样你就看不到UP事件Log了,因为UP事件被rootView给吸收了。
③:点击Button区域,让View_3拦截下本次点击事件,但是View_3淘气不消费本次事件(onTouch返回false),View_3又再淘气一点(有点小贱了),将dispatchTouchEvent返回值设置为true,向父亲View_2表示后续事件我也想要,但是我就是不消费(View_4表示View_3是个bitch),其他一切正常,看下Log:


可以看到前4句Log还是一样,第五句log View_3告诉View2后续事件也给我送来,然后View2 View1向root view转达了View_3的请求,这时候接下来的UP事件就被传递了进来,他先检查View_1的dispatch是true,那么这就表示View_1本身或者是子控件想要处理这个事件(这也就是官网API对dispatch函数的解释:Pass the touch screen motion event down to the target view, or this view if it is the target.),那如何判断这个UP事件到底应该给谁呢?系统会检查View_1的儿子View_2是不是也要dispatch这个事件,如果发现View_2也想dispatch这个事件,系统是不是就轻易的交给View_2呢?答案是不是得,系统还得再问问View_1是不是想要拦截这个UP事件,如果要是View_1拦截这个事件的话,那么UP事件就会转到View_1的onTouchEvent函数去做处理,这里发现View_1对UP事件不感兴趣,并不拦截UP事件,所以View_1把UP事件交给了View_2,View_2再去执行上面相同的逻辑。这里有3个问题可能会引起细心的人去思考:
第一个问题:为什么你知道是先检查子View需不需要事件,然后再去检查父View要不要拦截呢?而不是先检查需不需要拦截再去决定要不要询问子View想不想要得到本次事件呢?
第二个问题:这2种检查方式有什么区别吗?
第三个问题:这里的View_3既然没有消费这个事件,为什么系统不会像实验二那样的把时间往上回抛给他的父控件的onTouchEvent方法呢?

第一个问题很简单,大家看log里面的第9-10行,我们发现当View_2不拦截此事件时(第9行)并没有去调用View_3的拦截函数,而是直接就调用了View_3的onTouchEvent函数(第10行),所以我猜测检查这个事件到底应该给谁的顺序应该是先问子View需不需要本次事件再去拦截,由于DOWN事件根本都没传到View_4空间,所以View_4的dispatch值是默认的false,这时候系统就知道View_4不需要这个事件,那么这个事件就只能给View_3来处理了,根本都不需要判断View_3需不需要拦截。

第二个问题笔者也没有搞懂是为什么,欢迎大家讨论一下

第三个问题嘛,因为View_3对父亲撒谎了,在实验二中View_3说的是实话:我没有消费这次事件(dispatch设置为false);而在实验三中View_3对父控件说了谎:我或者我的子View消费了这个事件(dispatch设置为true)所以系统会根据子控件的dispatch值来决定是不是要将事件抛给父控件的onTouchView去处理;

基于上面的流程其实还可以做很多实验来摸清楚事件的分发过程;由于时间有限我就不再深入下去了;有兴趣的可以自己测试一下,下面总结一下:
①:时间分发的函数调用顺序是onInterceptTouchEvent -> onTouch -> onDispatchTouchEvent
②:dispatchTouchEvent表示当前控件以及子空间是不是消费过这次的事件,如果没有消费,就会把事件往上抛出,当下次事件达到的时候系统又会根据这个值来判断是不是要把后续事件再交给这个View和他的子Vie们。

转载请注明!谢谢

Android的事件分发机制的实验(亲测),,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。