Android事件传递机制详解及最新源码分析——View篇
摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处。
对于安卓事件传递机制相信绝大部分开发者都听说过或者了解过,也是面试中最常问的问题之一。但是真正能从源码角度理解具体事件传递流程的相信并不多,那么接下来我将写四篇文章从我目前掌握的情况来与大家共同探讨一下安卓事件传递机制。四篇文章分别为:View篇,ViewGroup篇,Activity篇,以及最后一篇实用例子。废话少话,马上开始View篇。
从简单的Demo例子现象开始分析
我们先写一个简单的demo。布局文件,代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wl.viewdispatchtouchevent.MainActivity" > <Button
android:id="@+id/click"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Clear灬Heart" /> </RelativeLayout>
public class MainActivity extends Activity implements OnTouchListener, OnClickListener {
private static final String TAG = "WL";
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.click);
mButton.setOnTouchListener(this);
mButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
//
Log.i(TAG, "onClick :"+v);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//
Log.i(TAG, "onTouch :"+",...action :"+event.getAction()+"...View :"+v);
return false;
}
}
很简单吧,就一个Button,设置触摸以及点击事件,我们看看运行情况:
一,正常点击Button打印如下:

二,我们发现onTouch方法有个返回值,默认情况下是false,我们将返回值改为true,点击Button打印如下:

到这里根据运行情况我们就可以小小的总结一下:
1⃣️ View的是事件执行顺序为先执行onTouch在执行onClick事件
2⃣️如果我们在onTouch事件中返回true那么将不会执行onClick事件
相信上面这些很简单,这结论很多开发者也都知道,那么源码中是怎么控制的呢?接下来我们看看源码中的逻辑。
View事件传递机制源码分析(源码版本为API23)
首先我们要知道在安卓中触摸控件首先会触发控件的dispatchTouchEvent方法,然而在Button类中并没有找到这个方法,在其父类TextView中还是没有找到,那么继续向上找,果然在其"爷爷"类View中找到dispatchTouchEvent方法,其实这个方法在View子类(ViewGroup除外,对于ViewGroup下一篇我们在做讨论)中一般都没有,那么我们看下View中dispatchTouchEvent方法源码:
/**
* 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 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;
}
这个方法还可以,不算很长,对于分析源码,强烈建议大家只看重点部分,否则只会把自己绕进去出不来了。
第19行,定义一个标记result,并且最后会反回这个标记。
最重点的就是31-43行代码,首先if (onFilterTouchEventForSecurity(event))主要判断当前窗体是否被遮盖,如果遮盖则返回false, 那么也就不会执行32-42代码逻辑,从而也不会改变result的值,最后58行返回result,即返回false。
大多数情况下onFilterTouchEventForSecurity(event)返回true,那么就执行32-42的代码逻辑。
33行代码ListenerInfo li = mListenerInfo;这行代码有个疑问,ListenerInfo这个类是做什么的?通过搜寻我们可以在View内部找到这个类,其实就是个简单的静态内部类,用来定义View的各种监听事件,源码就不贴出来了,自行查找看一下。
34-36行代码if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))首先li不为空,然后li.mOnTouchListener 不为空,这里mOnTouchListener是在哪里赋值的呢?稍微有经验就应该想到是在设置触摸事件监听的方法中设置的,我们看下:
/**
* 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) {
getListenerInfo().mOnTouchListener = l;
}
果然是在这里对其赋值,这里的getListenerInfo()又是什么呢?很简单就是给View中定义的mListenerInfo赋值,源码如下:
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
接下来继续判断View是否是enabled,这个没什么多解释的,应该都理解。最后li.mOnTouchListener.onTouch(this, event)调用我们设置的onTouch事件。
前面判断都成立的前提下,34-36行if判断是否成立则由onTouch事件的返回值决定。如果onTouch返回false,则if判断不成立result依然为false。如果onTouch返回true,则if判断成立,可以继续执行37行代码将result置为true。那么result为true或者false有什么用呢?别急,继续往下看。
40-42行代码中if (!result && onTouchEvent(event))判断,我们会发现只有在 !result 为true的前提下才会继续执行onTouchEvent方法,通过上面分析,正常情况下,result是否置为true是由onTouch返回值决定的,默认情况下在我们设置触摸事件时系统帮我们返回false,如下(代码出自demo):
@Override
public boolean onTouch(View v, MotionEvent event) {
//
Log.i(TAG, "onTouch :"+",...action :"+event.getAction()+"...View :"+v);
return false;
}
所以默认情况下result变量不会置为true,则!result为true,可以继续执行onTouchEvent。如果我们人为将onTouch事件返回true则result会被置为true,进而不会执行onTouchEvent事件。
到目前为止我们还没有看到任何关于onClick事件的代码逻辑,别急,我们继续分析。
40行代码if (!result && onTouchEvent(event))是否成立通过以上分析得知默认情况下由onTouchEvent决定,那么onTouchEvent具体做了什么呢?我们来看一下:
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;
}
也还好,不算太长,我们还是分析重点部分。
7-16行代码分析可以发现如果View是disabled的,并且是clickable的,那么直接返回true, 消费掉此事件,只是不给予响应。(源码中注视已经说明)
如果View是disabled的,并且是disclickable的,那么直接返回false, 不消费掉此事件。默认情况下View都是enabled的,这里不会进入。
我们继续往下分析
24-137行,我们会发现只要View是disclickable状态则onTouchEvent事件就会返回false,只要View是clickable就会进入判断内部根据事件不同分别进行对应操作最后都会反回true。
Switch的判断ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE情况主要是做一些设置,记录状态操作。
在ACTION_UP情况下:
29-50代码主要判断之前是否按下过以及是否能获取焦点。
46行代码,主要判断是否已经执行长按事件,如果没有执行则进入if条件判断,接下来48行移除长按事件的回掉监听。
55-57行代码,判断mPerformClick是否为空,为空则初始化,PerformClick类是做什么的呢?看一下源码:
private final class PerformClick implements Runnable {
@Override
public void run() {
performClick();
}
}
很简单吧,就是一个继承自Runnable接口的类,
58-60行,重点。主要做的就是通过Handler将PerformClick对象mPerformClick,Post到主线程执行performClick()方法。
接下来我们具体看下performClick()方法做了什么。
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;
}
第3行代码不用过多解释了吧,之前分析过。
第4行进行if判断,li不为空好理解,那么li.mOnClickListener中mOnClickListener在哪赋值的呢?通过搜寻我们发现在设置点击事件的时候给其赋的值,如下:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
通过上面代码我们还会发现在对mOnClickListener赋值前会先判断View是否可以点击,如果不可以点击则通过代码设置为可点击。这就说明即使我们在布局中将View设置为不可点击状态,如果我们设置了其点击事件依然会从代码层面设置回可点击状态,依然可以响应点击事件,可自行测试。
如果我们设置了点击事件监听回掉那么第4行if条件判断就会成立,进而执行5-7逻辑。
第6行则调用了onClick方法,到此我们终于找到在哪里执行了onClick方法。
我们回看dispatchTouchEvent方法40-42行if判断,默认情况下!result为true, 如果onTouchEvent返回true则接下来将result置为true,相反则置为false。
dispatchTouchEvent返回true则告诉父类子类已经消耗此事件,如果返回false则告诉父类子类没有消耗掉此事件,也可以理解为子类不能处理父类传递过来的事件,之后父类也不会将后续事件传递给子类,只有前一个事件返回true,才会出发后续事件。
总结:
我们点击一个View会首先触发View的dispatchTouchEvent方法。
在dispatchTouchEvent方法中会判断View是否设置触摸事件,如果设置触摸事件并且是enable的状态,则调用onTouch方法。
如果View是disabled状态则不会执行onTouch事件,直接执行onTouchEvent方法。
如果onTouch方法中返回true则消耗掉此事件,dispatchTouchEvent直接返回true。
如果onTouch事件返回false,则继续向下执行onTouchEvent方法。
在onTouchEvent方法中会先判断View是否为disabled状态,如果是并且是clickable的则直接返回true,消耗此事件。如果是disabled状态,但是是disclickable状态则返回false。
如果View是enable状态并且是clickable状态则onTouchEvent方法返回true,进而dispatchTouchEvent方法返回true,消耗此事件。
如果View是enable状态但是是disclickable状态则onTouchEvent方法返回false,进而dispatchTouchEvent方法返回false,不消耗此事件。
好了到此为止View的事件分发机制我想说的就已经全部讲解完毕,其实很简单,但是非常重要,如果View事件分发机制掌握不好那么再学习ViewGroup的事件分发机制的时候肯定是要蒙逼的,因为ViewGroup的事件分发机制分析起来会绕一些。要复杂很多。
Android事件传递机制详解及最新源码分析——View篇的更多相关文章
- Android事件传递机制详解及最新源码分析——ViewGroup篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...
- Android事件传递机制详解及最新源码分析——Activity篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象
前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...
- Android 的事件传递机制,详解
Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...
- Android Touch事件传递机制详解 下
尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...
- 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING
<Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th 2014 Email:skyseraph00@163.com 更多精彩请直接 ...
- Android事件分发机制详解
事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...
- Android Touch事件传递机制详解 上
最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘 ...
- Android事件分发机制详解(1)----探究View的事件分发
探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...
随机推荐
- Java后端开发书架
本人摘录于江南白衣文章,文章地址:http://calvin1978.blogcn.com/articles/javabookshelf.html 书架主要针对Java后端开发. 3.0版把一些后来买 ...
- Maven基础
Maven基础 maven核心内容:依赖管理. Maven是Apache组织的开源项目,是项目构建工具.用来管理jar包之间的相互依赖关系 Maven是一个项目构建和管理的工具,提供了帮助管理,构建, ...
- 英语曰曰曰No.523
---恢复内容开始--- [一句话新闻] The iPhone's 10th Anniversary:Can Apple Revive Its iPhone Sales ? 1.A look back ...
- LeetCode题解 343.Integer Break
题目:Given a positive integer n, break it into the sum of at least two positive integers and maximize ...
- 第一章之s5pv210启动顺序
我所使用的开发板是:友善之臂smart210,cpu为s5pv210.u-boot版本是:u-boot-2012-10 1,首先在u-boot中配置相对应的开发板的配置文件 #make s5p_gon ...
- Promise和异步编程
前面的话 JS有很多强大的功能,其中一个是它可以轻松地搞定异步编程.作为一门为Web而生的语言,它从一开始就需要能够响应异步的用户交互,如点击和按键操作等.Node.js用回调函数代替了事件,使异步编 ...
- JavaScript中的call()、apply()与bind():
关于call()与apply(): 在JavaScript中,每个函数都有call与apply(),这两个函数都是用来改变函数体内this的指向,并调用相关的参数. 看一个例子: 定义一个animal ...
- 深搜(DFS)广搜(BFS)详解
图的深搜与广搜 一.介绍: p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify; orp ...
- Cocoapods 应用第一部分 - Xcode 创建 .framework 相关
问题的提出: 随着项目的越来越大,可能会出现好几个团队共同维护一个项目的情况,例如:项目组A负责其中的A块,项目组B负责其中的B块.....这几块彼此之间既独立,也相互联系.对于这种情况,可以采用约定 ...
- swift UITextField光标聚焦以及光标颜色修改
光标聚焦闪烁: nick_textField.becomeFirstResponder() 光标颜色修改 nick_textField.tintColor = UIColor.red 备份:http: ...