天天看點

android ViewGoup事件分發機制dispatchTouchEvent

做本章内容給大家分享一下ViewGroup的事件分發中dispatchTouchEvent事件,這個事件大家很熟悉,但平時重寫最多的是onInterceptTouchEvent,onTouchEvent,說到點選事件大家都會明白有三個重要的方法,下面結合源碼、例子、圖來分析dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三者之間的關系

先上例子:

例子中,有4個ViewGroup,每個ViewGroup都重寫了三個重要的方法,而且也重寫了目前Activity的dispatchTouchEvent和onTouchEvent

ViewGroup之間的關系如下圖

android ViewGoup事件分發機制dispatchTouchEvent
android ViewGoup事件分發機制dispatchTouchEvent
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

public class MainActivity extends Activity {

    private final String TAG = "WXQ";
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        RelativeLayout viewA = new RelativeLayout(this){

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewA dispatchTouchEvent action=" + ev.getAction());
                boolean value = super.dispatchTouchEvent(ev);
                Log.d(TAG, "viewA dispatchTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewA onInterceptTouchEvent action=" + ev.getAction());
                boolean value = super.onInterceptTouchEvent(ev);
                Log.d(TAG, "viewA onInterceptTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onTouchEvent(MotionEvent event) {
                Log.i(TAG, "viewA onTouchEvent action=" + event.getAction());
                boolean value = super.onTouchEvent(event);
                Log.d(TAG, "viewA onTouchEvent value=" + value);
                return value;
            }
        };

        RelativeLayout viewB = new RelativeLayout(this) {

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewB dispatchTouchEvent action=" + ev.getAction());
                boolean value = super.dispatchTouchEvent(ev);
                Log.d(TAG, "viewB dispatchTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewB onInterceptTouchEvent action=" + ev.getAction());
                boolean value = super.onInterceptTouchEvent(ev);
                Log.d(TAG, "viewB onInterceptTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onTouchEvent(MotionEvent event) {
                Log.i(TAG, "viewB onTouchEvent action=" + event.getAction());
                boolean value = super.onTouchEvent(event);
                Log.d(TAG, "viewB onTouchEvent value=" + value);
                return value;
            }
        };
        RelativeLayout viewC = new RelativeLayout(this) {


            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewC dispatchTouchEvent action=" + ev.getAction());
                boolean value = super.dispatchTouchEvent(ev);
                Log.d(TAG, "viewC dispatchTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewC onInterceptTouchEvent action=" + ev.getAction());
                boolean value = super.onInterceptTouchEvent(ev);
                Log.d(TAG, "viewC onInterceptTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onTouchEvent(MotionEvent event) {
                Log.i(TAG, "viewC onTouchEvent action=" + event.getAction());
                boolean value = super.onTouchEvent(event);
                Log.d(TAG, "viewC onTouchEvent value=" + value);
                return value;
            }
        };
        RelativeLayout viewD = new RelativeLayout(this) {

            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewD dispatchTouchEvent action=" + ev.getAction());
                boolean value = super.dispatchTouchEvent(ev);
                Log.d(TAG, "viewD dispatchTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.i(TAG, "viewD onInterceptTouchEvent action=" + ev.getAction());
                boolean value = super.onInterceptTouchEvent(ev);
                Log.d(TAG, "viewD onInterceptTouchEvent value=" + value);
                return value;
            }

            @Override
            public boolean onTouchEvent(MotionEvent event) {
                Log.i(TAG, "viewD onTouchEvent action=" + event.getAction());
                boolean value = super.onTouchEvent(event);
                Log.d(TAG, "viewD onTouchEvent value=" + value);
                return value;
            }
        };
        viewC.addView(viewD, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
        viewA.addView(viewC, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
        viewA.addView(viewB, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
        setContentView(viewA);

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "activity dispatchTouchEvent action=" + ev.getAction());
        boolean value = super.dispatchTouchEvent(ev);
        Log.d(TAG, "activity dispatchTouchEvent value=" + value);
        return value;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "activity onTouchEvent action=" + event.getAction());
        boolean value = super.onTouchEvent(event);
        Log.d(TAG, "activity onTouchEvent value=" + value);
        return value;
    }
}
           

情景1:首先所有的傳回值都不做改動,我們運作一下看看效果

activity dispatchTouchEvent action=0
viewA dispatchTouchEvent action=0
viewA onInterceptTouchEvent action=0
viewA onInterceptTouchEvent value=false
viewB dispatchTouchEvent action=0
viewB onInterceptTouchEvent action=0
viewB onInterceptTouchEvent value=false
viewB onTouchEvent action=0
viewB onTouchEvent value=false
viewB dispatchTouchEvent value=false
viewC dispatchTouchEvent action=0
viewC onInterceptTouchEvent action=0
viewC onInterceptTouchEvent value=false
viewD dispatchTouchEvent action=0
viewD onInterceptTouchEvent action=0
viewD onInterceptTouchEvent value=false
viewD onTouchEvent action=0
viewD onTouchEvent value=false
viewD dispatchTouchEvent value=false
viewC onTouchEvent action=0
viewC onTouchEvent value=false
viewC dispatchTouchEvent value=false
viewA onTouchEvent action=0
viewA onTouchEvent value=false
viewA dispatchTouchEvent value=false
activity onTouchEvent action=0
activity onTouchEvent value=false
activity dispatchTouchEvent value=false
activity dispatchTouchEvent action=1
activity onTouchEvent action=1
activity onTouchEvent value=false
activity dispatchTouchEvent value=false
           

down事件, 首先明白一點就是touch任何事件都是最先到Activity,Activity接收順序為 dispatchTouchEvent然後是 onTouchEvent, 然後再向下分發給ViewGroup,ViewGroup最先接收到的是 dispatchTouchEvent方法,然後經過 onInterceptTouchEvent 再到onTouchEvent ,先不要問為什麼,一會兒咱們一起看源碼就知道了。因為ViewGroup預設都是不可點選,是以所有的ViewGroup方法都傳回了false,最終down事件又回到了Activity,是以Activity的dispatchTouchEvent的接收值為false

緊接着up事件,事件也是最先由目前Activity的dispatchTouchEvent來分發,然後就沒再向下分發,為什麼呢,再看一種情景就知道了

情景2:接下來我們做一點兒改動,我們将ViewB調用setClickable(true),使ViewB變成一個可點選的VIEW,運作一下看看會發生什麼

activity dispatchTouchEvent action=0

viewA dispatchTouchEvent action=0

viewA onInterceptTouchEvent action=0

viewA onInterceptTouchEvent value=false

viewB dispatchTouchEvent action=0

viewB onInterceptTouchEvent action=0

viewB onInterceptTouchEvent value=false

viewB onTouchEvent action=0

viewB onTouchEvent value=true

viewB dispatchTouchEvent value=true

viewA dispatchTouchEvent value=true

activity dispatchTouchEvent value=true

activity dispatchTouchEvent action=1

viewA dispatchTouchEvent action=1

viewA onInterceptTouchEvent action=1

viewA onInterceptTouchEvent value=false

viewB dispatchTouchEvent action=1

viewB onTouchEvent action=1

viewB onTouchEvent value=true

viewB dispatchTouchEvent value=true

viewA dispatchTouchEvent value=true

activity dispatchTouchEvent value=true

我們看到有了很大變化,,由于ViewB已經變成了可點選的View是以ViewB的onTouchEvent傳回了true,同時我們也看到了Activity的dispatchTouchEvent也傳回true,接着up事件,也是先經過Activity再往下傳遞給ViewA再傳遞給ViewB然後就沒再向下傳了,為什麼呢,因為ViewB是可點選的View,既然ViewB收到了down事件,那麼move、up事件也一定能收到才是合理的完整事件對吧。

那你可能會問兩個問題1、事件為什麼不再向ViewC和ViewD傳遞了呢,2、為什麼ViewA能收到事件。原因很簡單,抛開程式思維去想,ViewA收到事件後檢索自己的子View,ViewB收到事件了,再往下傳已經沒意義了,ViewB是ViewA的子View,事件必定會先經過父結點

我們看一下源碼粗體部分可知,目前ViewGroup的dispatchTouchEvent被調用後,會把事件交給了onInterceptTouchEvent來做攔截處理

public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
	...........
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
			.........
        }

		..........
        return handled;
    }
           

對子VIEW周遊,根據子VIEW的dispatchTouchEvent的傳回值是否決定下一個事件繼續分發

public boolean dispatchTouchEvent(MotionEvent ev) {

..................
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(childIndex);
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

...............
    }
           
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }
           
對ViewGroup的dispatchTouchEvent方法的總結:方法中的super.dispatchTouchEvent去分發目前事件到自己的子View,而目前方法的傳回值是告訴父View或Activity下一個事件(move、up)還需不需要給目前ViewGroup,它的傳回值是由其所有子View來決定,如果所有子View都不可點傳回false,如果有任何一個子View接收事件則傳回true,回過頭看看情景1知道為什麼up事件Activity不再向ViewGroup傳遞,因為ViewA的dispatchTouchEvent在down事件時傳回了false,說明ViewA的所有子View都沒有接收down事件,是以ViewA告訴Activity說up事件不需要再給我了

繼續閱讀