Android的ViewGroup中事件的传递机制(一)

本文主要针对dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三个方法,通过简单的例子来简单的介绍下。

根据字面意思的理解,dispatchTouchEvent分发触摸事件,onInterceptTouchEvent拦截触摸事件,onTouchEvent触摸事件。正如它们各自的字面意思,下面简单的列出各自的作用和用法。

1、dispatchTouchEvent作用:决定事件是否由onInterceptTouchEvent来拦截处理。

  • 返回super.dispatchTouchEvent时,由onInterceptTouchEvent来决定事件的流向
  • 返回false时,不会继续分发事件,自己内部只处理了ACTION_DOWN
  • 返回true时,不会继续分发事件,自己内部处理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

2、onInterceptTouchEvent作用:拦截事件,用来决定事件是否传向子View

  • 返回true时,拦截后交给自己的onTouchEvent处理
  • 返回false时,拦截后交给子View来处理

3、onTouchEvent作用:事件最终到达这个方法

  • 返回true时,内部处理所有的事件
  • 返回false时,事件会向上传递,由onToucEvent来接受,如果最上面View中的onTouchEvent也返回false的话,那么事件就会消失。
Android官方文档上:
onInterceptTouchEvent()与onTouchEvent()的机制:
  1. down事件首先会传递到onInterceptTouchEvent()方法
  2. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理
  3. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
  4. 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理
  5.  如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。

这是摘自网络:

More:

1.  一个事件必然从ACTION_DOWN开始,所以当某一个控件处理了一个ACTION_DOWN事件,可以理解直到下一个ACTION_DOWN之前所有的触摸事件都应当由该控件(target)进行处理。

2.  对于各种listener,显然是由android系统进行处理的。所以Super.onTouchEvent()应当放在子类的该函数的第一行,可以优先保证系统的方法不会被自定义的操作截取而导致没有机会执行。

3.  牢记一个原则,所有的touch事件都是从父容器开始向下传递的,呈U字形

(1)  父容器(类型为 <? Extends ViewGroup>) onInterceptTouchEvent接收到事件,如果return false,转步骤(2),否则转 步骤(3)

(2)  父容器在该位置(ACTION_DOWN发生的位置)存在子控件,如果子控件类型为

<? Extends ViewGroup>,此时该子控件成为父容器,转步骤(1),如果子控件为普通view,即 不是viewgroup的子类,使用子控件转步骤(3),如果不存在子控件,使用父容器转步骤(3)

(3)  该touch事件交给该控件的onTouchEvent进行响应,如果return false,当其存在父容器时,事件传递给父容器,转步骤(3)。当不存在父容器时,该事件被丢弃。如果return true,表示事件被消费了,此touch事件终止。

关于事件传递解释的更好的文章在这里

下面结合具体的例子来讲解,首先是容器类MyLinearLayout继承自LinearLayout,子View类MyTextView继承自TextView。

MyLinearLayout类:

package ldw.test;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

public class MyLinearLayout extends LinearLayout {

	private String TAB = "MyLinearLayout";

	public MyLinearLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyLinearLayout(Context context) {
		super(context);
	}

//	@Override
//	protected void onLayout(boolean changed, int l, int t, int r, int b) {
//		int childCount = getChildCount();
//		for(int i = 0;i < childCount;i++) {
//			View child = getChildAt(i);
//			child.layout(l, t, r, b);
//		}
//	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		switch(ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.d(TAB, "dispatchTouchEvent action:ACTION_DOWN");
			break;
		case MotionEvent.ACTION_UP:
			Log.d(TAB, "dispatchTouchEvent action:ACTION_UP");
			break;
		}
		return super.dispatchTouchEvent(ev);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		//true表示拦截后交给自己的onTouchEvent处理,false表示传递给子View
		switch(ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.d(TAB, "onInterceptTouchEvent action:ACTION_DOWN");
			break;
		case MotionEvent.ACTION_UP:
			Log.d(TAB, "onInterceptTouchEvent action:ACTION_UP");
			break;
		}
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch(event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.d(TAB, "---onTouchEvent action:ACTION_DOWN");
			break;
		case MotionEvent.ACTION_UP:
			Log.d(TAB, "---onTouchEvent action:ACTION_UP");
			break;
		}
		return false;
	}
}

MyTextView类:

package ldw.test;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

public class MyTextView extends TextView {

	private String TAB = "MyTextView";

	public MyTextView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public MyTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyTextView(Context context) {
		super(context);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		switch(event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.d(TAB, "dispatchTouchEvent action:ACTION_DOWN");
			break;
		case MotionEvent.ACTION_UP:
			Log.d(TAB, "dispatchTouchEvent action:ACTION_UP");
			break;
		}
		return super.dispatchTouchEvent(event);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch(event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.d(TAB, "---onTouchEvent action:ACTION_DOWN");
			break;
		case MotionEvent.ACTION_UP:
			Log.d(TAB, "---onTouchEvent action:ACTION_UP");
			break;
		}
		return false;
	}

}

布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ldw.test.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/testview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center" >

    <ldw.test.MyTextView
        android:layout_width="200dip"
        android:layout_height="200dip"
        android:text="@string/hello_world"
        android:textSize="50sp" />

</ldw.test.MyLinearLayout>

为了更清晰的理解事件的分发处理机制,最好自己将上面代码运行一下,主要更改下MyLinearLayout中的onInterceptTouchEvent的返回值(true或false),还有两个类中的onTouchEvent的返回值,来观察logcat中的打印结果,基本上和上面总结出来的一致。

记住一点:true就是自己内部消化掉所有事件,false就是继续传递事件。

Android的ViewGroup中事件的传递机制(二)