Android 事件分发学习心得
Android 事件是从Activity向Window、DecorView、ViewGroup、View这样依次传递的
事件传递主要搞清楚ViewGroup和View的传递流程:
一、View的事件分发流程
public boolean dispatchTouchEvent(MotionEvent event) {if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&mOnTouchListener.onTouch(this, event)) {return true;}return onTouchEvent(event);
}
事件传递到子View会先调用其dispatchTouchEvent,
1、如果子view注册了OnTouchListener,且在onTouch方法里做了事件处理返回true,则事件到此消费结束。
2、如果上面的if不成立,除了1外比如是不可用状态,会走view的onTouchEvent方法,如果进行了事件处理则返回true,否则返回false
总结: view的事件分发主要是在dispatchTouchEvent方法里控制
如果返回true代表消耗了该事件,false代表未消耗该事件。
且必须事件的第一个ACTION_DOWN返回了true,后续事件才会继续处理,否则后续事件不会进入处理
二、ViewGroup的事件分发流程
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传递,代码里面通过if条件进行跳过,最后target为null,然后调用super.dispatchTouchEvent,即ViewGroup父类View的dispatchTouchEvent进行处理,一般就调用ViewGroup的onTouchEvent事件了。
如果不进行拦截: 根据点击位置查找点击区域位于哪一个子View并调用子View的dispatchTouchEvent方法,如果子view dispatchTouchEvent返回true消费了事件则结束,如果没有消费事件则会走到target为null,super.dispatchTouchEvent
总结: ViewGroup如果拦截则自己在onTouchEvent处理,如果不拦截若点击区域子view处理了事件则结束,如果不处理则走ViewGroup的onTouchEvent
最后归纳: ViewGroup和View 的dispatchTouchEvent返回true代表事件被消费完毕,如果返回false,代表事件未被消费,则继续往上层ViewGroup的onTouch传递,这个过程的返回true和false建议追溯源码调试。