Android View事件分发机制
最近在开发中遇到view滑动冲突的问题,由于一开始就知道这个问题与view事件分发有关,之后在网上看了几篇关于事件分发的资料后,开发中遇到的问题很快便得到解决。
在这里总结一下我对view事件分发的理解。
首先,看下事件分发流程图:
Button事件演示
在对view的事件分发机制进行分析前,我们可以通过一个demo看看Button的事件处理的流程。
在布局文件中添加一个button控件,然后在代码中实现Button的setOnClickListener和setOnTouchListener方法,注册Click监听和Touch监听。
/**
* button事件
*/
private void showButtonTouch() {
mBtn = (Button) findViewById(R.id.btn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "Button onClick");
}
});
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
Log.e(TAG, "Button onTouch " + "ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "Button onTouch " + "ACTION_MOVE");
break;
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "Button onTouch " + "ACTION_DOWN");
break;
}
return false;
}
});
}
demo运行起来之后点击button,看下log日志(在点击时移动一下保证ACTION_MOVE事件能被触发):
在这里通过日志可以看出事件处理的流程。
View事件分发源码解析
在View的源码中,我们可以看到dispatchTouchEvent方法,这个方法可以理解为是View事件分发的入口。(代码基于4.0.3即API 15,建议大家在理解源码时不要看太高的版本,高版本源码会有过多的优化,会妨碍我们对于代码主要功能逻辑的理解)
dispatchTouchEvent方法解析
以下是View中dispatchTouchEvent方法代码:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
//这个是View事件分发的主要逻辑判断
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
在这整个方法中,事件分发的关键就在这个判断中 (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) 。
如果这个判断为true,那么该方法返回true;否则,执行onTouchEvent()方法并根据onTouchEvent方法的返回值返回具体结果。
li != null:代码中ListenerInfo对象li,是View内定义的一个静态类,这个类内部定义了View中所有Listener相关的类,一般情况下这个类不为空,在这里不做过多解释(2.3版本源码中没有该对象)。
li.mOnTouchListener != null:是否为空,mOnTouchListener 对象就是我们通过mBtn.setOnTouchListener(new View.OnTouchListener() {})设置的,所以这里不为空。
(mViewFlags & ENABLED_MASK) == ENABLED:判断控件是否是enable,很显然为true。
li.mOnTouchListener.onTouch(this, event):最后就是判断onTouch方法中返回值是否为true,onTouch方法就是我们在Button控件注册Touch监听时@Override的onTouch方法。
在前面的demo中,因为mOnTouchListener.onTouch(this, event)方法的返回值为false,我们在log中看到button先执行touch事件在执行click事件。在这里我们将onTouch方法的返回值改为true,可以看到以下log:
在log中可以看到click方法没有被执行,这又是为什么呢?
其实在这里大家通过阅读dispatchTouchEvent方法代码可以想到,因为onTouch返回值为true,所以这个判断条件成立即dispatchTouchEvent方法返回true。if (onTouchEvent(event))->onTouchEvent方法没有被执行,会不会是由于这个原因导致click方法没有被执行呢? 很显然我们要看看onTouchEvent方法的源码。
onTouchEvent源码解析:
以下是onTouchEvent方法的源码:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
// 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));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//这是onTouchEvent代码主要逻辑功能
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & 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.
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
if (!mHasPerformedLongPress) {
// 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();