本篇文章咱们专门来研究一下view层的事件分发机制,咱们在学习过程当中总会碰到关于事件分发的各类问题,如onTouch和onTouchEvent的关系,setOnTouchListener和setOnClickListener的关系等等,相似这样的问题不少,结论咱们都知道,有的时候是死记硬背的,记不长久,本篇文章咱们来从源码的角度来分析总结一下各类关系,这样才能理解,便于记忆。javascript
//Android源码环境
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
}
//分析工具
Android Studio 2.2.3
Build #AI-145.3537739, built on December 2, 2016
JRE: 1.8.0_112-release-b05 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o复制代码
接下来咱们正式分析一下view层的事件分发的源码。首先要知道一点,对于view层次的,事件分发主要有两个方法,dispatchTouchEve和onTouchEvent,咱们主要对这两种方法进行分析。html
咱们先经过自定义一个button来进行分析。自定义的button很简单,就是重写了一下dispatchTouchEve和onTouchEvent两个方法。java
public class MyButton extends Button {
protected static final String TAG = "liji-view-test";
public MyButton(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
}复制代码
自定义的MyButton很简单,就是重写了view的两个方法,咱们在这两个方法中只进行一些log操做,其余不改变。接着咱们在activity中使用这个自定义的MyButton。android
mMyButton = (MyButton) findViewById(R.id.myButton);
mMyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"onClick button click");
}
});
mMyButton.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouch ACTION_UP");
break;
default:
break;
}
return false;
}
});复制代码
能够看到,在activity中咱们也处理了两个方法,一个是setOnTouchListener、一个是setOnClickListener,而后运行一下,咱们能够看看log结果是什么。git
D/liji-view-test: dispatchTouchEvent ACTION_DOWN
D/liji-view-test: onTouch ACTION_DOWN
D/liji-view-test: onTouchEvent ACTION_DOWN
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_UP
D/liji-view-test: onTouch ACTION_UP
D/liji-view-test: onTouchEvent ACTION_UP
D/liji-view-test: onClick button click复制代码
能够大概看出来事件响应的顺序是:github
dispatchTouchEvent -> onTouch -> onTouchEvent -> onClick复制代码
从上面的log能够看出来,onTouch是优先于onClick执行的,而且onTouch执行了屡次,一次是ACTION_DOWN,一次是ACTION_UP,还有几回是ACTION_MOVE。所以事件传递的顺序是先通过onTouch,再传递到onClick。ide
onTouch方法是有返回值的,若是咱们尝试把onTouch方法里的返回值改为true,再运行一次就会发现onClick方法再也不执行了,这是由于onTouch方法返回true就认为这个事件被onTouch消费掉了,于是不会再继续向下传递。工具
这其中的原因到底是怎么样的?咱们经过源码来一探究竟。view事件分发的顺序是从dispatchTouchEvent开始的,因此咱们就从它开始分析:post
首先咱们进入view的dispatchTouchEvent方法中查看。学习
//view.java
public boolean dispatchTouchEvent(MotionEvent event) {
//...
boolean result = false;
//...
if (onFilterTouchEventForSecurity(event)) {
//...
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;
}
}
//...
return result;
}复制代码
咱们省略了其中无关的代码,只看对分析有用的代码,咱们进入到if中去,首先看到一个对象ListenerInfo的li对象指的是什么,
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
protected OnScrollChangeListener mOnScrollChangeListener;
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
//...
}复制代码
看到没有,其实这个li指的就是咱们设置的一些监听器,包括onTouchListener、onClickListener等等,咱们接着分析if中的条件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}复制代码
能够确认这里面的li!=null,因此第一个条件为true,第二个条件咱们由于设置了onTouchListener事件监听,因此这里面的li.mOnTouchListener != null也是为true,再看第三个条件(mViewFlags & ENABLED_MASK) == ENABLED,由于咱们的button是能够点击的,因此这里面也是为true,若是碰到不可点击的,如ImageView,这里面就是false了,咱们到时候另外再谈,咱们接着看下面一句代码。
li.mOnTouchListener.onTouch(this, event))复制代码
这句代码说明什么?若是咱们在setOnTouchListener里面返回true的话,那么咱们将直接返回result=true了,若是返回了false的话,那么这个if条件就不成立,因此它将会执行下一行代码if语句端判断-即它将会执行onTouchEvent事件
if (!result && onTouchEvent(event)) {
result = true;
}复制代码
由于咱们都是设置的默认返回值,因此在一开始的时候咱们的log日志显示的顺序是:
dispatchTouchEvent -> onTouch -> onTouchEvent -> onClick复制代码
这个时候就看看onTouch返回结果了,返回的结果不一样致使的顺序也不一样。咱们接着看看onTouchEvent的源码,分析一下里面藏了什么东西。
public boolean onTouchEvent(MotionEvent event) {
//...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
//...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
//...
break;
case MotionEvent.ACTION_DOWN:
//...
break;
case MotionEvent.ACTION_CANCEL:
//...
break;
case MotionEvent.ACTION_MOVE:
//...
break;
}
return true;
}
return false;
}复制代码
咱们在onTouchEvent方法中查看一下,省略一些无关的代码,咱们发现了其中有一个方法就是在手指松开的时候action=MotionEvent.ACTION_UP的时候,会调用这个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;
}复制代码
看到没?这里面就涉及到了onClick事件了,这也间接的证实了,onTouch的事件优先级高于onClick的优先级。
到了这里,咱们就能够总结一下关于一开始提出来的几个问题:
一、onTouch和onTouchEvent有什么区别,又该如何使用?
从源码中能够看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。若是在onTouch方法中经过返回true将事件消费掉,onTouchEvent将不会再执行。
另外须要注意的是,onTouch可以获得执行须要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。所以若是你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行(&&操做符,若是前面的判断为false的话,后面就不判断了)。对于这一类控件,若是咱们想要监听它的touch事件,就必须经过在该控件中重写onTouchEvent方法来实现。
二、onTouch和onClick优先级
咱们从源码中也能够分析获得:onTouch的优先级高于onClick的优先级,其中onClick的事件是在onTouchEvent中产生的。
判断是否发生onTouchEvent事件的条件有三个。(1)设置OnTouchListener监听,(2)该view是不是enable的,(3)在onTouch方法中返回true
若是上述三个条件有一个没有知足即为FALSE的话,那么它将执行onTouchEvent事件同时将产生onClick事件。
三、touch事件的层级传递
咱们都知道若是给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里须要注意,若是你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再获得执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
说到这里,不少的朋友确定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都获得执行了吗?其实你只是被假象所迷惑了,让咱们仔细分析一下,在前面的例子当中,咱们到底返回的是什么。参考着咱们前面分析的源码,首先在onTouch事件里返回了false,就必定会进入到onTouchEvent方法中,而后咱们来看一下onTouchEvent方法的细节。因为咱们点击了按钮,就会进入到第14行这个if判断的内部,而后你会发现,无论当前的action是什么,最终都必定会走到第89行,返回一个true。是否是有一种被欺骗的感受?明明在onTouch事件里返回了false,系统仍是在onTouchEvent方法中帮你返回了true。就由于这个缘由,才使得前面的例子中ACTION_UP能够获得执行。
那咱们能够换一个控件,将按钮替换成ImageView,而后给它也注册一个touch事件,并返回false。在ACTION_DOWN执行完后,后面的一系列action都不会获得执行了。这又是为何呢?由于ImageView和按钮不一样,它是默认不可点击的,所以在onTouchEvent的内部判断时没法进入到if的内部,直接跳到第最后面返回了false,也就致使后面其它的action都没法执行了。
接下来咱们来总结一下各个事件发生的流程。
针对于view来讲,当发生一个事件时(譬如:onTouch事件),这个时候就会调用view的dispatchTouchEvent事件,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断,也就是说,这个onTouch事件是不会继续执行下去了,就执行完一个dispatchTouchEvent事件,当它返回false时事件继续传递到onTouchListener中,这个onTouchListener(onTouch事件)也是一个拥有boolean类型的返回值的方法,默认返回false,这个时候就能够继续执行onClick(在onTouchEvent事件中)事件了,若是onTouch事件返回了true,那么就表明这个事件被它本身给消耗掉了,不会再继续传递。
用一张图来表示下:
对于View中的dispatchTouchEvent方法,在这个方法内,首先是进行了一个判断,里面有三个条件,若是这三个条件都知足,就返回true,不然就返回onTouchEvent方法执行的结果。对于第一个条件是一个mOnTouchListener变量,这个变量是在View中的setOnTouchListener方法里赋值的,也就是说只要咱们给控件注册了touch事件,mOnTouchListener就必定被赋值了。第二个条件是判断当前点击的控件是不是enable的,按钮默认都是enable的,所以这个条件恒定为true。第三个条件最为关键,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说若是咱们在onTouch方法里返回true,就会让这三个条件所有成立,从而整个方法直接返回true。若是咱们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
到这里,整个view的事件分发就比较清楚了,接下来咱们分析关于viewGroup的事件分发了。
github: github.com/crazyandcod…
博客: crazyandcoder.github.io
一、http://www.cnblogs.com/linjzong/p/4191891.html
二、http://3y.uu456.com/bp_5728a9pduw6m3qo9y5s6_1.html