前一篇文章中已经讲述了关于View的事件传递机制,这篇文章主要介绍一下ViewGroup的事件传递机制,ViewGroup的事件传递是基于View的,有兴趣可以看一下《Android View、ViewGroup事件分发机制(一)》
1. 首先我们来自定义一个ViewGroup,继承自LinearLayout,重写它的onInterceptTouchEvent方法
public class MyLayout extends LinearLayout {
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 默认返回false
//return true;
return super.onInterceptTouchEvent(ev);
}
}
2. 然后我们在布局文件中引用MyLayout,添加两个Button
<com.viewgroup.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.viewgroup.MyLayout>
3.在MainActivity中使用2中的布局,并且添加根布局的触摸事件和两个Button的点击事件
public class MainActivity extends Activity {
protected static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyLayout myLayout = (MyLayout) findViewById(R.id.my_layout);
Button button1 = (Button) findViewById(R.id.button1);
Button button2 = (Button) findViewById(R.id.button2);
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");
}
});
}
}
4.我们分别点击Button1,Button2和其他空白地方,Log如下
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9MmaNh3YtJGaWhUYqZ1MkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN1gzM1MTM5AjMxkDM0EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
5.我们将MyLayout.java中的onInterceptTouchEvent返回true,重新运行后Log如下
为什么会产生这种效果呢,我们知道它跟onInterceptTouchEvent方法的返回值有关系。流程如下图所示:
接下来,我们去查看一下ViewGroup的dispatchTouchEvent方法的伪源码(来自Android2.3源码)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onFilterTouchEventForSecurity(ev)) {
return false;
}
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//如果onInterceptTouchEvent方法返回false,进入if内部
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;
// 遍历所以子View,找到被点击的View
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)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 调用子View的dispatchTouchEvent方法
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
//如果点击空白区域,则会调用父类View的dispatchTouchEvent方法,之后流程跟View的事件处理一致
if (target == null) {
// We don't have a target, this means we're handling the
// event as a regular view.
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);
}
return target.dispatchTouchEvent(ev);
}
从上面伪代码的注释中可以看出, dispatchTouchEvent方法会依据 onInterceptTouchEvent方法的返回值,来确定是否拦截子View。如果返回值为true,则拦截子View,执行其父类View的 dispatchTouchEvent方法,否则,执行子View的dispatchTouchEvent方法。
下载Demo请猛戳