Android進階知識樹——View、ViewGroup事件分發機制詳解

對於Android開發者來講,自定義View是必須攻克的一關,也是從初級工程師邁向高級的進階關卡,要想經過此階段,除了必須掌握View的測量、繪製、滑動等基礎知識外,更要掌握View的核心知識點:View的事件分發,本篇就一塊兒從源碼的角度分析View和ViewGroup的事件分發機制;bash

一、View的事件分發

在咱們平時的使用或寫自定義View時,都會直接或間接的使用View的事件分發,View的事件分發主要與View源碼中的3個方法有關:併發

  1. dispatchTouchEvent()
  2. onTouch()
  3. onTouchEvent()

下面咱們針對這三個方法從源碼學習和分析事件的分發,一塊兒從本質上掌握View是如何在層層傳遞和消耗事件;ide

  • 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則事件被消耗,不然事件繼續傳遞;從上面的方法和敘述咱們能夠得出如下結論:源碼分析

  1. 若View設置OnTouchListener,則先調用onTouch(),因此OnTouchListener的優先級高於onTouchEvent()
  2. 若onTouch()返回true,表示onTouch消耗事件,此時onTouchEvent()不會調用
  3. 若onTouch()返回false,此時onTouchEvent()被調用,若onTouchEvent返回true,事件被消耗

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執行邏輯,只粘貼了部分關鍵代碼,所執行邏輯如上面註釋,下面咱們逐步分析如下:post

  1. 首先將mPrivateFlags3設置爲FINGER_DOWN標記
  2. 將mHasPerformedLongPress設置爲false,表示點擊還未觸發長按事件
  3. 建立CheckForTap()實例,並延時發送執行CheckForTap中的run()
  4. 在checkForLongClick中延時發送CheckForLongPress實例,檢測長按事件
  5. 在performLongClick()中代碼會最終調用performLongClickInternal(),performLongClickInternal回調設置的mOnLongClickListener.onLongClick()
  6. 若onLongClick()返回true,則會將mHasPerformedLongPress設置爲true表示觸發長按事件,不然不觸發長按事件
  • ACTION_MOVE
if (!pointInView(x, y, mTouchSlop)) { //判斷手指是否劃出View範圍
    removeTapCallback();  // 移除CheckForTap事件
    removeLongPressCallback();   // 移除長按檢測事件
    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
    }
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}複製代碼

在Action_Move事件中,主要根據手指滑動的座標判斷是否移除View的範圍,若移除則取消和移除CheckForTap事件學習

  • ACTION_UP
if (!clickable) {   // 若是步可點擊移除全部的事件檢測
    removeTapCallback();
    removeLongPressCallback();
    mInContextButtonPress = false;
    mHasPerformedLongPress = false;
    mIgnoreNextUpEvent = false;
    break;
}

if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { //若是已經出發長按事件,且mHasPerformedLongPress設置爲true則不去執行單擊
if (mPerformClick == null) {
    mPerformClick = new PerformClick();  //建立PerformClick檢測單擊事件,最終調用 performClick();
}
if (!post(mPerformClick)) { //發送失敗直接調用performClick()
    performClick();
}
}

public boolean performClick() {
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    li.mOnClickListener.onClick(this);  // 調用onClick方法
    result = true;
} else {
    result = false;
}
}複製代碼

在手指擡起時View執行如下操做:ui

  1. 若是View的clickable爲false,則移除全部的檢測事件
  2. 根據mHasPerformedLongPress的值,設置事件點擊檢測,若mHasPerformedLongPress爲true,代表觸發了長按事件則不用檢測點擊事件
  3. 若mHasPerformedLongPress爲false,建立PerformClick()實例,併發送PerformClick實例,若發送失敗則直接調用performClickInternal()方法
  4. 在PerformClick()實例中的run()直接調用performClickInternal()方法,最終調用performClick()
  • performClick()
public boolean performClick() {
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    li.mOnClickListener.onClick(this);  // 調用onClick方法
    result = true;
} else {
    result = false;
}
}複製代碼

這個方法看起來是否是很面熟,和上面判斷onTouch()的基本一致,首先判斷View是否設置了OnClickListener事件監聽,若設置則調用onClick()方法,此時result返回true表示消耗事件,因此咱們設置的onClick的監聽等級較低,按照事件分發邏輯看,處理咱們觸摸事件的方法按優先級以此爲:onTouch() -> onTouchEvent() -> onClick();this

View的事件傳遞到此就結束了,下面看看比他更復雜的、它的父類ViewGroup的事件分發;spa

二、ViewGroup事件分發

前面分析了View的事件分發,但在實際開發過程當中真正要使用View事件分發時,基本都是由於ViewGroup的嵌套致使的內外滑動問題,因此對ViewGroup的事件分發更須要深刻了解,和View的事件分發同樣,ViewGroup事件分發同樣與幾個方法有關:

  1. dispatchTouchEvent()
  2. onInterceptTouchEvent()
  3. onTouchEvent()

使用一段僞代碼來表述上面三個方法在ViewGroup事件分發中的做用,代碼以下:

public boolean dispatchTouchEvent(MotionEvent event){
  boolean consume = false;
  if(onInterceptTouchEvent(event)){
  consume = onTouchEvent(event);
}else{
  consume = child.dispatchTouchEvent(event);
}
 return consume;
}複製代碼

從上面代碼中看出,事件傳遞到ViewGroup時首先傳遞到dispatchTouchEvent(MotionEvent event)中,而後執行如下邏輯,首先在ViewGroup.dispatchTouchEvent() 中調用onInterceptTouchEvent() 方法:

  1. 返回true,表示攔截事件 -> onTouchEvent() -> 返回true 表示消耗
  2. 返回false,表示不攔截事件 -> child.dispatchTouchEvent(event) 事件向下傳遞,如此反覆傳遞分發

在onInterceptTouchEvent() 返回false時,代表當前ViewGroup不消耗事件,此事件會向下傳遞給子View,此子View多是View也多是ViewGroup,若是是View則按照上面的事件分發消耗事件;

  • ViewGroup.dispatchTouchEvent()

事件的傳遞首先是從手指觸摸屏幕開始,因此咱們先查看dispatchTouchEvent()中的ACTION_DOWN方法,剔除剩餘複雜的邏輯,方法有一段主要的代碼:

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  // 返回true表示子View設置了父容器不攔截事件
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); 
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}複製代碼

上述代碼雖然簡單但ViewGroup的事件分發多半與此處的邏輯有關,裏面的每一個細節都會影響到最終的事件消耗,總結上面代碼執行以下:

  1. 在dispatchTouchEvent()中只有在MotionEvent.ACTION_DOWN 或 mFirstTouchTarget != null,纔會調用onInterceptedTouchEvent()詢問是否攔截
  2. mFirstTouchTarget:指向處理觸摸事件的子View;當ViewGroup子View成功攔截後,mFirstTouchTarget指向子View,此時在整個事件過程當中會不斷詢問ViewGroup的攔截情況;
  3. 若是ViewGroup肯定攔截事件,mFirstTouchTarget爲null,因此整個觸摸事件不會詢問ViewGroup的onInterceptedTouchEvent();

在上述代碼中除了MotionEvent.ACTION_DOWN和mFirstTouchTarget != null條件以外,還有一個會影響到onInterceptedTouchEvent()的調用,就是(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0,這裏主要是用於在子View中設置父容器的攔截條件(多用於滑動衝突),先看如下FLAG_DISALLOW_INTERCEPT這個標識爲:

  1. FLAG_DISALLOW_INTERCEPT:控制事件攔截標記位,在子View中requestDisallowInterceptTouchEvent()中能夠設置標記位

看一下requestDisallowInterceptTouchEvent()方法源碼:

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {    // 狀態相等時無需設定
        return;
    }
    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;     // mGroupFlags = FLAG_DISALLOW_INTERCEPT
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; // mGroupFlags = 0;
    }
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}複製代碼

上面代碼中mGroupFlags初始值爲0,FLAG_DISALLOW_INTERCEPT初始值爲0x80000,在方法中根據參數boolean設置mGroupFlags的值:

  1. 當傳入disallowIntercept爲true時,mGroupFlags = mGroupFlags | FLAG_DISALLOW_INTERCEPT = 0x80000;此時在dispatchTouchEvent()中 知足(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 即disallowIntercept = true,因此intercepted 直接返回false,不攔截事件

  2. 當傳入disallowIntercept爲false時,mGroupFlags = mGroupFlags & ~FLAG_DISALLOW_INTERCEPT = 0;此時在dispatchTouchEvent()中 不知足(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 即disallowIntercept = false,因此回調onInterceptTouchEvent(),父佈局有機會攔截事件

總結一句話就是在requestDisallowInterceptTouchEvent()中設置true,表示不容許父容器攔截事件,設置爲false,表示容許父容器攔截事件;

既然上面全部的條件都在判斷是否須要調用onInterceptTouchEvent(),說明事件最後的攔截取決於onInterceptTouchEvent()方法的返回值,那麼咱們先看一下此方法;

  • onInterceptTouchEvent()默認返回false,表示父容器默認不攔截事件
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;  //默認返回false,即父容器不攔截任何事件
}複製代碼
  • dispatchTouchEvent()向子View的傳遞
if (!canceled && !intercepted) {
for (int i = childrenCount - 1; i >= 0; i--) {  //循環檢測每一個子View
        final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(
                preorderedList, children, childIndex);
          …...
        if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) { //檢測當前座標是否超出View的範圍,若超出跳過此view
            ev.setTargetAccessibilityFocus(false);
            continue;
        }

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //調用dispatchTransformedTouchEvent方法
    …...
    newTouchTarget = addTouchTarget(child, idBitsToAssign); // addTouchTarget中賦值mFirstTouchTarget指向child
    alreadyDispatchedToNewTouchTarget = true;
    break;
 }
}

//dispatchTransformedTouchEvent
if (child == null) {
    handled = super.dispatchTouchEvent(event); // 若是child == null,直接調用super.dispatchTouchEvent,ViewGroup本身處理
} else {
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    event.offsetLocation(offsetX, offsetY);

    handled = child.dispatchTouchEvent(event); // 若是存在child,調用child.dispatchTouchEvent(event)

    event.offsetLocation(-offsetX, -offsetY);
}
}複製代碼

上面代碼爲ViewGroup的dispatchTouchEvent()中的部分代碼,也是控制ViewGroup的事件傳向子View的傳遞,一塊兒來看一下執行邏輯:

  1. 首先判斷事件是否被取消或被ViewGroup攔截即intercepted是否爲false,若被攔截事件已經消耗不須要傳遞
  2. 檢測當前座標是否超出View的範圍,若超出跳過此view
  3. 調用dispatchTransformedTouchEvent()方法,

在dispatchTransformedTouchEvent()中根據子View判斷執行,若是child == null則直接調用super.dispatchTouchEvent,ViewGroup本身處理,若是存在child,調用child.dispatchTouchEvent(event),則事件傳遞到View,接着剛纔的代碼向下看,當dispatchTransformedTouchEvent()返回true時,代碼會執行到addTouchTarget(child, idBitsToAssign)方法:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }複製代碼

在addTouchTarget()方法中將mFirstTouchTarget指向子View,因此上面的判斷mFirstTouchTarget != null在子View攔截事件時成立;

  • onTouchEvent()返回false

到View的onTouchEvent()返回true即表示事件被View消耗,事件的分發也到此結束了,可有沒有考慮過最上層的子View的onTouchEvent()若是不攔截事件呢?最終的事件會去哪呢?答案是要被Activity的onTouchEvent()消耗,咱們知道當一個事件產生時最早獲取的是Activity,而後按照Activity -》Window -》ViewGroup -》View這樣的順序傳遞下去,而在ViewGroup中子View的返回值是在dispatchTransformedTouchEvent()中獲取的,查看代碼:

if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;複製代碼

在dispatchTransformedTouchEvent()中若返回false,程序會執行到如下邏輯:

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

經過上面的學習咱們知道mFirstTouchTarget是指向消耗事件的子View,但當子View不消耗時此時mFirstTouchTarget == null成立,代碼會再次調用dispatchTransformedTouchEvent()方法,此時傳遞的child爲null,經過上面的代碼咱們知道child = null時代碼執行super.dispatchTouchEvent(event),即調用父類的dispatchTouchEvent(event),由於ViewGroup本質上也是繼承View,只不過是包含子View的View,因此事件的傳遞又到了上層View中,在View的dispatchTouchEvent()會詢問onTouch()和onTouchEvent()方法,因此事件又被向上傳遞了;

但若是全部的ViewGroup和子View都不消耗事件,事件會逐層向上傳遞知道事件的開始,也就是Activity層,這時咱們點開Activity的dispatchTouchEvent()方法,

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

從代碼中能夠看出系統調用getWindow().superDispatchTouchEvent(ev)進行事件分發,其實就是向Window和ViewGroup進行事件的傳遞,如有消耗事件的這裏返回true方法結束,若沒有View消耗事件即getWindow().superDispatchTouchEvent(ev)返回false,系統會調用Activity的onTouchEvent()處理事件,因此事件必定會被消耗掉,到此針對View不消耗事件的分析就結束了,咱們也能夠得出如下結論:

  1. 當事件傳遞到View時,若是View的onTouchEvent()返回false,則父類的onTouchEvent()會被調用,依次向上傳遞
  2. 若全部的View都不消耗事件時,Activity的onTouchEvent()會被調用

關於ViewGroup的事件分發的基本知識和源碼分析到這裏就介紹完了,可能直接理解會比較抽象,下面咱們具體的看一下是如何控制和攔截事件的;

三、結論分析

根據上面的View和ViewGroup的事件分發學習,這裏給出幾個View事件傳遞的結論(如下結論針對系統自動分發),並根據學習內容進行逐條分析

  • 正常狀況下一個事件序列只能被一個View攔截或消耗;
  • 對於View一旦決定攔截事件即onTouchEvent()返回true,那後續的整個事件序列都會交給它消耗;
  • 若是View不消耗ACTION_DOWN事件,則後續的事件序列都不會再給他處理
  1. 若是View在ACTION_DOWN時返回false,那系統的mFirstTouchTarget爲null,在後續的MOVE、UP事件中onInterceptTouchEvent()不會再被調用,直接攔截事件

四、ViewGroup、View的事件攔截

事件攔截最經典的使用示例和場景就是滑動衝突,按照View的衝突場景分,滑動衝突能夠分爲3類:

  1. 外部滑動和內部滑動方向不一致
  2. 外部滑動和內部滑動方向一致
  3. 以上兩種狀況嵌套

通常處理滑動衝突有兩種攔截方法:外攔截和內攔截

  • 外部攔截

外攔截顧名思義是在View的外部攔截事件,對View來講外部就是其父類容器,即在父容器中攔截事件,經過上面的代碼咱們知道,ViewGroup的事件攔截取決與onInterceptTouchEvent()的返回值,因此咱們在ViewGroup中重寫onInterceptTouchEvent()方法,在父類須要的時候返回true攔截事件,具體須要的場景要按照本身的業務邏輯判斷:

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        var intercept = false
        when(ev!!.action){
            MotionEvent.ACTION_DOWN ->{intercept = false}
            MotionEvent.ACTION_MOVE->{
                intercept = if (isNeed()){
                    true
                }else{
                    false
                }
            }
            MotionEvent.ACTION_UP->{intercept = false}
        }
        return intercept
    }複製代碼

從上面代碼中看出:在onInterceptTouchEvent()的ACTION_DOWN中必須返回false,即不攔截ACTION_DOWN事件,由於若是ACTION_DOWN一但攔截,事件後面的事件都會默認給ViewGroup處理,也不會再調用onInterceptTouchEvent()詢問攔截,那子View將沒有獲取事件的機會;在ACTION_DOWN中,根據本身須要的時候返回true,那此時事件就會被父ViewGroup消耗

  • 內部攔截

內攔截是在View的內部控制父容器是否攔截事件,你可能已經想到了就是使用上面介紹的requestDisallowInterceptTouchEvent(),答案沒錯就是利用這個方法,關於使用這個方法去控制mGroupFlags的值上面已經介紹了,下面咱們分析下爲什麼設置此數據來控制ViewGroup的事件攔截:

由於事件的攔截是在onInterceptTouchEvent()中肯定的,咱們不可能在子View中控制父容器的方法,但從上面的代碼中看出,ViewGroup訪問onInterceptTouchEvent()以前必須經過一段關卡,就是(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 要成立,而若是此條件不成立,那dispatchTouchEvent()會直接返回false,因此咱們在子View中只要控制這個值就能夠了;

到此雖然能夠控制訪問權限,但如何確保只要在容許訪問的時候就會自動攔截呢?那就是onInterceptTouchEvent()要在特定狀態下一直返回true,即默認想攔截事件 ,綜上所述咱們在子View中要想控制父容器必須知足如下條件:

  1. 事件要能夠傳遞到子View,即父容器不能攔截ACTION_DOWN事件
  2. 子View中要經過requestDisallowInterceptTouchEvent()設置控制onInterceptTouchEvent()訪問的開關
  3. ViewGroup中onInterceptTouchEvent()中要攔截除了ACTION_DOWN事件意外的事件

上面的事件分發,其實和公司安排任務同樣,當一項任務來臨時,公司會開會進行任務安排,你可能作好了承擔一切任務的準備,但大領導不詢問你,整個事件就會按照領導的意見進行安排,忽然在某個任務時大領導問了你願不肯意接,這時你提出了確定的答覆,而後事情就歸你了 ,固然幹好幹很差就是你的問題了,攔截的狀況和這個例子同樣,下面看下攔截的代碼:

//在子View中重寫dispatchTouchEvent()方法控制父類的攔截
@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                y = event.getY();
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float currentY = event.getY();
                int minTouchSlop = 150;
                if (Math.abs(currentY - y) >= minTouchSlop) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }


//在ViewGroup中攔截除ACTION_DOWN之外的事件
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        var intercept = false
        when(ev!!.action){
            MotionEvent.ACTION_DOWN ->{intercept = false}
            else -> {intercept = true}
        }
        return intercept
    }複製代碼

到此View和ViewGroup的事件分發和事件滑動衝突的處理到此介紹完畢了,雖然很早以前就學習過這部分的內容,但並無很好的整理這部份內容,本身寫一遍會對整個只是點更加詳細的理解,相信在開發過程當中不少人都被滑動衝突困擾過,尤爲對初級開發者,那段痛苦是必須通過的,因此只有熟悉View和ViewGroup的事件分發邏輯,才能從根本上解決實際開發中的問題

相關文章
相關標籤/搜索