Android事件分發機制(二)

上篇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不會接收到任何事件。

相關文章
相關標籤/搜索