Android 事件分发机制

本文详细解析了Android中事件分发机制的核心概念,包括dispatchTouchEvent分发、onInterceptTouchEvent拦截及onTouchEvent处理。通过源码分析展示了事件传递顺序及如何通过返回值控制事件的消费。

与分发相关机制相关的3个方法
1、dispatchTouchEvent() 分发机制
2、onInterceptTouchEvent() 拦截机制
3、onTouchEvent 点击

从阅读源码中学习,先来分析View的事件分发,然后在探究ViewGroup的事件分发。

eg: 在只有一个Activity的简单的工程中,并且Activity中只有一个按钮,这个按钮注册了点击事件,需要调用:
这里写图片描述

在onClick方法里面实现,就可以在按钮被点击的时候执行。如果再给它添加一个touch事件,需要调用:
这里写图片描述

在onTouch方法里面可以做很多事情,比如手指的按下 down,抬起 up,移动 move 等事件。通过onClick,onTouch两个事件,运行程序打印日志可以看到,onTouch先于onClick执行。因此事件的传递顺序是先经过onTouch 再到onClick的。

仔细看会发现,onTouch是有返回值的,这里我们返回的是false,如果把返回值改为true,运行的结果是onClick将不执行。可以先理解为onTouch方法返回true就认为这个事件被onTouch消费掉了,因此不会再传递下去。

通过阅读源码,我们知道,只要你触摸到任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。当我们点击按钮的时候,就会调用Button类里的dispatchTouchEvent方法,可是在Button类里找不到这样的方法,接着往它的父类TextView里找,会发现也没有,接着往TextView的父类View找,在View里面找到了这个方法。

dispatchTouchEvent方法的源码:

这里写图片描述

首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。

先看第一个条件,mOnTouchListener变量在哪里赋值?在view中有这里写图片描述

mOnTouchListener是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。

第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。

第三个条件比较关键,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。

再回到我们说传递顺序是先经过onTouch 再到onClick的。经过分析,dispatchTouchEvent中最先执行的就是onTouch方法,优先于onClick执行,如果onTouch方法返回true,就会让dispatchTouchEvent方法直接返回true,不再往下执行,onClick就不再执行了。

接下来我们分析onClick在onTouchEvent(event)方法中的调用。
onTouchEvent的源码:

**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;
}**

从 if ((viewFlags & ENABLED_MASK) == DISABLED) { 中,如果控件可点击就会进入switch判断中去,如果当前事件是抬起手指,则会进入MotionEvent.ACTION_UP这个case当中。在经过这种判断后会执行

***public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}* 这个方法。

如果mOnClickListener 不为空,就会调用onClick方法,那mOnClickListener又在哪里赋值的?赋值方法如下:
**public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}**

通过分析,知道调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。

这里写图片描述

从图中我们可以看的出来,View只有dispatchTouchEvent 和onTouchEvent两种机制。
ViewGroup有三种消费机制:这里写图片描述

处理事件:
用户在点击View(View指Button,TextView等)后,

1、返回值是true时,自己消费,不往里面传。返回值是false时,传递给ViewGroup,返回值为false时,自己不消费,往下一级传递,为true时,自己消费。

2、activity启动时,点击事件分发dispatchTouchEvent ,传递给ViewGroup,返回值为true时调用onInterceptTouchEvent方法拦截,然后调用自己的onTouchEvent方法,不再往下传递。

归纳
事件分发过程由dispatchTouchEvent控制,返回值为false时往下传递,下方考虑是否onInterceptTouchEvent拦截,返回值为true时,调用onTouchEvent方法,自己消费,否则继续往下传递。

总价
事件分发可以比喻成,爷爷把好吃的给父亲,父亲再给儿子,如果父亲自己吃了,就是拦截了。
事件消费可以比喻成,儿子把好吃的吃了,自己消费了,不再给父亲,爷爷传递,儿子没有消费,又传递给父亲,父亲拦截自己消费。
事件分发是由外往内一级一级传递,事件消费是由内往外一级一级传递的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值