View 的事件分发处理各种滑动冲突,复杂布局中事件处理的基础,这里对View的事件分发做一个简单的整理分析。
View的事件分发直接对应用户的操作就是对view的点击处理,就是对MotionEvent这个对象进行分析。
1.ViewGroup事件分发
我们首先分析ViewGroup的事件分发,其中我们需要了解其中最为重要的三个方法。
- dispatchTouchEvent()
用于分发接收到的事件,如果当前View可以接收到事件这个方法一定会被调用,返回true 表示当前view处理事件,false不处理,事件将继续传递给子view
- onInterceptTouchEvent()
在dispatchTouchEvent()内部调用,用于拦截判断是否拦截某个事件,一但拦截了事件在同一个事件序列中此方案讲不再被调用。这里的同一事件序列指的是用户操作屏幕后产生的down,move,up一套完整的流程。
- onTouchEvent()
在dispatchTouchEvent()内部调用,用于处理点击事件。返回true表示消耗当前事件,false表示不消耗,并且在同一事件序列下无法在接收事件。
他们三者的关系可以用一段伪代码表示
public boolean dispatchTouchEvent(Motion e){
boolean result=false;
if(onInterceptTouchEvent(e)){
//如果当前View截获事件,那么事件就会由当前View处理,即调用onTouchEvent()
result=onTouchEvent(e);
}else{
//如果不截获那么交给其子View来分发
result=child.dispatchTouchEvent(e);
}
return result;
}
总结
1.一个点击事件产生后当根ViewGroup接收到事件,他的dispatchTouchEvent就会被调用,当onInterceptTouchEvent方法返回true的时候,表示当前的view要拦截这个事件,那么后续的事件就会交给当前的View去处理。就会接着调用onTouchEvent方法。onInterceptTouchEvent返回为false的时候调用子view的dispatchTouchEvent方法直到这个事件被处理。
1.View事件分发
这里的view是不含ViewGroup的。我们看下View的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;
}
view是没有子元素的,所以他的事件没法继续往下传递。其中有段代码比较重要
//处理点击事件
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//先判断有没有设置mOnTouchListener,并判断mOnTouchListener.onTouch的返回值
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果mOnTouchListener的onTouch方法返回true,则onTouchEvent就不会被调用
if (!result && onTouchEvent(event)) {
result = true;
}
}
我们在看一下onTouchEvent方法
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
.....
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//不可用的View照样会消耗点击事件
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
......
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
}
总结
1.View会先判断有没有设置onTouchListener,如果mOnTouchListener的onTouch返回true则onTouchEvent就不会被调用,说明onTouchListener的优先级会高于onTouchEvent。
2.不可用的view依然会消耗点击事件
3.如果view设置有TouchDelegate,会执行TouchDelegate的touch方法
4.ACTION_UP中会触发PerformClick,如果view设置了onClickListener,PerformClick会调用onclick方法。因此知道OnTouchListener的优先级高于OnClick。OnClick是在onTouchListener没被设置的情况下onTouchEvent中触发的