前提
这篇博客主要讲 针对 View 的事件处理机制,所以,不存在 ViewGroup 的事件拦截
知识要点
在这个里面我们 主要关注以下两个方法 :
dispatchTouchEvent 作用:事件分发
onTouchEvent(一般都会被我们复写)
相关源码
// View.java 中的源码
public boolean dispatchTouchEvent(MotionEvent event) {
、、、省略
//初始化 result 标志位 false
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//如果 动作为按下 调用 stopNestedScroll() 方法 清除 滚动效果
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 是一个类 里面封装了很多 事件
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null // 这句话是 true
&& (mViewFlags & ENABLED_MASK) == ENABLED // 这句话也是 true 默认ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { //所以关键是看 这个方法返回 true或false
result = true;
}
if (!result && onTouchEvent(event)) { //View 中的 onTouchEvent方法里会执行 cliclk监听,注意是 View 里
result = true;
}
}
、、、省略
return result;
}
//其中有一个很重要的内部类 ListenerInfo
static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected OnContextClickListener mOnContextClickListener;
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
OnCapturedPointerListener mOnCapturedPointerListener;
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
}
// onTouchEvent 函数
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
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;
// 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;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
//在抬起事件中会触发 click 事件
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_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.
setPressed(true, x, y);
}
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)) {
//触发的关键点,以前这个地方好像是 performClick()
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
、、、省略
}
return true;
}
return false;
}
//performClickInternal()函数
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
//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);
//这个就是调用 onClick() 方法的关键之处
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
关键提炼
dispatchTouchEvent() | 用来分派事件 其中调用了 mOnTouchListener.onTouch() 一般设置监听 setOnTouchListener
onTouchEvent() 一般重写该方法
|
源码及其分析已在上面粘贴
| 用来监听 Touch 事件 dispatchTouchEvent() 第一个执行的函数,Touch() 方法 返回 true,则不会执行onTouch和click事件 |
if (li != null && li.mOnTouchListener != null // 这句话不等于空
&& (mViewFlags & ENABLED_MASK) == ENABLED // 这句话也是 true 默认ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { //所以关键是看 这个方法返回 true或false
result = true;
}
if (!result && onTouchEvent(event)) { //View 中的 onTouchEvent方法里会执行 cliclk监听,注意是 View 里
result = true;
}
这段源码中我们能够很清楚的看到,我们如果在 onTouch 方法中返回 true ,就会执行 result = true; 所以造成 !result 为 false ,导致 onTouchEvent(event) 不执行
onTouchEvent() | 用来处理事件。 返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View); 返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理 |