Android开发笔记(三十一)Android View的事件分发机制详细版(如何阅读与分析源码)
五一还过的快乐吗,是不是收假第一天感觉好累。今天为大家讲一下VIEW的事件分发过程,来**大家想要提高技术水平的欲望,这样也许就不累了吧。虽然网上有很多关于事件分发的文章,大多数思路都不是那么顺。我们做任何事情都应该从最简单的部分做起,从最简单的事情倒推其原理与过程,一步一步拼接成复杂的整体。那好,就让我们从最简单的Button点击事件说起(示例源码:https://download.csdn.net/download/gaoxiaoweiandy/11161215)。
1. 分析最简单的onClick单击事件
1.1 布局与JAVA代码
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout"
>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="Button" />
</RelativeLayout>
Java Code:
package com.example.event1;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends Activity implements OnClickListener {
private Button button1;
private RelativeLayout layout;
private ImageView imvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
layout = (RelativeLayout)findViewById(R.id.layout);
layout.setOnClickListener(this);
button1.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Log.i("eventTest", "OnClickListener----view:"+v);
}
}
上述代码里我们为button与根布局RelativeLayout都设置了onClick监听。当我们依次点击button1与layout时,onClick函数打印日志如下:
eventTest: OnClickListener----view:android.widget.Button
eventTest: OnClickListener----view:android.widget.RelativeLayout
这说明button与RelativeLayout的单击事件的分发过程还是有雷同的部分的,因为它们都继承于VIEW,不过还是有一些区别的,今天我们先讲解Button(View)的事件分发,至于RelativeLayout的事件分发,我们在下一篇VIEWGROUP的事件分发中讲解。Tip:在这里我们知道了事件分发的知识点可分为View事件分发与ViewGroup事件分发。由于Button,TextView,Imageview这类控件的事件分发过程其实都是由父类View的源码来完成的,所以我们把这些控件就看作VIEW.,把RelativeLayout、LinearLayout,FrameLayout都看作ViewGroup,因为它们的事件分发过程都是由直接父类ViewGroup的源码来控制。
1.2 分析源码,整理事件分发机制(本文以onClick事件为例)
此刻我们就来分析一下Button的onClick, android的事件是经历了一个什么过程才传递到这个onClick函数里的。我们运用“倒推”的思路来分析产生onClick调用的源头。好,那我们就看看源码中是谁调用了onClick,然后我们顺藤摸瓜,一步一步找到堆栈调用的源头函数。前面我们说了Button的事件过程是由基类View控制,那我们就来看看View的源码,在View的源码里找出是谁调用了onClick。View的源码2万多行,我来教大家如何来看源码,我们只能带着目的和思路去看源码,因为如果全看的话会陷入困惑与疲惫中。
我们知道点击Button时调用的是onClick函数,同时会有DOWN事件与UP事件的处理才会产生单击。OK,那我们现在有2个思路,第1,在View源码里搜索 "onClick(" 注意旁边有一个左括号 ,这个的目的是用来搜索哪个地方调用了onClick函数;第2,在View源码里搜索处理DOWN事件与UP事件的代码。我们先用第1个思路,搜“onClick(”, 搜出的代码片段包括以下:
1. DeclaredOnClickListener
第5623行搜到了"onClick(",它包含在DeclaredOnClickListener类里,这个是一个onClickListener的实现类,很显然我们在MainActivity.java里已经实现了onClickListener的onClick函数,所以这个DeclaredOnClickListener类应该不是我们要找的线索,不过我们还是证实一下,我们在View里再搜DeclaredOnClickListener类是用来做什么的,
我们最终在View的构造函数里找到了DeclaredOnClickListener类,我们看到第5280行与5288行,大家能想到这是正在做什么吗?R.styleable.View_onclick:是 读取的view的onClick属性,handlerName就是onClick的值,即将来单击view时要调用的函数名。DeclaredOnClickListener的onClick里调用了我们配置的函数名handlerName(mMethodName), 看到这里我估计大家已经明白了,我们是否玩过在XML里定义为按钮定义点击函数:
<Button
android:onClick="buttonClick"
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="Button" />
其中android:onClick指定了handlerName(mMethodName) 函数名为buttonClick, 直接在MainActivitry.java中去声明这个函数及实现代码即可实现单击响应处理。 这里就不再赘述了,我们明白了DeclaredOnClickListener是为了给xml里定义单击处理函数,并不是我们要找的 调用onClick的源头。OK,接下来我们看一下搜“onClick(”关键字,搜出来的代码片段2,我们逐个来辨别。
2. interface
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
这个不用多说了吧,就是MainActivity.java中为按钮定义单击处理代码时,实现的那个接口类的定义。我们要找的是调用(回调)onClick函数的地方,这个只是onClick函数的抽象声明。
3. callOnClick函数
/**
* Directly call any attached OnClickListener. Unlike {@link #performClick()},
* this only calls the listener, and does not do any associated clicking
* actions like reporting an accessibility event.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean callOnClick() {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
这个貌似有戏,其中li.mOnClickListener.onClick(this);正是调用了我们的MainActivity里的onClick函数。但是根据注释描述:这个只是调用了onClick函数,并不是点击事件引起的,它相当于一个普通函数的调用。这个应该是用于盲人模拟手指点击事件,直接调用onClick函数,关于模拟手指点击在此不予讨论。看来这个还不是我们需要的。
4. performClick函数
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
// NOTE: other methods on View should not call this method directly, but performClickInternal()
// instead, to guarantee that the autofill manager is notified when necessary (as subclasses
// could extend this method without calling super.performClick()).
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;
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);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
看见 if 语句 里面的 li.mOnClickListener.onClick(this) 正是调用了我们为button定义的onClick函数。其中i.mOnClickListener正是MainAcitivty.java中为button实现的那个onClickListener接口实例:button1.setOnClickListener(this);
OK,我们只搜索到了4个结果包含“onClick(”的代码片段,唯独这最后一个比较靠谱。那我们现在就来搜一下这个performClick函数在View中又是在哪里调用的? 经过在View.java源码里搜索“performClick”,发现performClickInternal调用了performClick函数,而performClickInternal函数又是在onTouchEvent里调用的,前提是控件可单击(clickable = true), 然后在case MotionEvent.ACTION_UP抬起事件下调用performClickInternal函数的。
public boolean onTouchEvent(MotionEvent event) {
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
return true;
}
......
return false;
}
Ok,至此我们得到了一个重要的结论:Button(View)的onClick函数是在View的onTouchEvent里调用的。
接下来我们来看一下onTouchEvent又是谁调起的。经过搜索原来是在dispatchTouchEvent事件分发函数里调起的。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)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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;
}
我们从以上代码中摘取重点代码片段:
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;
}
我们发现当第2个if语句条件中 result = false时 才会执行onTouchEvent(event)函数,从而才能响应单击函数。 然而,要想result = false,那么第1个if语句中,要么没有为Button设置onTouchListener, 要么设置了但是onTouch返回false。换句话,可以总结以下2个结论:
1. 没有为button设置onTouchListener, 则会执行onTouchEvent,从而onClick单击函数得以响应。 2. 为为button设置onTouchListener, 并实现了onTouch函数,让onTouch函数返回false, 同样会执行onTouchEvent,从而onClick单击函数得以响应;让onTouch返回true则不会执行onClick。
关于第1个结论我们在1.1的代码中已经证实,确实没有设置onTouchListener,从而响应了onClick函数。
关于第2个结论我们修改一下代码来证实,在此之前我们还是在这里先总结一下View事件分发链的过程如下:
dispatchTouchEvent---->onTouchListener(onTouch)----false-----> onTouchEvent---->onClick----dispatchTouchEvent
最先由dispatchTouchEvent捕捉事件并分发,先执行onTouchListener的回调函数“onTouch”,然后onTouch返回false的话,就会继续往下执行onTouchEvent,onClick等。好的,我们现在修改一下代码来证明我们通过分析源码得出的结论2.
1.3 证明上面的结论2
我们修改MainActivity.java代码,为button设置一个onTouchListener, 让它的回调函数onTouch分别返回false与true来看一下打印出的Log日志。
1.3.1 onTouchListener.onTouch返回false
修改后的MainActivity.java代码如下:
package com.example.event1;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends Activity implements OnClickListener, View.OnTouchListener {
private Button button1;
private RelativeLayout layout;
private ImageView imvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
layout = (RelativeLayout)findViewById(R.id.layout);
layout.setOnTouchListener(this);
layout.setOnClickListener(this);
button1.setOnClickListener(this);
button1.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
return false;
}
@Override
public void onClick(View v) {
Log.i("eventTest", "OnClickListener----view:"+v);
}
}
我们让onTouchListener.c函数返回false, 然后当点击Button时日志如下:
eventTest: onTouchListener:acton--0----view:android.widget.Button
eventTest: onTouchListener:acton--2----view:android.widget.Button
eventTest: onTouchListener:acton--1----view:android.widget.Button
eventTest: OnClickListener----view:android.widget.Button
0代表down按下事件,2代表Move事件(可能我们在单击的时候手指颤了一下,标注的单击事件只有Down与UP),3代表UP事件。 从日志中可以看出,我们的按钮经历了Down---Move---Up事件,同时onTouchListener.onTouch返回了false,就会执行onTouchEvent,所以onClick得以响应。
同理,我们也为RelativeLayout设置了onTouchListener.onTouch与onClick,点击RelativeLayout后,日志如下:
eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout
我们发现ViewGroup与View的事件分发在此一致,只是少了个事件2(Move事件,这次手没抖,呵呵)。当然我们前面提到了ViewGroup事件分发的有些细节还是与View不一样,这个我们下一次讨论。
1.3.2 onTouchListener.onTouch返回true
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
return true;
}
这时我们再次点击Button,日志如下:
eventTest: onTouchListener:acton--0----view:android.widget.Button
eventTest: onTouchListener:acton--1----view:android.widget.Button
我们发现只是打印出了onTouchListener.onTouch函数中的Down与Up事件,并没有打印出OnClickListener函数的执行日志,说明当onTouch返回true时,onTouchEvent不会执行,当然也就不会调用onClick了。
Ok,整个View的单击事件(或者直接说事件,因为我们只是以单击为例来讲解)分发的一个正常流程就介绍完毕了,同时验证了onTouchListener.onTouch返回false与true时,会影响onClick的执行。但是,这还未结束。接下来我们来看一下非正常流程,我们人为的让dispatchTouchEvent返回false,返回true。或者让onTouchEvent返回false或true, 然后来看一下事件分发的过程。这里我可以说明一下,我们上面演示的正常流程下,dispatchTouchEvent与onTouchEvent默认总是返回true。我们接下来先关注一下dispatchTouchEvent返回false或true对按钮的事件分发有何用.
2. 重写dispatchTouchEvent
此节我们让dispatchTouchEvent分别返回true和false,来看一下对button事件分发(接收)的影响。
2.1 dispatchTouchEvent返回true
为了自定义dispatchTouchEvent,我们需要自定义Button,代码如下:
package com.example.event1;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean dispatchResult = super.dispatchTouchEvent(event);
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
",result="+dispatchResult+",view:MyButton");
return dispatchResult;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEventResult = super.onTouchEvent(event); //这个默认返回true
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
",result="+onTouchEventResult+",view:MyButton");
return onTouchEventResult;
}
}
同时布局文件改为:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout"
>
<com.example.event1.MyButton
android:onClick="buttonClick"
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="Button" />
/>
</RelativeLayout>
MainActivity.java代码不变:
package com.example.event1;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends Activity implements OnClickListener, View.OnTouchListener {
private Button button1;
private RelativeLayout layout;
private ImageView imvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
layout = (RelativeLayout)findViewById(R.id.layout);
layout.setOnTouchListener(this);
layout.setOnClickListener(this);
button1.setOnClickListener(this);
button1.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+"----view:"+v);
return false;
}
@Override
public void onClick(View v) {
Log.i("eventTest", "OnClickListener----view:"+v);
}
}
我们在
dispatchTouchEvent与onTouchEvent调用了super类(View类)的函数,即打印出默认返回值。
猜猜它们默认返回true还是false,在此我猜它默认都返回true.
因为我们前面的正常事件分发流程已经验证了这个。我们单击Button还是看一下日志吧:
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
eventTest: onTouchListener:acton--1----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
eventTest: OnClickListener----view:com.example.event1.MyButton
我们的猜测是对的,默认dispatchTouchEvent与onTouchEvent都返回true. 按钮也如期执行了onClick函数。
函数执行顺序是
- Down事件(action:0):
dispatchTouchEvent(自定义Button)-----> super.dispatchTouchEvent(View) ----> onTouchListener.onTouch(打印onTouchListener日志)---false---->onTouchEvent(自定义Button)----->super.onTouchEvent(return true)--->打印onTouchEvent日志----->打印dispatchTouchEvent日志。
- UP事件(action:1)执行顺序同上。
为了更明显写,我们修改MyButton.java直接让dispatchTouchEvent与onTouchEvent返回true,效果是一样的。
package com.example.event1;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean dispatchResult = super.dispatchTouchEvent(event);
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
",result="+dispatchResult+",view:MyButton");
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
",result="+onTouchEventResult+",view:MyButton");
return true;
}
}
这里的super.dispatchTouchEvent(event)与super.onTouchEvent(event)不能省略,因为主要实现都在View源码里。
Ok,我们接下来我们让dispatchTouchEvent返回false试验下,我们猜测一下都会执行哪些事件,我反正猜测不出来,代码及打印出的日志如下:
2.2 dispatchTouchEvent返回false
package com.example.event1;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
dispatchResult = false;
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
",result="+dispatchResult+",view:MyButton");
return dispatchResult;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
",result="+onTouchEventResult+",view:MyButton");
return true;
}
}
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton
eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout
发现MyButton.dispatchTouchEvent返回false,会使Button只能接收到Down事件,后续的UP事件不再接收,同时会把DOWN事件向父布局分发,由于父布局的dispatchTouchEvent默认返回true,所以父布局RelativeLayout后续的UP事件还会继续接收,由于onTouchListener.onTouch统一返回false,所以RelativeLayout的onClick得以执行。
这里得出一个重要结论:只要View的dispatchTouchEvent在处理某一事件时返回了false(比如这里的DOWN事件),那么后续的action事件将不再由该View接收,会将DOWN事件向上传递给父容器ViewGroup。
2.3 onTouchEvent返回值对dispatchTouchEvent返回值的影响
2.3.1 onTouchEvent返回false
Ok,我们接下来看一下把onTouchEvent返回值改为false时,对dispatchTouchEvent的返回值的影响。
MyButton代码如下:
package com.example.event1;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
dispatchResult = false;
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
",result="+dispatchResult+",view:MyButton");
return dispatchResult;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
",result="+onTouchEventResult+",view:MyButton");
return false;
}
}
执行日志如下:
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton
eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout
发现效果与2.2一样没有变化,也就是说onTouchEvent返回false还是true不起决定作用, dispatchTouchEvent的返回false起了决定性作用。那么onTouchEvent返回false或true,在什么情况会影响dispatchTouchEvent,是在MyButton的dispatchTouchEvent返回super.dispatchTouchEvent的返回值的时候有影响:
package com.example.event1;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
",result="+dispatchResult+",view:MyButton");
return dispatchResult;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
",result="+onTouchEventResult+",view:MyButton");
return false;
}
}
点击Button,我猜测这样会和上述情况一样,只有DOWN事件被接收处理,后续的UP事件将不再由Button接收。猜测为这个结果的理由是当onTouchEvent返回了false,那么super.dispatchTouchEventy也就返回了false,因此MyButton.dispatchTouchEvent返回fasle,这样就和我们上面2.2节的MyButton.dispatchTouchEvent直接返回false的效果一样了。
我们看一下日志,看猜的是否正确。点击Button日志如下:
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=false,view:MyButton
eventTest: onTouchListener:acton--0----view:android.widget.RelativeLayout
eventTest: onTouchListener:acton--1----view:android.widget.RelativeLayout
eventTest: OnClickListener----view:android.widget.RelativeLayout
果然,结果一样!
2.3.2 onTouchEvent返回true
这时,接着把onTouchEvent强行返回true,MyButton代码如下:
package com.example.event1;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowInsets;
import android.widget.Button;
public class MyButton extends Button {
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
",result="+dispatchResult+",view:MyButton");
return dispatchResult;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
",result="+onTouchEventResult+",view:MyButton");
return true;
}
}
猜测结果是:Button可以正常响应click.而且时间不会分发给ViewGroup,理由是dispatchTouchEvent将返回true,接下来的UP事件Button还会接收到的。我们看下点击Button后的日志:
eventTest: onTouchListener:acton--0----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
eventTest: onTouchListener:acton--1----view:com.example.event1.MyButton
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
eventTest: OnClickListener----view:com.example.event1.MyButton
结果,真的是这样,MyButton一路接收了Down与UP事件,正常执行了onClick函数。
Ok,至此View的整个事件分发流程介绍完毕,我们总结为以下两点:
- View事件执行流程
dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick
2.dispatchTouchEvent的返回值对事件分发的影响
当返回true时,后续的事件,该View还会继续接收到,如Down---Up事件(Up还会继续接收到)
当返回false时,后续的事件,该View再也不能接收到,如Down---Up事件(接收完Down事件后,Up不会再分发给该VIEW,而是把Down事件转发给父容器ViewGroup,如果条件满足,UP事件以后也由ViewGroup接收,从而ViewGroup实现了单击函数)。
备注:该View源码是基于API28。同时ViewGroup的事件分发将在下一篇详解。
示例源码:https://download.csdn.net/download/gaoxiaoweiandy/11161215