ViewGroup的事件分发机制

转载请标明出处:http://blog.csdn.net/u014800493/article/details/52056311


上一张说到View的事件处理机制。而在Activity的dispatchTouchEvent()中最终定位到了ViewGroup()的事件分发:

上一章 请看:这里Android  事件分发机制--View

还是先看下ViewGroup的层级结构图吧:

上章也说过了,我们用到的布局Layout全部继承ViewGroup 如:FrameLayout,RelativeLayout,LineraLayout等等。

首先还是看看XML:

<?xml version="1.0" encoding="utf-8"?>                                
布局很简单一个Button,一个TextView,一个ImageView放在LinearLayout里面。

而LinearLayout高度设置为200,背景白色,最外层设置为黑色。看看MyLinearLayout:

/** * @author Gordon * @since 2016/7/27 * do() */public class MyLinearLayout extends LinearLayout {    public MyLinearLayout(Context context) {        super(context);    }    public MyLinearLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.i("layout","Layout_dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.i("layout","Layout_onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("layout","Layout_onTouchEvent");        return super.onTouchEvent(event);    }}
主要就是Log出 Layout dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()方法;

看看MyButton:其实上一章已经说过了

/** * @author Gordon * @since 2016/7/27 * do() */public class MyButton extends Button {    public MyButton(Context context) {        super(context);    }    public MyButton(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public MyButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("button","Button_onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.i("button","Button_dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }}

/** * @author Gordon * @since 2016/8/1 * do() */public class MyTextView extends TextView {    public MyTextView(Context context) {        super(context);    }    public MyTextView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("textview","TextView_onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.i("textview","TextView_dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }}

/** * @author Gordon * @since 2016/8/1 * do() */public class MyImageView extends ImageView {    public MyImageView(Context context) {        super(context);    }    public MyImageView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public MyImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("imageview","ImageView_onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.i("imageview","ImageView_dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }}
主要就是Log出 Layout dispatchTouchEvent(),onTouchEvent()方法;

其实比较一下,这里layout比子view多了个onInterceptTouchEvent()方法。这些方法到底怎么运作的

看下Activity的代码:

public class OnClickEventActivity extends Activity {    @Bind(R.id.button_onclick)    MyButton click_button;    @Bind(R.id.text_onclick)    MyTextView click_text;    @Bind(R.id.image_onclick)    MyImageView click_image;    @Bind(R.id.layout_button_onclick)    ViewGroup click_layout;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_onclick_layout);        ButterKnife.bind(this);        clickAble();        initView();    }    private void clickAble() {        Log.i("button", "Button_isClick="+click_button.isClickable());        Log.i("textview", "TextView_isClick="+click_text.isClickable());        Log.i("imageview", "ImageView_isClick="+click_image.isClickable());    }    private void initView() {        click_button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Log.i("button", "Button_OnClick");            }        });        click_button.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View view, MotionEvent motionEvent) {                Log.i("button", "Button_setOnTouchListener");                return false;            }        });        click_text.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Log.i("textview", "textview_OnClick");            }        });        click_text.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View view, MotionEvent motionEvent) {                Log.i("textview", "textview_setOnTouchListener");                return false;            }        });        click_image.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Log.i("imageview", "imageview_OnClick");            }        });        click_image.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View view, MotionEvent motionEvent) {                Log.i("imageview", "imageview_setOnTouchListener");                return false;            }        });        click_layout.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.i("layout", "Layout_OnClick");            }        });        click_layout.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.i("layout", "Layout_OnTouchListener");                return false;            }        });    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("activity", "Activity_onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.i("activity", "Activity_dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }}
这里简单的说一下,clickAble()方法主要是看看Button,TextView,ImageView的默认的clickable是什么,为下面的分析左准备工作

initView()方法主要是设置Button,TextView,ImageView以及他们的parent的layout的事件,也就是添加Listener。


先来看看运行的结果图:


看下log:


可以看出Button的默认clickable为true。而TextView和ImageView 默认为false。

先说明一下,一次点击事件,包括Event的Action_down和Action_up所以TouchEvent事件会执行2次。

先点击一下黑色区域。看下log:


没什么说的,走向为:Activity_dispatchTouchEvent-------- >Activity_ouTouchEvent

再来点击一下上面的白色空白区域。看下log:


到这里的走向:Activity_dispatchTouchEvent------>Layout_dispatchTouchEvent------>Layout_onTouchListener(onTouch())

---->Layout_onTouchEvent---->Layout_onClick.

其实你会发现到这为止的log 和上一章说的没啥区别。这里的内层LinearLayout也即是白色的区域,相当于上一章的button。

至于为什么没有走activity_ouTouchEvent  也说过了。是因为被Layout的点击事件拦截并且处理掉了,至于怎么拦截怎么处理。

先别急,继续点击button,看下log:


先来看下方法的走向:Activity_dispatchTouchEvent------>Layout_dispatchTouchEvent------>Layout_onInterceptTouchEvent

---->Button_dispatchTouchEvent------>Button_setOnTouchEvent(onTouch())------>Button_onTouchEvent------->Button_onClick

仔细看一下发现(Layout_onTouchEvent---->Layout_onClick)都没有执行。而且多了个(Layout_onInterceptTouchEvent)

这又是为什么呢,会是我们所说的被拦截掉了吗,而(Layout_onInterceptTouchEvent)又是干什么的呢?

这一系列的问题,让我们看下源码便知,先来看看Layout_diapatchonTouchEvent方法的源码:

 /**     * {@inheritDoc}     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {            ev.setTargetAccessibilityFocus(false);        }        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }            // 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;            }            // If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }            // Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;            // Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                    final int childrenCount = mChildrenCount;                    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 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 there is a view that has accessibility focus we want it                            // to get the event first and if not handled we will perform a                            // normal dispatch. We may do a double iteration but this is                            // safer given the timeframe.                            if (childWithAccessibilityFocus != null) {                                if (childWithAccessibilityFocus != child) {                                    continue;                                }                                childWithAccessibilityFocus = null;                                i = childrenCount - 1;                            }                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                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;                            }                            // The accessibility focus didn't handle the event, so clear                            // the flag and do a normal dispatch to all children.                            ev.setTargetAccessibilityFocus(false);                        }                        if (preorderedList != null) preorderedList.clear();                    }                    if (newTouchTarget == null && mFirstTouchTarget != null) {                        // Did not find a child to receive the event.                        // Assign the pointer to the least recently added target.                        newTouchTarget = mFirstTouchTarget;                        while (newTouchTarget.next != null) {                            newTouchTarget = newTouchTarget.next;                        }                        newTouchTarget.pointerIdBits |= idBitsToAssign;                    }                }            }            // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                while (target != null) {                    final TouchTarget next = target.next;                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                            handled = true;                        }                        if (cancelChild) {                            if (predecessor == null) {                                mFirstTouchTarget = next;                            } else {                                predecessor.next = next;                            }                            target.recycle();                            target = next;                            continue;                        }                    }                    predecessor = target;                    target = next;                }            }            // Update list of touch targets for pointer up or cancel, if needed.            if (canceled                    || actionMasked == MotionEvent.ACTION_UP                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                resetTouchState();            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {                final int actionIndex = ev.getActionIndex();                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);                removePointersFromTouchTargets(idBitsToRemove);            }        }        if (!handled && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);        }        return handled;    }

第6—8行的if语句处理一些手势如:action_down,up,move判断手势,手势的传递,以及处理之前手势等等。这里不多讲

继续往下看 12行的if语句处理一些焦点问题。继续往下,17行的onFilterTouchEventForSecurity()方法是什么呢?看下

源码:

  /**     * Filter the touch event to apply security policies.     *     * @param event The motion event to be filtered.     * @return True if the event should be dispatched, false if the event should be dropped.     *     * @see #getFilterTouchesWhenObscured     */    public boolean onFilterTouchEventForSecurity(MotionEvent event) {        //noinspection RedundantIfStatement        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {            // Window is obscured, drop this touch.            return false;        }        return true;    }
过滤一些事件比如Window隐藏或者遮挡了直接返回false,正常情况下返回true。继续往下:

  1.  // Handle an initial down.  
  2.            if (actionMasked == MotionEvent.ACTION_DOWN) {  
  3.                // Throw away all previous state when starting a new touch gesture.  
  4.                // The framework may have dropped the up or cancel event for the previous gesture  
  5.                // due to an app switch, ANR, or some other state change.  
  6.                cancelAndClearTouchTargets(ev);  
  7.                resetTouchState();  
  8.            }  
这个是当手势按下的时候,清除处理view的手势,并且重新设置手势。继续往下,这里注意resetTouchState()方法:

 /**     * Resets all touch state in preparation for a new cycle.     */    private void resetTouchState() {        clearTouchTargets();        resetCancelNextUpFlag(this);        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        mNestedScrollAxes = SCROLL_AXIS_NONE;    }

第3句重置了disalloIntercept. ViewGroup中disalloIntercept默认为false。注意一下,这里可能和后面的requestDisallowIntercept()

方法设置无效有关。继续往下:

  1.  // Check for interception.  
  2.            final boolean intercepted;  
  3.            if (actionMasked == MotionEvent.ACTION_DOWN  
  4.                    || mFirstTouchTarget != null) {  
  5.                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  6.                if (!disallowIntercept) {  
  7.                    intercepted = onInterceptTouchEvent(ev);  
  8.                    ev.setAction(action); // restore action in case it was changed  
  9.                } else {  
  10.                    intercepted = false;  
  11.                }  
  12.            } else {  
  13.                // There are no touch targets and this action is not an initial down  
  14.                // so this view group continues to intercept touches.  
  15.                intercepted = true;  
  16.            }  
看下if语句的判断 为ACTION_DOWN或者首次touch对象不为空,进入。而(mGroupFlags & Flag_DISALLOW_INTERCEPT)

是什么呢?这里是判断是否设置了disallowIntercept,也就是是否设置了拦击。这里可以通过如下方法进行设置:

  /**     * {@inheritDoc}     */    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {            // We're already in this state, assume our ancestors are too            return;        }        if (disallowIntercept) {            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;        } else {            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // Pass it up to our parent        if (mParent != null) {            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);        }    }
此方法可以主动设置是否拦截事件。前面也说了要在适当的位置设置此方法,因为可能会重置。

当然前面说了,disalloIntercept默认是false。然后继续往下进入if语句中。重点来了:

onInterceptTouchEvent(ev)方法出现了,点进去看看里面是什么代码:

    /**     * 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.     *     * 

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: * *

    *
  1. You will receive the down event here. *
  2. 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. *
  3. 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(). *
  4. 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. *
* * @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。但是注释很长。就不一一解释了。直接看注释的@return那里。

如果此方法直接返回了true。就会拦截子View 的所有事件。是不是这样的呢,我们试试就知道了。

把Avtivity代码中的MyLinearLayout的onInterceptTouchEvent方法直接返回true。看下log:


无论点击Button,TextView,ImageView都是这个log。也验证了上面的说法。继续往下

49-58行主要是检测Event是否cancle事件等等。不多说。继续:

61行语句。如果没有cancel并且没有intercept事件。进入if语句内。

71行的if语句不多说也就是判断是不是Action_down,move等,

80行的removePointersFromTouchTargets()清除之前的Touch目标。接下来重点来了

  1.                    if (newTouchTarget == null && childrenCount != 0) {  
  2.                        final float x = ev.getX(actionIndex);  
  3.                        final float y = ev.getY(actionIndex);  
  4.                        // Find a child that can receive the event.  
  5.                        // Scan children from front to back.  
  6.                        final ArrayList preorderedList = buildOrderedChildList();  
  7.                        final boolean customOrder = preorderedList == null  
  8.                                && isChildrenDrawingOrderEnabled();  
  9.                        final View[] children = mChildren;  
  10.                        for (int i = childrenCount - 1; i >= 0; i--) {  
  11.                            final int childIndex = customOrder  
  12.                                    ? getChildDrawingOrder(childrenCount, i) : i;  
  13.                            final View child = (preorderedList == null)  
  14.                                    ? children[childIndex] : preorderedList.get(childIndex);  
  15.   
  16.                            // If there is a view that has accessibility focus we want it  
  17.                            // to get the event first and if not handled we will perform a  
  18.                            // normal dispatch. We may do a double iteration but this is  
  19.                            // safer given the timeframe.  
  20.                            if (childWithAccessibilityFocus != null) {  
  21.                                if (childWithAccessibilityFocus != child) {  
  22.                                    continue;  
  23.                                }  
  24.                                childWithAccessibilityFocus = null;  
  25.                                i = childrenCount - 1;  
  26.                            }  
  27.   
  28.                            if (!canViewReceivePointerEvents(child)  
  29.                                    || !isTransformedTouchPointInView(x, y, child, null)) {  
  30.                                ev.setTargetAccessibilityFocus(false);  
  31.                                continue;  
  32.                            }  
  33.   
  34.                            newTouchTarget = getTouchTarget(child);  
  35.                            if (newTouchTarget != null) {  
  36.                                // Child is already receiving touch within its bounds.  
  37.                                // Give it the new pointer in addition to the ones it is handling.  
  38.                                newTouchTarget.pointerIdBits |= idBitsToAssign;  
  39.                                break;  
  40.                            }  
  41.   
  42.                            resetCancelNextUpFlag(child);  
  43.                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {  
  44.                                // Child wants to receive touch within its bounds.  
  45.                                mLastTouchDownTime = ev.getDownTime();  
  46.                                if (preorderedList != null) {  
  47.                                    // childIndex points into presorted list, find original index  
  48.                                    for (int j = 0; j < childrenCount; j++) {  
  49.                                        if (children[childIndex] == mChildren[j]) {  
  50.                                            mLastTouchDownIndex = j;  
  51.                                            break;  
  52.                                        }  
  53.                                    }  
  54.                                } else {  
  55.                                    mLastTouchDownIndex = childIndex;  
  56.                                }  
  57.                                mLastTouchDownX = ev.getX();  
  58.                                mLastTouchDownY = ev.getY();  
  59.                                newTouchTarget = addTouchTarget(child, idBitsToAssign);  
  60.                                alreadyDispatchedToNewTouchTarget = true;  
  61.                                break;  
  62.                            }  
  63.   
  64.                            // The accessibility focus didn't handle the event, so clear  
  65.                            // the flag and do a normal dispatch to all children.  
  66.                            ev.setTargetAccessibilityFocus(false);  
  67.                        }  
  68.                        if (preorderedList != null) preorderedList.clear();  
  69.                    }  
先判断有没有新的touch目标,和子child是否为空。进入内部,开始循环内部的childView。看看有没有子View

接收point事件。

28行 先看下canViewReceivePointerEvents()和isTransformedTouchPointInView()方法:

    /**     * Returns true if a child view can receive pointer events.     * @hide     */    private static boolean canViewReceivePointerEvents(View child) {        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE                || child.getAnimation() != null;    }
很简单 ,如果childView有接收此pointer事件,返回true。

    /**     * Returns true if a child view contains the specified point when transformed     * into its coordinate space.     * Child must not be null.     * @hide     */    protected boolean isTransformedTouchPointInView(float x, float y, View child,            PointF outLocalPoint) {        final float[] point = getTempPoint();        point[0] = x;        point[1] = y;        transformPointToViewLocal(point, child);        final boolean isInView = child.pointInView(point[0], point[1]);        if (isInView && outLocalPoint != null) {            outLocalPoint.set(point[0], point[1]);        }        return isInView;    }
也不难,就是判断此childView 是否包含当前ponit坐标,也就是在点击范围内部。

其实这两个判断就是判断当前childView是否真正的接收到此Event。如果都没有,continue,结束本次循环。

如果有继续往下。

35行,如果child准备处理此次Event。结束for循环,继续往下。如果没有,继续执行for循环。

43行 dispatchTransformedTouchEvent()方法。多次在dispatchTouchEvent()方法中调用到。

如最上面的ViewGroup的dispatchTouchEvent()源码中的168和182行。都调用了此方法。

进去看看到底是啥:

    /**     * Transforms a motion event into the coordinate space of a particular child view,     * filters out irrelevant pointer ids, and overrides its action if necessary.     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.     */    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;    }
方法很长。不一一解读了。看下上面的此方法的官方注释。处理传递来的事件。如果child是空。调用super.dispatchEvent()方法。

如果不为空,根据条件调用child.dispatchEvent().如果chid或者child的parent有消耗此事件的返回true。没有则返回false。

到这了大概明白了。无论在上面的for循环内外。如果layout的所有childView中有接收并处理了Event事件,会直接消耗此Event。

如果没有则返回上一级layout处理此次Event,知道消耗此Event为止。如下图:


上图描述了VieGroup 的事件分发事件。也就是说如果没有消耗事件(返回false)。就会交给parent去处理。以此类推。

打个比方:一个团队老板要发个任务。流程为:

BOSS-------->研发经理--------->架构师---------->小组组长----------->个人

如果个人处理了此事件(返回true)。会反馈给上级,以此类推谁处理好了。

个人 --------> 小组组长--------> 架构师  --------> 研发经理-------->BOSS

如果个人没有处理好(返回false),组长就是处理此事情,如果继续返回false,继续交给上级处理,以此类推。

(view,viewGroup的onclick()事件,也就是点击事件是在onTouchEvent()的Action_up中执行的,这里就没加进去)

下面看下案例:

比如最上面的Activity如果把initView()方法注释掉,也就是不给childView添加任何的事件listener。看下log:


此为button的点击log图,看下textView的点击log:


此为TexiView的log图。看下ImageView的log图


有人会发现在没设置listener的时候,button执行完自己的onTouchEvent()后并没有返回给parent处理。而是直接消耗了

而TextView和ImageView没有消耗事件而是返回给parent去处理了。

让我们来看看最开头的log出的Button,TextView,ImagView的默认clickable.

Button为true。TextView,ImageView为false。为什么true就会消耗此事件呢?

上一章我们已经说过原因了。可以看下 Android  事件分发机制--childView

简单说下就是:在View 的onTouchEvent()方法中,如果view 的Clickable,或者longClickable或者contextClickable

任何一个为true。无论View是不是Enable的,都会返回true(消耗此事件)。具体原因看下上一章。

总结:

到此View,ViewGroup的事件分发机制已经说完了。花了很多精力,主要的还是要自己去实践

实践中结合源码去看,就会记忆深刻啦。

更多相关文章

  1. Android(安卓)API Guides---Calendar Provider
  2. Android进程优先级部分整理与理解
  3. Android(安卓)-- Messager与Service
  4. Android控件:EditText之setOnEditorActionListener的使用
  5. Android(安卓)依赖注入: Dagger 2 实例讲解(一)
  6. Android(安卓)自定义控件
  7. Android(安卓)webkit 事件传递流程通道分析
  8. Android(安卓)自动接听来电
  9. android fragments

随机推荐

  1. Hyperf日志如何查看组件
  2. 编译PHP扩展的方法
  3. 解决PHP里大量数据循环时内存耗尽问题的
  4. 简易实现HTTPS之自签名证书
  5. 21个php常用方法汇总
  6. php如何整合qq互联登录
  7. PHP实现手机网站支付(兼容微信浏览器)
  8. PHP自定义的 printf 函数新用途
  9. 我们还会继续使用PHP的原因
  10. 简易实现HTTPS之自动实现ssl