上篇Android事件分發機制(一)咱們已經講述了View的dispatchTouchEvent方法,今天咱們來看一下ViewGroup的事件傳遞。先來看下什麼是ViewGroup,ViewGroup是一組View的集合,它能夠包含子View或者子ViewGroup,咱們經常使用的一些佈局都是繼承自ViewGroup,固然他也是View的子類,只不過是能夠包含子View和子ViewGroup。下面咱們自定一個佈局來看一下ViewGroup的傳遞機制。java
public class MyLayout extends LinearLayout { public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); } }
而後引入咱們的佈局:android
<com.example.viewgrouptouchevent.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.example.viewgrouptouchevent.MyLayout>
在activity中咱們給兩個button和佈局添加監聽:web
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"); } });
咱們來分別點擊btn1,btn2,空白區域打印結果以下:tomcat
經過結果看到,點擊按鈕的時候不會觸發父佈局的touch方法,難道是從btn分發到父佈局的?查閱文檔,看到ViewGroup除了dispatchTouchEvent方法還有一個onInterceptTouchEvent方法,那咱們先來看下它的源碼app
/** * 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. * * <p>Using this function takes some care, as it has a fairly complicated * interaction with {@link View#onTouchEvent(MotionEvent) * View.onTouchEvent(MotionEvent)}, and using it requires implementing * that method as well as this one in the correct way. Events will be * received in the following order: * * <ol> * <li> You will receive the down event here. * <li> The down event will be handled either by a child of this view * group, or given to your own onTouchEvent() method to handle; this means * you should implement onTouchEvent() to return true, so you will * continue to see the rest of the gesture (instead of looking for * a parent view to handle it). Also, by returning true from * onTouchEvent(), you will not receive any following * events in onInterceptTouchEvent() and all touch processing must * happen in onTouchEvent() like normal. * <li> For as long as you return false from this function, each following * event (up to and including the final up) will be delivered first here * and then to the target's onTouchEvent(). * <li> If you return true from here, you will not receive any * following events: the target view will receive the same event but * with the action {@link MotionEvent#ACTION_CANCEL}, and all further * events will be delivered to your onTouchEvent() method and no longer * appear here. * </ol> * * @param ev The motion event being dispatched down the hierarchy. * @return Return true to steal motion events from the children and have * them dispatched to this ViewGroup through onTouchEvent(). * The current target will receive an ACTION_CANCEL event, and no further * messages will be delivered here. */ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
註釋比較多,方法卻很簡單,直接返回了一個false。那咱們在自定義佈局裏面重寫一下這個方法,讓他返回true試一下ide
public class MyLayout extends LinearLayout { public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } }
打印結果:佈局
點擊btn不會觸發點擊動做了,只會觸發父佈局的touch方法,那若是是從View到ViewGroup,那麼父佈局是怎麼攔截的呢?咱們來看一下ViewGroup的dispatchTouchEvent方法ui
public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { mMotionTarget = null; } 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; 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)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; if (target == null) { 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); } if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { } mMotionTarget = null; return true; } if (isUpOrCancel) { mMotionTarget = null; } final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } return target.dispatchTouchEvent(ev); }
ViewGroup的dispatchTouchEvent方法要比View的dispatchTouchEvent方法複雜一些,咱們仍是來看重點,13行的判斷條件,第一個是disallowIntercept,這個意思是禁止事件攔截,默認是false,能夠經過requestDisallowInterceptTouchEvent方法對這個值進行修改,那麼只要是onInterceptTouchEvent返回true就會進入下面的方法,咱們上面說道onInterceptTouchEvent默認是返回false的呀,仔細看下你會發現,判斷條件裏是取反的!也就是返回false正好能進入。也就是能夠理解爲返回true表明攔截事件,false返回不攔截。this
再來看進入之後的方法,進入之後呢就是對ViewGroup內的View進行遍歷,直到找到被點擊的對象,而後調用他的dispatchTouchEvent方法。接着就是咱們上一篇的內容了,返回true就不會執行後面的代碼了,可見咱們重寫onInterceptTouchEvent返回true就不會進入13行的判斷了。若是點擊空白區域,就不會在31行返回true,看44行判斷target,通常都會返回null,那麼繼續執行super.dispatchTouchEvent(ev); 也就是調用父類View的dispatchTouchEvent(ev)了,所以咱們重寫的父佈局的touch方法獲得執行。spa
咱們借用一張流程圖來看下整個事件的傳遞過程:
總結一下:
Android 的事件傳遞機制是從ViewGroup往下傳到View的,若是ViewGroup對事件進行了攔截,也就是onInterceptTouchEvent方法返回了true,事件就不會往下傳遞,由ViewGroup自己消費。
子View若是消費了事件,那麼ViewGroup不會接收到任何事件。