Android中的控件大体分为View和ViewGroup两类,而ViewGroup本质也是View(继承自View),本篇文章也主要分析View的事件分发(源码基于Android3.0)
View事件分发:
View的事件传递是从dispatchTouchEvent中开始的,在View中我们找到该方法:
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (!onFilterTouchEventForSecurity(event)) {
return false;
}
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
在该方法中我们可以看到主要的判断条件:mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&mOnTouchListener.onTouch(this, event)。
mOnTouchListener指的是该view是否设置了onTouchListener:
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
(mViewFlags & ENABLED_MASK) == ENABLED 代表该View是否可用通过setEnable(boolean enable)来改变
mOnTouchListener.onTouch(this, event)顾名思义就是onTouchListener的返回值
可以看出只有这三个条件同时返回满足dispatchTouchEvent方法会返回True,否则就会去执行返回onTouchEvent(event),接下来我们来看一下该方法都做了哪些事:
/**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
首先是判断了view是否是enable的如果不可用就会返回CLICKABLE或者LONG_CLICKABLE,一般不手动设置enable默认都是true的所以这一块跳过,下面是对mTouchDelegate的一些判断,该值主要是处理增大view的点击区域的问题,如有需要自己查阅相关资料,这里不过多说明,如果不手动设置mTouchDelegate是null的所以也跳过。
接下来是重点((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE),如果view是可点击或可长按的就会进入条件内部,在条件中对MotionEvent的action做了处理,其中在MotionEvent.ACTION_UP分支中我们可以看到performClick()方法的调用,而找到该方法:
/**
* Call this view's OnClickListener, if it is defined.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
可以看出这里正式对view有没有设置onClickListener,如果有就调用其onClick方法。
从onTouchEvent的源码中我们可以看出只要进入了((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)判断内部,无论action的值为什么最终该方法都会返回true,由此可以大胆的推断出,ontouch事件中的action触发的前提是上一个action返回的是true。对于这个问题我们可以通过设置view的clickAble为false并为其设置ontouch事件来观察action的触发情况,将出现执行了down之后就不再执行。
至此我们大致就可以弄清楚以下几个问题:
- 1.view的onTouch方法先于onclick调用
- 2.如果onTouch中返回true,onTouchEvent方法不会调用onclick事件就不会触发。
- 3.onTouch方法执行的前提是onTouchListener不为null,并且view的enable为true。如果View是不可用的我们只能通过重写该view的onTouchEvent方法来监听事件。
- 4.无论怎么做处理(前提view可用,事件没有被父容器拦截)子view的down事件一定会触发
view的方法调用dispatchTouchEvent--->onTouch-->onTouchEvent-->onClick
最后附上View的事件分发步骤图解:
参考资料:
https://blog.youkuaiyun.com/guolin_blog/article/details/9097463