前一篇文章中已經講述了關于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如下
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請猛戳