android 中事件传递实现分析
概述
我们在android 应用开发当中经常会用到onClick、onTouch等事件的处理,但是这个事件是如何分发到我们的View上的呢?或者是如果我们在LinearLayout 中的button或者textview 是谁先收到view的触摸或者点击事件呢?是父view先收到然后分发给你了嘛,今天就学习一下android当中关于事件分发机制。
同时监听了view的onClick和onTouch哪个消息先收到
首先需要明确的一点是任何view在触摸的时候首先执行的是view的dispatchTouchEvent 方法,
比如button ,button的父类是textview,textview的父类是View,在view中有:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
可以看到 在判断条件mOnTouchListener.onTouch(this, event)中执行了ontouch的回调,如果我们ontouch返回的false的话,我们的onclick事件就不会得到响应了。而onclick事件的回调就在onTouchEvent方法里面。
所以根据以上我们知道了 ontouch是优先onclick执行的,如果ontouch返回为true的话就会导致onclick无法执行。
Viewgroup当中的控件是如何得到触碰消息的。
首先需要知道viewgroup的概念,大家知道android当中所有的widget都是继承自view的,而viewgroup是包含了好几个view的组合,比如我们经常用到的linearlayout ,LinearLayout、RelativeLayout等都是继承自ViewGroup的,但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。我们平时用的各种布局都是Vie`wGroup的子类。
事件传递看看 ViewGroup 中dispatchTouchEvent的源码
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);
}
里面有个if (disallowIntercept || !onInterceptTouchEvent(ev))的判断,根据这2个条件的值来判断是否进入到里面执行,在 ViewGroup当中onInterceptTouchEvent 默认是返回为false的,你也可以复写这个方法,
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
返回为true的话,该viewgroup当中的子view将收不到ontouch的事件,因为在for 循环当中会通过一个for循环,遍历了当前ViewGroup下的所有子View,然后判断当前遍历的View是不是正在点击的View,如果是的话就会进入到该条件判断的内部,该View的dispatchTouchEvent,之后就走到第一部分讲的内容里面去了。
关于view的重绘
我们重绘view的时候都是调用的invalidate()方法,那看这个方法里应该就有重绘的流程了吧,