首先我们先自定义几个ViewGroup
public class MyLinearLayout extends LinearLayout {
private String TAG = "MyLinearLayout";
public MyLinearLayout(@NonNull Context context) {
super(context);
}
public MyLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent");
return super.onTouchEvent(event);
}
}
public class MyFrameLayout extends FrameLayout {
private String TAG = "MyFrameLayout";
public MyFrameLayout(@NonNull Context context) {
super(context);
}
public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent");
return super.onTouchEvent(event);
}
}
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
private String TAG = "MyTextView";
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent");
return super.onTouchEvent(event);
}
}
我们发现ViewGroup跟事件相关的方法有三个dispatchTouchEvent,onInterceptTouchEvent,和onTouchEvent
而View中只有dispatchTouchEvent和onTouchEvent两个
然后XML引用这几个View
<event.test.hxy.com.testenent.MyFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<event.test.hxy.com.testenent.MyLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<event.test.hxy.com.testenent.MyTextView
android:id="@+id/my_bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮" />
</event.test.hxy.com.testenent.MyLinearLayout>
</event.test.hxy.com.testenent.MyFrameLayout>
然后点击一下textview
08-14 14:54:19.775 23874-23874/ E/MyFrameLayout: dispatchTouchEvent
08-14 14:54:19.776 23874-23874/ E/MyFrameLayout: onInterceptTouchEvent
08-14 14:54:19.776 23874-23874/ E/MyLinearLayout: dispatchTouchEvent
08-14 14:54:19.776 23874-23874/ E/MyLinearLayout: onInterceptTouchEvent
08-14 14:54:19.776 23874-23874/ E/MyTextView: dispatchTouchEvent
08-14 14:54:19.777 23874-23874/ E/MyTextView: onTouchEvent
08-14 14:54:22.888 23874-23874/ E/MyLinearLayout: onTouchEvent
08-14 14:54:22.889 23874-23874/ E/MyFrameLayout: onTouchEvent
我们发现时间都是从最外层的ViewGroup的dispatchTouchEvent -> onInterceptTouchEvent一层层往里传的,但是到onTouchEvent事件的时候又是从里一层层往外传的?
翻看源码我们发现:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
return true;
}
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
if(!intercepted ){
final boolean handled;
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
}
}
简单来说,ViewGroup会先调用自己的onInterceptTouchEvent判断自己是否要拦截该事件, 如果不拦截的话ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法.
在看看dispatchTouchEvent这个方法的描述是:
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.
大体 的意思就是该方法用于判断事件是否传给子View还是自己拦截处理
如果返回true就到自己的onTouchEvent()去处理,不会在往下传给子View.
我们在来看看子View的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
大体就是返回onTouch看自己处理不处理,如果onTouch返回false就执行onTouchEvent,如果onTouchEvent返回false,就返回给父View的onTouchEvent,这看起来有点像类的双亲委派机制??
好了,到这里就能解释最开始的点击事件为什么是一层层往里传递的了,这里我们总结一下:
这里盗用一张别人的图总结一下,当我们手指触摸到控件的时候,事件会从最外层的ViewGroup的dispatchTouchEvent开始判断自己的onInterceptTouchEvent要不要拦截处理事件,如果要拦截则返回true,交给自己的onTouchEvent处理,如果返回false则查找子View的dispatchTouchEvent这样循环调用,直到有子View拦截事件为止,如果都没人处理的话又会从子View的onTouchEvent一层层往回传.有点像双亲委派机制?