之前都是在网上看别人的文章,很容易忘掉,今天重新翻一下源码并简单记录一下
先简单看一下dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent这三个方法在Activity、ViewGroup、View中是怎么个流程。如下图(有些地方可能画的不太准确)

根据上图我们再梳理一下源码就容易很多了。
先从 Activity 的 dispatchTouchEvent看起
public boolean dispatchTouchEvent(MotionEvent ev) {
//判断是否是 MotionEvent.ACTION_DOWN 事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这是一个空的方法
onUserInteraction();
}
//调用到了 Window 中的 superDispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
上面的getWindow().superDispatchTouchEvent(ev)句话调用了抽象类Window 中的方法, PhoneWindow 是 Window 的唯一子类,所以我们看一下 子类PhoneWindow中的superDispatchTouchEvent方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
上面这个方法调用到了 DecorView 中的方法,而他是继承了 FrameLayout , FrameLayout又继承了ViewGroup,这里调用到了 ViewGroup 的 dispatchTouchEvent方法
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//该方法的最终返回值
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {//过滤触摸事件以确保安全
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down. 处理 ACTION_DOWN 事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在开始一个新的Touch之前先清空所有的状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.// onInterceptTouchEvent 方法的结果
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//禁用拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用 onInterceptTouchEvent()方法,方法返回false,
intercepted = onInterceptTouchEvent(ev);//该方法决定是否拦截事件
//恢复Action 防止他被改变
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
//默认情况下canceled 和 intercepted 为false
if (!canceled && !intercepted) {
...
//判断是否是ACTION_DOWN 事件,如果是那么表示这是一个系列事件的开始
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
//这个方法是对事件进行分发(分发给子View)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
if (mFirstTouchTarget == null) {
//如果没有子View消费事件,这里最终会调用ViewGroup的onTouchEvent()
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
ViewGroup中没有自己的onTouchEvent方法,ViewGroup是继承在View的,最终是通过dispatchTransformedTouchEvent方法将事件分发到View进行事件消费的。接下来看下这个方法是如何进行事件分发的
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
//如果 子View 为 null时说明事件不需要继续往下分发,调用本身父类(ViewGroup的父类是View)的dispatchTouchEvent,
//也就是说调用了View类中的dispatchTouchEvent方法
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
//如果子View不为null,则调用 子View的 dispatchTouchEvent 方法
handled = child.dispatchTouchEvent(transformedEvent);
}
...
// Done.
transformedEvent.recycle();
return handled;
}
上面调用到View类中的 dispatchTouchEvent 方法,事件就是从这个方法中消费的。
对于上面 super.dispatchTouchEvent 和 child.dispatchTouchEvent(transformedEvent); 这两个要分清,第一个是 ViewGroup 父类中的方法,第二个是 子View(比如 TextView、Button等) 的方法,这两个都是为了去调用 onTouchEvent 方法消费事件
从Activity——>ViewGroup到这算是结束了,接下来就是View中消费事件
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//按钮是不是Enable的
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//ListenerInfo 监听的合集,OnFocusChangeListener、OnScrollChangeListener、OnClickListener等等
//如果他不为null说明我们设置了监听
//判断是否设置了onTouchListener 监听
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//这里会执行OnTouchListener 的 onTouch 方法
result = true;
}
//如果设置了onTouchListener的监听并且返回为true时就不会调用onTouchEvent方法
//如果上面没有处理就会调用onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
上面这个方法可以看出onTouchListener、onTouchEvent 的优先级;onTouchListener—>onTouchEvent
还有一点 就onTouchListener、onTouchEvent这两个方法而言,如果没有 setOnTouchListener那么一定会执行onTouchEvent方法,如果设置了setOnTouchListener那就要看onTouch的返回值了
那么我们的setOnClickListener是什么时候调用的呢,接下来看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();
//是否可以点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
//mHasPerformedLongPress是否已经执行了长按事件,true已经执行了,false未执行
// 还未执行长按事件时才能进行
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//先移除还未被执行的长按事件
removeLongPressCallback();
// 只有在我们处于按下状态时才执行点击操作
if (!focusTaken) {
//使用Runnable 处理这个点击事件,而不是直接执行点击。目的就是在单击操作开始之前可以更新View的状态
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//真正执行Click的方法,内部调用了performClick这个方法
performClickInternal();
}
}
}
break;
case MotionEvent.ACTION_DOWN:
...
if (isInScrollingContainer) {
...
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
...
//长按事件,是一个延迟的消息,
//刚调用checkForLongClick方法时 mHasPerformedLongPress=false,
//当长按事件的延迟消息被执行后 mHasPerformedLongPress = true
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
...省略代码
break;
case MotionEvent.ACTION_MOVE:
...省略代码
break;
... 省略代码
}
最后看一下performClick这个方法,这个方法中还是使用LinstenerInfo 判断是否设置了 onClickListener监听,如果用户手动设置了setOnclickListener(), 就调用 onClick
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
//ListenerInfo 各种监听的合集
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//执行用户设置的 onClickListener 中的 onClick方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
到这整个流程就算是走完了,onTouchListener、onTouchEvent、onClick 的优先级也比较清楚了
onTouchListener—>onTouchEvent—>onClick
下面贴出事件分发的注意事项:

是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).
这是我在ActiveAdmin中的自定义页面ActiveAdmin.register_page"Settings"doaction_itemdolink_to('Importprojects','settings/importprojects')endcontentdopara"Text"endcontrollerdodefimportprojectssystem"rakedataspider:import_projects_ninja"para"OK"endendend我想做的是,当我单击“导入项目”按钮时,我想在Controller中执行rake任务。但是我无法访问该方法。可能是什
例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果
我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几
我有一个帖子属于城市的关系,城市又属于一个州,例如:classPost现在我想找到所有帖子及其所属的城市和州。我编写了以下查询来获取带有城市的帖子,但不知道如何在同一查找器中获取带有城市的相应州:@post=Post.find:all,:include=>[:city]感谢任何帮助。谢谢。 最佳答案 Post.all(:include=>{:city=>:state}) 关于ruby-on-rails-使用Rails事件记录获取二级模型,我们在StackOverflow上找到一个类似的问
我觉得我错过了什么。我正在编写一个rubygem,它允许与事件记录进行交互,作为其主要功能的附加功能。在为其编写测试用例时,我需要能够指定虚拟事件记录模型来测试此功能。如果我可以获得一个事件记录模型的实例,它不需要与数据库的任何连接,可以有关系,所有这些东西,但不需要我在数据库中设置表,那就太棒了。我对测试还很陌生,在Rails测试之外我也很陌生,但似乎我应该能够相当轻松地完成类似的事情,但我什么也没找到。谁能告诉我我错过了什么?我看过工厂、制造商、固定装置,所有这些似乎都想达到目标。人们如何在您只需要AR对象进行测试的地方测试gem? 最佳答案
我想创建一个模块,为从事件记录库继承的类提供一些通用方法。以下是我们可以实现的两种方式。1)moduleCommentabledefself.extended(base)base.class_evaldoincludeInstanceMethodsextendClassMethodsendendmoduleClassMethodsdeftest_commentable_classmethodputs'testclassmethod'endendmoduleInstanceMethodsdeftest_commentable_instance_methodputs'testinstanc