Android 事件分发机制详解(2)-ViewGroup

源码

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!onFilterTouchEventForSecurity(ev)) {
            return false;
        }

        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;

		//mGroupFlags可以通过requestDisallowInterceptTouchEvent赋值
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
		
		//首先处理ACTION_DOWN事件
        if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                mMotionTarget = null; //mMotionTarget置为空
            }
			如果disallowIntercept为true表示不允许viewgroup拦截事件,或者onInterceptTouchEvent返回false,那么就会把该ACTION_DOWN事件交给子View
            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];
					//该子view必须是可见的或者没有动画
                    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;
                            if (child.dispatchTouchEvent(ev))  { //如果子View的dispatchTouchEvent方法处理ACTION_DOWN事件返回true,那么就赋值mMotionTarget
                                // Event handled, we have a target now.
                                mMotionTarget = child; //当子View处理了ACTION_DOWN事件,就说明找到了事件的target了。。
                                return true;//子View处理了ACTION_DOWN事件,直接返回,不执行后续代码。
                            }
                        }
                    }
                }
            }
        }

		//如果action是ACTION_UP,ACTION_CANCEL。isUpOrCancel为true
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
                (action == MotionEvent.ACTION_CANCEL);

        if (isUpOrCancel) {
            // Note, we've already copied the previous state to our local
            // variable, so this takes effect on the next event
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // The event wasn't an ACTION_DOWN, dispatch it to our target if
        // we have one.
		//如果是ACTION_MOVE,ACTION_UP,ACTION_CANCEL事件则会执行到这里
        final View target = mMotionTarget; 
		//如果mMotionTarget为null,那么就交给该ViewGroup处理事件。。说明如果子View中dispatchTouchEvent中处理ACTION_DOWN事件返回false,那么后续的事件都交给viewgroup。
		//或者ACTION_DOWN事件中onInterceptTouchEvent返回了true,那么包括ACTION_DOWN事件的所有事件都交给viewgroup,子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);
        }

        // if have a target, see if we're allowed to and want to intercept its
        // events
        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); //生成一个ACTION_CANCEL事件交给子View处理
            ev.setLocation(xc, yc);
            if (!target.dispatchTouchEvent(ev)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            }
            // clear the target
            mMotionTarget = null; //如果拦截了,那么把mMotionTarget置空
            // Don't dispatch this event to our own view, because we already
            // saw it when intercepting; we just want to give the following
            // event to the normal onTouchEvent().
            return true;
        }

		//如果当前事件是up或者cancel,也会把mMotionTarget置空
        if (isUpOrCancel) {
            mMotionTarget = null;
        }

        // finally offset the event to the target's coordinate system and
        // dispatch the event.
        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;
        }
		//如果执行到了这一步,就说明该事件交给子View处理了
        return target.dispatchTouchEvent(ev);
    }
	
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }


    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }
    由上面可知:
    首先我们知道: Down Move Up事件依次发生
     子View的dispatchTouchEvent处理Down事件返回值才会影响后续事件的继续处理。。如果返回了false,那么该子view都接收不到后续的Move Up事件。。
    如果dispatchTouchEvent处理Move事件,返回false/true,都不会影响up事件的处理的。。子view都会处理

    /×这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。
    简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。×/
    前面部分是对的,后面就是错的了。 dispatchTouchEvent处理Move事件时并不会影响Up事件。
    
     但是如果在Move事件时把事件给拦截了,那么就会影响Up事件了,因为此时把mMotionTarget置为null了,那么move事件和up事件其实都是交给viewgroup处理
    
    为了验证这个问题,代码如下:

主界面:

<lbb.demo.test.MyLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <lbb.demo.test.MyButton
        android:id="@+id/mybutton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="MyButton"/>
</lbb.demo.test.MyLinearLayout>

测试代码

例子1

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyLinearLayout dispatchTouchEvent:" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyLinearLayout onTouchEvent:" + event.getAction());
        return super.onTouchEvent(event);
    }
}
public class MyButton extends Button {
    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyButton dispatchTouchEvent:" + event.getAction()); //0:Down 1:Up 2:Move 3:Cancel
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            super.dispatchTouchEvent(event);//先调用这个,否则onTouchEvent调用不到
            return false;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyButton onTouchEvent:" + event.getAction()); //0:Down 1:Up 2:Move 3:Cancel
        return super.onTouchEvent(event);
    }
}
打印结果:
11-05 03:33:05.219 13771-13771/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:0
11-05 03:33:05.219 13771-13771/lbb.demo.test D/LiaBin: MyButton dispatchTouchEvent:0
11-05 03:33:05.219 13771-13771/lbb.demo.test D/LiaBin: MyButton onTouchEvent:0
11-05 03:33:05.283 13771-13771/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:2
11-05 03:33:05.283 13771-13771/lbb.demo.test D/LiaBin: MyButton dispatchTouchEvent:2
11-05 03:33:05.283 13771-13771/lbb.demo.test D/LiaBin: MyButton onTouchEvent:2
11-05 03:33:05.306 13771-13771/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:2
11-05 03:33:05.306 13771-13771/lbb.demo.test D/LiaBin: MyButton dispatchTouchEvent:2
11-05 03:33:05.306 13771-13771/lbb.demo.test D/LiaBin: MyButton onTouchEvent:2
11-05 03:33:05.683 13771-13771/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:1
11-05 03:33:05.683 13771-13771/lbb.demo.test D/LiaBin: MyButton dispatchTouchEvent:1
11-05 03:33:05.683 13771-13771/lbb.demo.test D/LiaBin: MyButton onTouchEvent:1
可以看到,虽然dispatchTouchEvent中ACTION_MOVE事件返回了false,但是依然不影响UP事件的接收


例子2

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        super(context);
    }

    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyLinearLayout dispatchTouchEvent:" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            return true;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyLinearLayout onTouchEvent:" + event.getAction());
        return super.onTouchEvent(event);
    }
}
public class MyButton extends Button {
    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyButton dispatchTouchEvent:" + event.getAction()); //0:Down 1:Up 2:Move 3:Cancel
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(BaseApp.TAG, "MyButton onTouchEvent:" + event.getAction()); //0:Down 1:Up 2:Move 3:Cancel
        return super.onTouchEvent(event);
    }
}
打印结果:
11-05 03:30:04.192 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:0
11-05 03:30:04.192 10976-10976/lbb.demo.test D/LiaBin: MyButton dispatchTouchEvent:0
11-05 03:30:04.192 10976-10976/lbb.demo.test D/LiaBin: MyButton onTouchEvent:0
11-05 03:30:04.233 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:2
11-05 03:30:04.233 10976-10976/lbb.demo.test D/LiaBin: MyButton dispatchTouchEvent:3
11-05 03:30:04.233 10976-10976/lbb.demo.test D/LiaBin: MyButton onTouchEvent:3
11-05 03:30:04.249 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:2
11-05 03:30:04.249 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout onTouchEvent:2
11-05 03:30:04.266 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:2
11-05 03:30:04.266 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout onTouchEvent:2
11-05 03:30:04.524 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout dispatchTouchEvent:1
11-05 03:30:04.524 10976-10976/lbb.demo.test D/LiaBin: MyLinearLayout onTouchEvent:1
可以看到,如果viewgourp中拦截了move事件,那么move事件和后续的up事件都交给viewgroup了。。同时注意这里生成了一个cancel事件,见源代码分析   


高版本源码

以上的都是针对2.3.7以下的,如果以上,代码就不一样了。。但是本质上是一样的

可以参考 Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)


如果更深层一步了解  Android触摸屏事件派发机制详解与源码分析三(Activity篇)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值