对于Android开发者来说,自定义View是必须攻克的一关,也是从初级工程师迈向高级的进阶关卡,要想通过此阶段,除了必须掌握View的测量、绘制、滑动等基础知识外,更要掌握View的核心知识点:View的事件分发,本篇就一起从源码的角度分析View和ViewGroup的事件分发机制;
1、View的事件分发
在我们平时的使用或写自定义View时,都会直接或间接的使用View的事件分发,View的事件分发主要与View源码中的3个方法有关:
- dispatchTouchEvent()
- onTouch()
- onTouchEvent()
下面我们针对这三个方法从源码学习和分析事件的分发,一起从本质上掌握View是如何在层层传递和消耗事件;
- dispatchTouchEvent(MotionEvent event)
public boolean dispatchTouchEvent(MotionEvent 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;
}
}
上面代码是dispatchTouchEvent()中的部分代码,也是与我们使用最接近的核心代码,首先会判断View是否设置触摸监听mOnTouchListener,如果设置则会调用OnTouchListener.onTouch()方法,如果此方法返回true,则dispatchTouchEvent()返回true即拦截事件,若onTouch()返回false,则调用onTouchEvent(),如果onTouchEvent()返回true则事件被消耗,否则事件继续传递;从上面的方法和叙述我们可以得出以下结论:
- 若View设置OnTouchListener,则先调用onTouch(),所以OnTouchListener的优先级高于onTouchEvent()
- 若onTouch()返回true,表示onTouch消耗事件,此时onTouchEvent()不会调用
- 若onTouch()返回false,此时onTouchEvent()被调用,若onTouchEvent返回true,事件被消耗
1.1、onTouchEvent()源码分析
- ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN; // 设置mPrivateFlags3为FINGER_DOWN标记
}
mHasPerformedLongPress = false; //设置false表示此事还未出发长按事件
boolean isInScrollingContainer = isInScrollingContainer(); // 调用父容器的shouldDelayChildPressedState(),默认true
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED; // 状态设置为中间状态PFLAG_PREPRESSED
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); // 延时发送执行CheckForTap中的run(),ViewConfiguration.getTapTimeout() = 100ms
} else {
setPressed(true, x, y);
checkForLongClick(0, x, y); // 直接检测长按事件
}
//CheckForTap中调用检测长按事件
@Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);//调用长按检测方法
}
// checkForLongClick中延时发送CheckForLongPress实例
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset); // getLongPressTimeout()为 500ms(系统默认的长按时间)
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick(mX, mY)) { //
mHasPerformedLongPress = true; // 设置标志表示触发长按;此标志是否为true取决于li.mOnLongClickListener.onLongClick的返回值
}
}
}
//在performLongClick()中代码会最终调用performLongClickInternal()
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this); //调用长按监听中的onLongClick();返回值影响mHasPerformedLongPress
}
以上代码是View的onTouchEvent()的ACTION_DIOWN执行逻辑,只粘贴了部分关键代码,所执行逻辑如上面注释,下面我们逐步分析以下:
- 首先将mPrivateFlags3设置为FINGER_DOWN标记
- 将mHasPerformedLongPress设置为false,表示点击还未触发长按事件
- 创建CheckForTap()实例,并延时发送执行CheckForTap中的run()
- 在checkForLongClick中延时发送CheckForLongPress实例,检测长按事件
- 在performLongClick()中代码会最终调用performLongClickInternal(),performLongClickInternal回调设置的mO