天天看點

Android View、ViewGroup 事件分發機制(二)

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

Android View、ViewGroup 事件分發機制(二)

5.我們将MyLayout.java中的onInterceptTouchEvent傳回true,重新運作後Log如下

Android View、ViewGroup 事件分發機制(二)

為什麼會産生這種效果呢,我們知道它跟onInterceptTouchEvent方法的傳回值有關系。流程如下圖所示:

Android View、ViewGroup 事件分發機制(二)

接下來,我們去檢視一下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請猛戳

繼續閱讀