1. 程式人生 > >一文讀懂 Android TouchEvent 事件分發、攔截、處理過程

一文讀懂 Android TouchEvent 事件分發、攔截、處理過程

什麼是事件?事件是使用者觸控手機螢幕,引起的一系列TouchEvent,包括ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等,這些action組合後變成點選事件、長按事件等。

在這篇文章中,用打Log測試的方法來了解Android TouchEvent 事件分發,攔截,處理過程。雖然看了一些其他的文章和原始碼及相關的資料,但是還是覺得需要打下Log和畫圖來了解一下,不然很容易忘記了事件傳遞的整個過程。所以寫下這篇文章,達到看完這篇文章基本可以瞭解整個過程,並且可以自己畫圖畫出來給別人看。

先看幾個類,主要是畫出一個3個ViewGroup疊加的介面,並在事件分發、攔截、處理時打下Log.

GitHub地址:https://github.com/libill/TouchEventDemo

一、通過打log分析事件分發

這裡在一個Activity上新增三個ViewGroup來分析,這裡值得注意的是Activity、View是沒有onInterceptTouchEvent方法的。

一、瞭解Activity、ViewGroup1、ViewGroup2、ViewGroup3四個類

  1. activity_main.xml

     <?xml version="1.0" encoding="utf-8"?>
         <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context="com.touchevent.demo.MyActivity">
         <com.touchevent.demo.ViewGroup1
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:background="@color/colorAccent">
         <com.touchevent.demo.ViewGroup2
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_margin="50dp"
             android:background="@color/colorPrimary">
             <com.touchevent.demo.ViewGroup3
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:layout_margin="50dp"
                 android:background="@color/colorPrimaryDark">
             </com.touchevent.demo.ViewGroup3>
         </com.touchevent.demo.ViewGroup2>
         </com.touchevent.demo.ViewGroup1>
     </android.support.constraint.ConstraintLayout>  
  2. 主介面:MainActivity.java

     public class MyActivity extends AppCompatActivity {
         private final static String TAG = MyActivity.class.getName();
    
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_main);
         }
    
         @Override
         public boolean dispatchTouchEvent(MotionEvent ev) {
             Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
             boolean superReturn = super.dispatchTouchEvent(ev);
             Log.d(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
             return superReturn;
         }
    
         @Override
         public boolean onTouchEvent(MotionEvent ev) {
             Log.i(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev));
             boolean superReturn = super.onTouchEvent(ev);
             Log.d(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
             return superReturn;
         }
     }
  3. 三個ViewGroup,裡面的程式碼完全一樣:ViewGroup1.java,ViewGroup2.java,ViewGroup3.java。由於程式碼一樣所以只貼其中一個類。

     public class ViewGroup1 extends LinearLayout {
         private final static String TAG = ViewGroup1.class.getName();
    
         public ViewGroup1(Context context) {
             super(context);
         }
    
         public ViewGroup1(Context context, AttributeSet attrs) {
             super(context, attrs);
         }
    
         @Override
         public boolean dispatchTouchEvent(MotionEvent ev) {
             Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
             boolean superReturn = super.dispatchTouchEvent(ev);
             Log.d(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
             return superReturn;
         }
    
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
             Log.i(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev));
             boolean superReturn = super.onInterceptTouchEvent(ev);
             Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
             return superReturn;
         }
    
         @Override
         public boolean onTouchEvent(MotionEvent ev) {
             Log.i(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev));
             boolean superReturn = super.onTouchEvent(ev);
             Log.d(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
             return superReturn;
         }
     }

二、不攔截處理任何事件

新增沒有攔截處理任何事件的程式碼,看看事件是怎麼傳遞的,選擇Info,檢視Log.

從流程圖可以看出,事件分發從Activity開始,然後分發到ViewGroup,在這個過程中,只要ViewGroup沒有攔截處理,最後還是會回到Activity的onTouchEvent方法。

三、ViewGroup2的dispatchTouchEvent返回true

把ViewGroup2.java的dispatchTouchEvent修改一下,return 返回true使事件不在分發

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
 Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
 Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + true);
 return true;
}

此時的Log

從圖片可以看出,當ViewGroupon2的dispatchTouchEvent返回true後,事件不會再分發傳送到ViewGroup3了,也不會分發到Activity的onTouchEvent了。而是事件到了ViewGroupon2的dispatchTouchEvent後,就停止了。dispatchTouchEvent返回true表示著事件不用再分發下去了。

四、ViewGroup2的onInterceptTouchEvent返回true

把ViewGroup2.java的onInterceptTouchEvent修改一下,return 返回true把事件攔截了

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
    boolean superReturn = super.dispatchTouchEvent(ev);
    Log.d(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
    return superReturn;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.i(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev));
    Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + true);
    return true;
}

此時的Log


可以看出ViewGroup2攔截了事件,就不會繼續分發到ViewGroup3;而且ViewGroup3攔截了事件又不處理事件,會把事件傳遞到Activity的onTouchEvent方法。

五、ViewGroup2的onInterceptTouchEvent、onTouchEvent返回true

把ViewGroup2.java的onTouchEvent修改一下,return 返回true把事件處理了

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.i(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev));
    Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + true);
    return true;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    Log.i(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev));
    Log.d(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev) + " " + true);
    return true;
}


從流程可以總結出,當ViewGroup2的onInterceptTouchEvent、onTouchEvent都返回true時,事件最終會走到ViewGroup2的onTouchEvent方法處理事件,後續的事件都會走到這裡來。

上面通過log分析很清楚了,是不是就這樣夠了?其實還不行,還要從原始碼的角度去分析下,為什麼事件會這樣分發。

二、通過原始碼分析事件分發

一、Activity的dispatchTouchEvent

先看看Activity下的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

onUserInteraction方法

public void onUserInteraction() {
}

從程式碼可以瞭解

  1. 呼叫Activity的onUserInteraction方法,action為down時會進去onUserInteraction方法,但是這個是空方法不做任何事情,可以忽略。

  2. 呼叫window的superDispatchTouchEvent方法,返回true時事件分發處理結束,否則會呼叫Activity的onTouchEvent方法。

  3. 呼叫Activity的onTouchEvent方法,進入這個條件的方法是window的superDispatchTouchEvent方法返回false。從上面的分析(二、不攔截處理任何事件)可以知道,所有子View的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent都返回false時會調動Activity的onTouchEvent方法,這個時候也是使window的superDispatchTouchEvent方法返回false成立。

二、window的superDispatchTouchEvent

Activity的getWindow方法

public Window getWindow() {
    return mWindow;
}

mWindow是如何賦值的?
是在Activity的attach方法賦值的,其實mWindow是PhoneWindow。

Activity的attach方法

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
}

PhoneWindow的superDispatchTouchEvent方法

private DecorView mDecor;

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

DevorView的superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

而mDecor是一個繼承FrameLayout的DecorView,就這樣把事件分發到ViewGroup上了。

三、ViewGroup的dispatchTouchEvent

3.1 ViewGroup攔截事件的情況

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

這裡分為2種情況會判斷是否需要攔截,也就是當某一條件成立時,會執行onInterceptTouchEvent判斷是否需要攔截事件。

  1. 當actionMasked == MotionEvent.ACTION_DOWN時。
  2. 當mFirstTouchTarget != null時。mFirstTouchTarget是成功處理事件的ViewGroup的子View,也就是ViewGroup的子View在以下情況返回true時,這個在log分析流程圖輕易得到:

    2.1 dispatchTouchEvent返回true

    2.2 如果子View是ViewGroup時,onInterceptTouchEvent、onTouchEvent返回true

另外還有一種情況是disallowIntercept為true時,intercepted直接賦值false不進行攔截。FLAG_DISALLOW_INTERCEPT是通過requestDisallowInterceptTouchEvent方法來設定的,用於在子View中設定,設定後ViewGroup只能攔截down事件,無法攔截其他move、up、cancel事件。為什麼ViewGroup還能攔截down事件呢?因為ViewGroup在down事件時進行了重置,看看以下程式碼

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
    // Throw away all previous state when starting a new touch gesture.
    // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

通過原始碼可以瞭解到,ViewGroup攔截事件後,不再呼叫onInterceptTouchEvent,而是直接交給mFirstTouchTarget的onTouchEvent處理,如果該onTouchEvent不處理最終會交給Activity的onTouchEvent。

3.2 ViewGroup不攔截事件的情況

ViewGroup不攔截事件時,會遍歷子View,使事件分發到子View進行處理。

final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(
            preorderedList, children, childIndex);

    // If there is a view that has accessibility focus we want it
    // to get the event first and if not handled we will perform a
    // normal dispatch. We may do a double iteration but this is
    // safer given the timeframe.
    if (childWithAccessibilityFocus != null) {
        if (childWithAccessibilityFocus != child) {
            continue;
        }
        childWithAccessibilityFocus = null;
        i = childrenCount - 1;
    }

    if (!canViewReceivePointerEvents(child)
            || !isTransformedTouchPointInView(x, y, child, null)) {
        ev.setTargetAccessibilityFocus(false);
        continue;
    }

    newTouchTarget = getTouchTarget(child);
    if (newTouchTarget != null) {
        // Child is already receiving touch within its bounds.
        // Give it the new pointer in addition to the ones it is handling.
        newTouchTarget.pointerIdBits |= idBitsToAssign;
        break;
    }

    resetCancelNextUpFlag(child);
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        mLastTouchDownTime = ev.getDownTime();
        if (preorderedList != null) {
            // childIndex points into presorted list, find original index
            for (int j = 0; j < childrenCount; j++) {
                if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                }
            }
        } else {
            mLastTouchDownIndex = childIndex;
        }
        mLastTouchDownX = ev.getX();
        mLastTouchDownY = ev.getY();
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }
}
3.2.1 尋找可接收事件的子View

通過canViewReceivePointerEvents判斷子View是否能夠接收到點選事件。必須符合2種情況,缺一不可:1、點選事件的座標落在在子View的區域內;2、子View沒有正在播放動畫。滿足條件後,呼叫dispatchTransformedTouchEvent,其實也是呼叫子View的dispatchTouchEvent。

private static boolean canViewReceivePointerEvents(@NonNull View child) {
    return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
            || child.getAnimation() != null;
}

protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    ...

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

當dispatchTransformedTouchEvent返回true時,結束for迴圈遍歷,賦值newTouchTarget,相當於發現了可以接收事件的View,不用再繼續找了。

newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;

在addTouchTarget方法賦值mFirstTouchTarget。

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}
3.2.2 ViewGroup自己處理事件

另一種情況是mFirstTouchTarget為空時,ViewGroup自己處理事件,這裡注意第三個引數為null,ViewGroup的super.dispatchTouchEvent將呼叫View的dispatchTouchEvent。

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

3.3 View處理點選事件的過程

View的dispatchTouchEvent是怎麼處理事件的呢?

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    ...
    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;
        }
    }
    ...
    return result;
}
  1. 首先使用onFilterTouchEventForSecurity方法過濾不符合應用安全策略的觸控事件。

     public boolean onFilterTouchEventForSecurity(MotionEvent event) {
         //noinspection RedundantIfStatement
         if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
             // Window is obscured, drop this touch.
             return false;
         }
         return true;
     }
  2. mOnTouchListener != null判斷是否設定了OnTouchEvent,設定了就執行mOnTouchListener.onTouch並返回true,不再執行onTouchEvent。這裡得出OnTouchEvent的優先順序高於OnTouchEvent,便於使用setOnTouchListener設定處理點選事件。

  3. 另一種情況是進入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;
         }
         ...
     }

當View不可用時,依然會處理事件,只是看起來不可用。

接著執行mTouchDelegate.onTouchEvent

if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
        return true;
    }
}

下面看看up事件是怎麼處理的

/**
 * <p>Indicates this view can display a tooltip on hover or long press.</p>
 * {@hide}
 */
static final int TOOLTIP = 0x40000000;

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
        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)) {
                            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;
}

從上面程式碼可以瞭解,clickable、TOOLTIP(長按)有一個為true時,就會消耗事件,使onTouchEvent返回true。其中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);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}

如果View設定了OnClickListener,那performClick會呼叫內部的onClick方法。

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

public void setOnLongClickListener(@Nullable OnLongClickListener l) {
    if (!isLongClickable()) {
        setLongClickable(true);
    }
    getListenerInfo().mOnLongClickListener = l;
}

通過setOnClickListener設定clickable,通過setOnLongClickListener設定LONG_CLICKABLE長按事件。設定後使得onTouchEvent返回true。到這裡我們已經分析完成點選事件的分發過程了。

本文地址:http://libill.github.io/2019/09/09/android-touch-event/

本文參考以下內容:

1、《Android開發藝術探索