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

来源:https://www.cnblogs.com/liqw/archive/2019/09/09/11490088.html
-Advertisement-
Play Games

什麼是事件?事件是用戶觸摸手機屏幕,引起的一系列TouchEvent,包括ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等,這些action組合後變成點擊事件、長按事件等。 在這篇文章中,用打Log測試的方法來瞭解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開發藝術探索》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySQL中的日誌包括:錯誤日誌、通用查詢日誌、二進位日誌、慢查詢日誌等等。這裡主要介紹下比較常用的兩個功能:通用查詢日誌和慢查詢日誌。 錯誤日誌:記錄啟動、運行或停止mysqld時出現的問題。通用日誌:記錄建立的客戶端連接和執行的語句。二進位日誌:記錄所有更改數據的語句。還用於複製。慢查詢日誌:記 ...
  • 七、多表查詢 ​ 對於查詢在之前已經學過了簡單查詢、限定查詢、查詢排序,這些都屬於 SQL 的標準語句,而上一章的單行函數,主要功能是為了彌補查詢的不足。 ​ 而從多表查詢開始就正式進入到了複雜查詢部分。 7.1、基本語法 多表查詢就是在一條查詢語句中,從多張表裡一起取出所需要的數據。如果要想進行多 ...
  • apache Directory Studio 是一個 LDAP 服務的一個圖形化客戶端工具。本文主要講解一下 apache Directory Studio 簡單的使用方法。 ...
  • 1、瞭解 Apache Kafka 1.1、簡介 官網:http://kafka.apache.org/ Apache Kafka 是一個開源 消息系統 ,由Scala 寫成。是由Apache 軟體基金會開發的一個開源消息系統項目。 Kafka 最初是由LinkedIn 開發,並於2011 年初開源 ...
  • select @@global.sql_mode, mysql5.7 導致 group查詢出錯 windows下,報錯: sessionn_start(): Cannot send session cache limiter - headers already sent ...
  • 配置主伺服器:主伺服器1 Ip: 192.168.0.1 主伺服器2 Ip: 192.168.0.2 主伺服器1配置 2.1、修改mysql配置文件 Server-id = 1 #這是資料庫ID,此ID是唯一的,主庫預設為1,其他從庫以此ID進行遞增,ID值不能重覆,否則會同步出錯; log-bin ...
  • 裝載請標明出處:https://www.cnblogs.com/tangZH/p/11175120.html 觀察者模式 說白了,就是一個對發生改變,所有依賴於它的對象也發生改變,這是一對多的關係。 比如對象A,對象B,對象C。B與C依賴於A,那麼A發生改變,B與C也將發生改變。此時A是被觀察者,B ...
  • 歡迎參加“決勝Flutter” 實訓課程,這裡是你此次實訓之旅的起點。 本章將帶您快速瞭解移動開發的現狀,然後向您介紹Flutter的發展歷史以及優勢特點,最後一起動手,搭建高效的開發環境。 由於Flutter 跨平臺(同時支持Android、iOS、Web以及PC)的特性,本書將以Mac OS作為 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...