Android事件的分发机制
在分析Android事件分发机制前,明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View,表示可以有子控件,如Linearlayout、Listview这些。今天我们先来了解View的事件分发机制。
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.i("Tag", "This is button onClick event");
- }
- });
- btn.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- Log.i("Tag", "This is button onTouch action" + event.getAction());
- return false;
- }
- });
运行一下项目,结果如下:
- I/Tag: This is button onTouch action0
- I/Tag: This is button onTouch action2
- I/Tag: This is button onTouch action2
- I/Tag: This is button onTouch action1
- I/Tag: This is button onClick event
可以看到,onTouch是有先于onClick执行的,因此事件的传递顺序是先onTouch,在到OnClick。具体为什么这样,下面会通过源码来说明。这时,我们可能注意到了,onTouch的方法是有返回值,这里是返回false,我们将它改为true再运行一次,结果如下:
- I/Tag: This is button onTouch action0
- I/Tag: This is button onTouch action2
- I/Tag: This is button onTouch action2
- I/Tag: This is button onTouch action2
- I/Tag: This is button onTouch action1
- public boolean dispatchTouchEvent(MotionEvent event) {
- // If the event should be handled by accessibility focus first.
- if (event.isTargetAccessibilityFocus()) {
- // We don't have focus or no virtual descendant has it, do not handle the event.
- if (!isAccessibilityFocusedViewOrHost()) {
- return false;
- }
- // We have focus and got the event, then use normal event dispatch.
- event.setTargetAccessibilityFocus(false);
- }
- boolean result = false;
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onTouchEvent(event, 0);
- }
- final int actionMasked = event.getActionMasked();
- if (actionMasked == MotionEvent.ACTION_DOWN) {
- // Defensive cleanup for new gesture
- stopNestedScroll();
- }
- if (onFilterTouchEventForSecurity(event)) {
- //noinspection SimplifiableIfStatement
- ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null
- && (mViewFlags & ENABLED_MASK) == ENABLED
- && li.mOnTouchListener.onTouch(this, event)) {
- result = true;
- }
- if (!result && onTouchEvent(event)) {
- result = true;
- }
- }
- if (!result && mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
- }
- // Clean up after nested scrolls if this is the end of a gesture;
- // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
- // of the gesture.
- if (actionMasked == MotionEvent.ACTION_UP ||
- actionMasked == MotionEvent.ACTION_CANCEL ||
- (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
- stopNestedScroll();
- }
- return result;
- }
- public boolean onTouchEvent(MotionEvent event) {
- final float x = event.getX();
- final float y = event.getY();
- final int viewFlags = mViewFlags;
- final int action = event.getAction();
- if ((viewFlags & ENABLED_MASK) == DISABLED) {
- if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
- setPressed(false);
- }
- // 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)
- || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
- }
- if (mTouchDelegate != null) {
- if (mTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- }
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
- (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
- switch (action) {
- case MotionEvent.ACTION_UP:
- boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
- if ((mPrivateFlags & PFLAG_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 (prepressed) {
- // The button is being released before we actually
- // showed it as pressed. Make it show the pressed
- // state now (before scheduling the click) to ensure
- // the user sees it.
- setPressed(true, x, y);
- }
- if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
- // 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) {
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
- // If the post failed, unpress right now
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- mIgnoreNextUpEvent = false;
- break;
- case MotionEvent.ACTION_DOWN:
- mHasPerformedLongPress = false;
- if (performButtonActionOnTouchDown(event)) {
- break;
- }
- // Walk up the hierarchy to determine if we're inside a scrolling container.
- boolean isInScrollingContainer = isInScrollingContainer();
- // For views inside a scrolling container, delay the pressed feedback for
- // a short period in case this is a scroll.
- if (isInScrollingContainer) {
- mPrivateFlags |= PFLAG_PREPRESSED;
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPendingCheckForTap.x = event.getX();
- mPendingCheckForTap.y = event.getY();
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- } else {
- // Not inside a scrolling container, so show the feedback right away
- setPressed(true, x, y);
- checkForLongClick(0);
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- setPressed(false);
- removeTapCallback();
- removeLongPressCallback();
- mInContextButtonPress = false;
- mHasPerformedLongPress = false;
- mIgnoreNextUpEvent = false;
- break;
- case MotionEvent.ACTION_MOVE:
- drawableHotspotChanged(x, y);
- // Be lenient about moving outside of buttons
- if (!pointInView(x, y, mTouchSlop)) {
- // Outside button
- removeTapCallback();
- if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
- // Remove any future long press/tap checks
- removeLongPressCallback();
- setPressed(false);
- }
- }
- break;
- }
- return true;
- }
- return false;
- }
从源码的21行我们可以看出,该控件可点击就会进入到switch判断中,当我们触发了手指离开的实际,则会进入到MotionEvent.ACTION_UP这个case当中。我们接着往下看,在源码的50行,调用到了mPerformClick()方法,我们继续进入到这个方法的源码看看。
- public boolean performClick() {
- final boolean result;
- final ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- li.mOnClickListener.onClick(this);
- result = true;
- } else {
- result = false;
- }
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- return result;
- }
现在我们可以看到,只要ListenerInfo和mOnClickListener不为null就会调用onClick这个方法,之前说过,只要有监听事件,ListenerInfo就不为null,带mOnClickListener又是在哪里赋值呢?我们再继续看下它的源码。
- public void setOnClickListener(@Nullable OnClickListener l) {
- if (!isClickable()) {
- setClickable(true);
- }
- getListenerInfo().mOnClickListener = l;
- }
看到这里一切就清楚了,当我们调用setOnClickListener方法来给按钮注册一个点击事件时,就会给mOnClickListener赋值。整个分发事件的顺序是onTouch()-->onTouchEvent(event)-->performClick()-->OnClick()。
现在我们可以解决之前的问题。
1、onTouch方法是优先于OnClick,所以是执行了onTouch,再执行onClick。
2、无论是dispatchTouchEvent还是onTouchEvent,如果返回true表示这个事件已经被消费、处理了,不再往下传了。在dispathTouchEvent的源码里可以看到,如果onTouchEvent返回了true,那么它也返回true。如果dispatchTouchEvent在执行onTouch监听的时候,onTouch返回了true,那么它也返回true,这个事件提前被onTouch消费掉了。就不再执行onTouchEvent了,更别说onClick监听了。
Android事件的分发机制的更多相关文章
- Android NestedScrolling与分发机制
在Android5.0之间要实现控件的嵌套滑动,都是要自己处理View事件即分发机制. 共有三个方法: dispatchTouchEvent().onInterceptTouchEvent()和 ...
- View,ViewGroup的Touch事件的分发机制
原帖地址:http://blog.csdn.net/xiaanming/article/details/21696315 ViewGroup的事件分发机制 我们用手指去触摸Android手机屏幕,就会 ...
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
转自:xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315) 今天这篇文章主要分析的是Android的事件分发机制, ...
- Android事件总线分发库EventBus3.0的简单讲解与实践
Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...
- Android NestedScrolling与分发机制 二
上篇转载了 Android:30分钟弄明白Touch事件分发机制 这篇转载 Android中的dispatchTouchEvent().onInterceptTouchEvent()和onTouchE ...
- android事件拦截处理机制详解
前段时间刚接触过Android手机开发,对它的事件传播机制不是很了解,虽然网上也查了相关的资料,但是总觉得理解模模糊糊,似是而非,于是自己就写个小demo测试了一下.总算搞明白了它的具体机制.写下自己 ...
- 45、Android事件总线分发库的使用
事件总线分发库EventBus和Otto的简介及对比 什么是事件总线管理: a.将事件放到队列里,用于管理和分发b.保证应用的各个部分之间高效的通信及数据.事件分发c.模块间解耦 Event Bus是 ...
- android事件拦截处理机制具体解释
前段时间刚接触过android手机开发.对它的事件传播机制不是非常了解,尽管网上也查了相关的资料,可是总认为理解模模糊糊,似是而非,于是自己就写个小demo測试了一下. 总算搞明确了它的详细机制.写下 ...
- touch事件的分发机制
作者:谢昆 一段伪代码反应整个touch事件的分发 public boolean dispatchTouchEvent(MotionEvent event) { boolean consume = f ...
随机推荐
- javascript入门学习笔记
<button type="button" onclick="alert('Welcome!')">点击这里</button>alert ...
- 深入理解offsetTop与offsetLeft
做为走上前端不归路的我,以前只是认为offsetTop是元素的左边框至包含元素offsetParent的左内边框之间的像素距离,同理offsetRight是相对于上内边框.那么问题来了,包含元素off ...
- Android单位度量
px(像素):屏幕上的点. in(英寸):长度单位.mm(毫米):长度单位.pt(磅):1/72英寸.dp(与密度无关的像素):一种基于屏幕密度的抽象单位.在每英寸160点的显示器上,1dp = 1p ...
- 【BZOJ1901】Dynamic Rankings
Description 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是 ...
- Spring3.0提供的表达式语言spel
package com.zf.spel; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.D ...
- jquery mobile 主题
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...
- Hibernate的CRUD
1.CRUD: C:sesion.save() R:session.get()? session.load() D:session.delete() U:session.update() 2.读取数据 ...
- 如何写angularJS模块
angularJS中提供模块的概念,供我们把代码封装在模块单元中,使用模块能给我们带来的好处 保持全局命名空间的清洁 易于在不同应用间复用代码 demo.html <!doctype html& ...
- jQuery解析JSON的问题
在WEB数据传输过程中,json是以文本,即字符串的轻量级形式传递的,而客户端一般用JS操作的是接收到的JSON对象,所以,JSON对象和JSON字符串之间的相互转换.JSON数据的解析是关键. JS ...
- Day4 内置函数补充、装饰器
li = [11,22,33,44]def f1(arg): arg.append(55)#函数默认返回值None,函数参数传递的是引用li = f1(li) print(li) 内置函数补充: ...