Android onTouchEvent和onInterceptTouchEvent事件分发详解(三)

Android onTouchEvent和onInterceptTouchEvent事件分发详解

Posted by 山大王 on April 14, 2016

“花门楼前见秋草,岂能贫贱相看老。” –岑参

正文

紧接前一篇几个问题的验证,在看之前最好把上一篇的Android onTouchEvent和onInterceptTouchEvent事件分发详解(二)先看一下。
在上一篇我们根据源码分析了Android事件的分发机制,在最后总结了几个问题,在这一篇我们将为大家逐一验证。
总共有3个类,一个Activity,一个ViewGroup,一个View

(1)在Activity中的代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("wld",
				"DispatchActivity_dispatchTouchEvent" + "_"
						+ MotionEvent.actionToString(ev.getAction()));
		return super.dispatchTouchEvent(ev);
	}
 
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("wld",
				"DispatchActivity_onTouchEvent" + "_"
						+ MotionEvent.actionToString(event.getAction()));
		return super.onTouchEvent(event);
	}

ViewGroup中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("wld", "LinearLayoutParent_dispatchTouchEvent" + "_"
				+ MotionEvent.actionToString(ev.getAction()));
		return super.dispatchTouchEvent(ev);
	}
 
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("wld",
				"LinearLayoutParent_onTouchEvent" + "_"
						+ MotionEvent.actionToString(event.getAction()));
		return super.onTouchEvent(event);
	}
 
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("wld", "LinearLayoutParent_onInterceptTouchEvent" + "_"
				+ MotionEvent.actionToString(ev.getAction()));
		return super.onInterceptTouchEvent(ev);
	}

View中的代码(View为TextView)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("wld",
				"TextViewChild_dispatchTouchEvent" + "_"
						+ MotionEvent.actionToString(ev.getAction()));
		return super.dispatchTouchEvent(ev);
	}
 
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("wld",
				"TextViewChild_onTouchEvent" + "_"
						+ MotionEvent.actionToString(event.getAction()));
		return super.onTouchEvent(event);
	}

我们运行一下,看一下结果

我们知道TextView默认是不消耗事件的,我们看一下打印的log

DOWN和UP只会触发一次,MOVE可能会触发很多次,我们知道就行了,从上面可以看到,默认情况下TextView是不消耗事件的,只是触发了DOWN事件,后面的MOVE,UP都没有触发,且传递顺序是从Activity的dispatchTouchEvent→ViewGroup的dispatchTouchEvent→ViewGroup的onInterceptTouchEvent→View的dispatchTouchEvent→View的onTouchEvent(返回false往上抛)→ViewGroup的onTouchEvent(返回false表示事件没消耗,往上抛)→Activity的onTouchEvent(处理了事件,执行了MOVE,UP事件),此后会一直交给Activity处理,不在往下分发。
(2)我们让TextView中的onTouchEvent方法返回为true,在看一下执行结果

1
2
3
4
5
6
7
8
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("wld",
				"TextViewChild_onTouchEvent" + "_"
						+ MotionEvent.actionToString(event.getAction()));
		super.onTouchEvent(event);
		return true;
	}

打印log如下

我们看到TextView的onTouchEvent消耗了事件(执行了DOWM,MOVE,UP事件),就不会再往上抛,所以ViewGroup的onTouchEvent和Activity的onTouchEvent就不会再触发,此后所有的事件都会交由它来处理。
(3)我们在看另外一种情况,把上面TextView的onTouchEvent返回默认值(false)让ViewGroup的onTouchEvent放回true,

onInterceptTouchEvent

1
2
3
4
5
6
7
8
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("wld",
				"LinearLayoutParent_onTouchEvent" + "_"
						+ MotionEvent.actionToString(event.getAction()));
		super.onTouchEvent(event);
		return true;
	}

我们看一下打印的log

我们看到TextView只执行了DOWM事件,因为返回的是false,表示事件没有被消耗,就会往上抛,交给ViewGroup,但ViewGroup的onTouchEvent返回的是true,表示事件被他消耗了,就不会再往上抛,所以就执行了后面的MOVE,UP事件,此后的一系列事件也不会再往下传递了,每次传递到ViewGroup的时候就直接调用它的onTouchEvent方法。
(4)我们在(3)的基础上,让onInterceptTouchEvent返回true,在看一下

onInterceptTouchEvent

1
2
3
4
5
6
7
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("wld", "LinearLayoutParent_onInterceptTouchEvent" + "_"
				+ MotionEvent.actionToString(ev.getAction()));
		super.onInterceptTouchEvent(ev);
		return true;
	}

我们看一下打印的log

我们看到TextView的方法根本就没有执行,这时TextView的方法无论放回true还是false都没有任何影响,因为ViewGroup的onInterceptTouchEvent方法返回true,表示事件被他拦截,就不会再往下传递,我们还看到ViewGroup的onInterceptTouchEvent方法只执行了一次,因为已经被拦截了,后续的一系列事件就都会交给他,就不需要在拦截了,因为ViewGroup的onTouchEvent返回true,事件被他消耗,就不在往上抛。
(5)我们在(4)的基础上,让onTouchEvent返回false,在看一下打印的log

因为ViewGroup拦截,所以没有传递到TextView,因为ViewGroup没有消耗事件,所以往上抛到Activity,所以事件最终被Activity给处理了(执行了MOVE,UP事件)
(6)关于requestDisallowInterceptTouchEvent事件,这里我们再来研究一下,我们让所有的都返回默认,然后让ViewGroup的onInterceptTouchEvent方法返回true(表示拦截,默认情况下就不会往下传递),我们让TextView的onTouchEvent方法返回true(表示事件被他消耗),然后改写TextView的dispatchTouchEvent方法

1
2
3
4
5
6
7
8
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		getParent().requestDisallowInterceptTouchEvent(true);
		Log.d("wld",
				"TextViewChild_dispatchTouchEvent" + "_"
						+ MotionEvent.actionToString(ev.getAction()));
		return super.dispatchTouchEvent(ev);
	}

我们看一下log

发现很奇葩的事,TextView的方法并没有被调用,我们看一下源码就会发现,在ViewGroup的dispatchTouchEvent方法中有这样一段代码

1
2
3
4
5
6
7
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

表示在按下的时候调用了resetTouchState方法,我们再看一下这个方法

resetTouchState

1
2
3
4
5
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

看到没,在最后一行把FLAG_DISALLOW_INTERCEPT标记清除了,就是说每次点击的时候,执行DOWN事件的时候就会把它清除,由于后面的onInterceptTouchEvent返回true,所以TextView的方法根本就执行不了,我们把ViewGroup的onInterceptTouchEvent方法改一下

1
2
3
4
5
6
7
8
9
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("wld", "LinearLayoutParent_onInterceptTouchEvent" + "_"
				+ MotionEvent.actionToString(ev.getAction()));
		// super.onInterceptTouchEvent(ev);
		if (ev.getAction() == MotionEvent.ACTION_DOWN)
			return false;
		return true;
	}

然后再看一下log

我们看到TextView的所有方法都被执行了,虽然在ViewGroup的onInterceptTouchEvent方法中除了DOWN以外都被拦截了,但我们还是能看到TextView消耗了事件,因为在我们在TextView的dispatchTouchEvent方法中调用了requestDisallowInterceptTouchEvent方法(不让父类拦截事件),我们把TextView中的requestDisallowInterceptTouchEvent方法去掉再看一下

我们看到TextView只执行了DOWN和CANCEL事件,MOVE和UP事件都被ViewGroup拦截了,由于没有消耗,直接往上抛,交给Activity处理。且ViewGroup的onInterceptTouchEvent方法在UP事件的时候也没有执行,因为在执行MOVE的时候就已经返回了True,所以后面的UP事件就不会再触发。
OK,到目前为止,Activity的事件拦截机制就分析完了。