View的事件分發(fā)機(jī)制

什么是事件分發(fā)。當(dāng)我們點(diǎn)擊屏幕時(shí),就產(chǎn)生了點(diǎn)擊事件,這個(gè)事件被封裝成了一個(gè)類(lèi):MotionEvent。而當(dāng)這個(gè) MotionEvent產(chǎn)生后,那么系統(tǒng)就會(huì)將這個(gè)MotionEvent傳遞給View的層級(jí),MotionEvent在View中的層級(jí)傳遞過(guò)程就是點(diǎn)擊事件分發(fā)。

1.MotionEvent

MotionEvent類(lèi)就是記錄手指接觸屏幕后所產(chǎn)生的一系列的事件,下面看幾個(gè)常用的事件的類(lèi)型與含義:

事件 含義
MotionEvent.ACTION_DOWN 手指按下時(shí)觸發(fā)
MotionEvent.ACTION_UP 手指抬起時(shí)觸發(fā)
MotionEvent.ACTION_MOVE 手指移動(dòng)時(shí)觸發(fā)
MotionEvent.ACTION_CANCEL 事件被攔截時(shí)觸發(fā)
MotionEvent.ACTION_OUTSIDE 手指不在控件區(qū)域時(shí)觸發(fā)
  1. 點(diǎn)擊事件: ACTION_DOWN-->ACTION_UP
  2. 滑動(dòng)事件:ACTION_DOWN-->ACTION_MOVE--> ACTION_UP

2.點(diǎn)擊事件分發(fā)的傳遞規(guī)則

點(diǎn)擊事件是由三個(gè)很重要的方法來(lái)實(shí)現(xiàn)的,分別是dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent event)。

  • dispatchTouchEvent(MotionEvent ev)
    用來(lái)進(jìn)行事件的分發(fā)。如果事件能夠傳遞給當(dāng)前View,那么此方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent和下級(jí)View的dispatchTouchEvent方法的影響,表示是否消耗當(dāng)前事件。
  • onInterceptTouchEvent(MotionEvent ev)
    在上述方法內(nèi)部調(diào)用,用來(lái)判斷是否攔截某個(gè)事件,如果當(dāng)前View攔截了某個(gè)事件,那么在同一個(gè)事件序列中,此方法不會(huì)被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。
  • onTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法中調(diào)用,用來(lái)處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前View無(wú)法再次接收到事件。
    這三個(gè)方法就是事件分發(fā)機(jī)制中的核心三個(gè)方法,也是我們下面在源碼中重要去分析的三個(gè)方法。他們?nèi)咧g的關(guān)系可以概述如下 (注意這是一段偽代碼,只是為了對(duì)解釋三個(gè)方法關(guān)系)
    //點(diǎn)擊事件產(chǎn)生后
    // 步驟1:調(diào)用dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false; //代表 是否會(huì)消費(fèi)事件
        // 步驟2:判斷是否攔截事件
        if (onInterceptTouchEvent(ev)) {
            // a. 若攔截,則將該事件交給當(dāng)前View進(jìn)行處理
            // 即調(diào)用onTouchEvent ()方法去處理點(diǎn)擊事件
            consume = onTouchEvent(ev);
        } else {
            // b. 若不攔截,則將該事件傳遞到下層
            // 即 下層元素的dispatchTouchEvent()就會(huì)被調(diào)用,重復(fù)上述過(guò)程
            // 直到點(diǎn)擊事件被最終處理為止
            consume = child.dispatchTouchEvent(ev);
        }
        // 步驟3:最終返回通知 該事件是否被消費(fèi)(接收 & 處理)
        return consume;
    }

根據(jù)上面這段偽代碼能夠很好的理解三者的關(guān)系。當(dāng)我們點(diǎn)擊事件產(chǎn)生后,它的傳遞過(guò)程如下順序:Activity->ViewGroup->View。即事件總是先傳遞給Activity,Activity傳遞給ViewGroup, 再由ViewGroup傳遞給View。頂級(jí)View接收到事件后,就會(huì)按照事件分發(fā)機(jī)制去分發(fā)事件。
對(duì)于一個(gè)根ViewGroup,點(diǎn)擊事件產(chǎn)生后,首先會(huì)傳遞給它,這時(shí)會(huì)調(diào)用它的dispatchTouchEvent方法,如果這個(gè)ViewGroup的onInterceptTouchEvent返回true,就表示要攔截當(dāng)前事件,接著事件就會(huì)交給這個(gè)ViewGroup來(lái)處理,即它的onTouchEvent方法被調(diào)用;返回false,就表示當(dāng)前事件不需要被攔截,當(dāng)前時(shí)間就會(huì)被繼續(xù)傳遞給它的子元素,接著會(huì)調(diào)用子元素的dispatchTouchEvent方法,如此返回直到事件最終被處理。

為什么Activity向下分發(fā)第一個(gè)就是ViewGroup,如果我們布局中只有一個(gè)簡(jiǎn)單View控件(如TextView)呢?因?yàn)槲覀儾季旨虞d中的頂級(jí)View是DecorView(繼承FrameLayout),他本是就是一個(gè)ViewGroup。

3.源碼分析

上面我們分析了View的事件分發(fā)機(jī)制,下面我們從源碼的角度去學(xué)習(xí)分析。

3.1 Activity對(duì)點(diǎn)擊事件的分發(fā)過(guò)程

點(diǎn)擊事件使用MotionEvent來(lái)表示,當(dāng)點(diǎn)擊事件時(shí),事件最先傳遞給當(dāng)前的Activity,由Activity的dispatchTouchEvent來(lái)進(jìn)行事件派發(fā),具體工作是由Activity內(nèi)部的Window來(lái)完成的。Window會(huì)將事件傳遞給DecorView,DecorView一般就是當(dāng)前界面的底層容器(即setContentView 所設(shè)置的View的父容器),通過(guò)Activity.getWindow.getDecorView()可以獲得。
關(guān)于activity的構(gòu)成學(xué)習(xí)可以參考
劉望舒 Android View體系(六)從源碼解析Activity的構(gòu)成
下面看下Activity的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 一般事件列開(kāi)始都是DOWN事件 = 按下事件,故此處基本是true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //當(dāng)此activity在棧頂時(shí),觸屏點(diǎn)擊按home,back,menu鍵等都會(huì)觸發(fā)此方法
        onUserInteraction();
    }
    //注釋1
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //當(dāng)一個(gè)點(diǎn)擊事件未被Activity下任何一個(gè)View接收或處理
    //或是發(fā)生在Window邊界外的觸摸事件就會(huì)調(diào)用
    //注釋2
    return onTouchEvent(ev);
}

分析上面的代碼,從注釋1處可以看出事件開(kāi)始交給Activity的Window進(jìn)行分發(fā)的。返回true,整個(gè)事件循環(huán)就結(jié)束了,返回false意味著沒(méi)人處理,即調(diào)用Activity的onTouchEvent方法。
從注釋1處點(diǎn)擊去就是Window#superDispatchTouchEvent(),這是一個(gè)抽象方法。我們得找到它的實(shí)現(xiàn)類(lèi)。

  //Window#superDispatchTouchEvent()
  public abstract boolean superDispatchTouchEvent(MotionEvent event);

而Window的實(shí)現(xiàn)類(lèi)也就是PhoneWindow,我們看下PhoneWindow的superDispatchTouchEvent方法,其源碼如下所示:

  // com.android.internal.policy.PhoneWindow 
 //PhoneWindow#superDispatchTouchEvent()
 @Override
 public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
 }

mDecor是PhoneWindow中一個(gè)DecorView類(lèi)型的變量,DecorView代表了當(dāng)前Window最頂級(jí)的View,可以看做是根View。由上代碼看出,后面會(huì)執(zhí)行DecorView的superDispatchTouchEvent方法,其源碼如下所示:

  //DecorView#superDispatchTouchEvent()
 public boolean superDispatchTouchEvent(MotionEvent event) {
    //DecorView繼承FrameLayout 那么他本是就是一個(gè)ViewGroup
    //那么這個(gè)方法最后就會(huì)調(diào)用到ViewGroup#dispatchTouchEvent()
    return super.dispatchTouchEvent(event);
}

可以看到最后Activity的分發(fā)過(guò)程最后就是將事件交給頂級(jí)DecorView(即ViewGroup)去進(jìn)行事件分發(fā)。然后它又會(huì)調(diào)用ViewGroup#dispatchTouchEvent()。到這里我們就將我們的事件由Activity->ViewGroup的傳遞。并將返回值設(shè)置成true。表示這個(gè)事件已經(jīng)被我們消耗掉了。從注釋2中我們可以看出只有Window沒(méi)處理觸摸事件的情況下,Activity才會(huì)調(diào)用onTouchEvent方法去處理事件:

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }

只有當(dāng)觸摸事件沒(méi)有被任何的View或ViewGroup處理過(guò)的時(shí)候,Activity才會(huì)執(zhí)行自己的onTouchEvent去處理觸摸事件。一種典型的情形就是,當(dāng)前觸摸點(diǎn)在Window范圍之外,這樣Window里面所有的View都不會(huì)接收更不會(huì)處理該觸摸事件,這時(shí)候我們可以重寫(xiě)該方法實(shí)現(xiàn)一些自己的邏輯處理這種情形。如果我們處理了,就返回true,否則返回false。其默認(rèn)一直返回false。

3.2 ViewGroup對(duì)點(diǎn)擊事件的發(fā)分過(guò)程

通過(guò)上面的分析,當(dāng)Activity接收到觸摸事件之后,會(huì)通過(guò)DectorView調(diào)用ViewGroup的dispatchTouchEvent方法。那么我們就分析ViewGroup#dispatchTouchEvent()方法:

// 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();
}

// Check for interception.
//檢查是否事件攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        //調(diào)用事件攔截方法
        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;
}  

從上面我們可以看出,事件在開(kāi)始的時(shí)候會(huì)調(diào)用resetTouchState方法清空mFirstTouchTarget 。 ViewGroup在兩種情況下會(huì)判斷是否要攔截當(dāng)前事件:事件類(lèi)型為ACTION_DOWN或者mFirstTouchTarget != null。ACTION_DOWN是按下,mFirstTouchTarget 用于保存當(dāng)前ViewGroup中處理了觸摸事件的子View。

下面看下ViewGroup不攔截事件,將事件向下分發(fā),傳遞給子View進(jìn)行處理

//對(duì)子元素進(jìn)行遍歷
for(int i = childrenCount - 1;i >=0;i--){
    //子view是否在做動(dòng)畫(huà)
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
    //事件坐標(biāo)是否在子元素的區(qū)域內(nèi)
    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.
    //判斷是否接受點(diǎn)擊事件
    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);
    //如果有子元素,將進(jìn)行事件分發(fā)
    //注釋1
    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;
    }

    // The accessibility focus didn't handle the event, so clear
    // the flag and do a normal dispatch to all children.
    ev.setTargetAccessibilityFocus(false);
}

ViewGroup遍歷其子View,通過(guò)子元素是否在播放動(dòng)畫(huà)和點(diǎn)擊事件的坐標(biāo)是否在子元素的區(qū)域內(nèi),來(lái)判斷子元素是否能夠接收到點(diǎn)擊事件。對(duì)于符合點(diǎn)擊事件的子View并調(diào)用dispatchTransformedTouchEvent()進(jìn)行事件分發(fā)。當(dāng)子元素處理點(diǎn)點(diǎn)擊事件就會(huì)調(diào)用addTouchTarget方法對(duì)mFirstTouchTarget進(jìn)行賦值,alreadyDispatchedToNewTouchTarget 設(shè)置為true,表示只要有子View處理了觸摸事件,就表示當(dāng)前的ViewGroup也處理了觸摸事件,并且這種情況下ViewGroup不會(huì)調(diào)用從View中繼承來(lái)的dispatchTouchEvent方法,從而不會(huì)觸發(fā)ViewGroup的onTouchEvent方法的執(zhí)行。
下面看下如何對(duì)mFirstTouchTarget進(jìn)行賦值:

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

我們可以看到mFirstTouchTarget的賦值是在addTouchTarget方法中進(jìn)行的,mFirstTouchTarget是否被賦值,將影響到ViewGroup對(duì)事件的攔截。

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

如果mFirstTouchTarget ==null 即表示ViewGroup沒(méi)有子元素,或者是子元素處理了點(diǎn)擊事件,下一步將會(huì)把點(diǎn)擊事件交給View來(lái)處理。
我們從上面的注釋1和注釋2處可以看到,都調(diào)用了dispatchTransformedTouchEvent方法,而第三個(gè)參數(shù)中 注釋2傳遞的是一個(gè)null。下面看下源碼:

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        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;
        }
      ......
   }

可以可看到,如果ViewGroup有子元素同時(shí)子元素可以處理點(diǎn)擊事件。那么就會(huì)調(diào)用子元素的child.dispatchTouchEvent方法。如果是child是ViewGroup繼續(xù)上面的循環(huán),如果子元素是View,那么就會(huì)調(diào)用View.dispatchTouchEvent方法。關(guān)于這個(gè)方法我們后面分析。如果child為空,就會(huì)調(diào)用super.dispatchTouchEvent方法,那么就會(huì)調(diào)用ViewGroup的父類(lèi),即View.dispatchTouchEvent方法,ViewGroup自己處理點(diǎn)擊事件。最后都會(huì)默認(rèn)調(diào)用onTounchEvent方法。

3.3 View對(duì)點(diǎn)擊事件的處理過(guò)程

view對(duì)點(diǎn)擊事件處理過(guò)程稍微簡(jiǎn)單些。這里的view是不包含ViewGroup。下面看下dispatchTouchEvent方法:

    public boolean dispatchTouchEvent(MotionEvent event) {  
        ......

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //如果設(shè)置了OnTouchListener,那么會(huì)在此處執(zhí)行OnTouchListener的onTouch方法
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //觸摸事件沒(méi)有被OnTouchListener處理,那么就會(huì)執(zhí)行View的onTouchEvent方法
            if (!result && onTouchEvent(event)) {
                //如果onTouchEvent返回了true,就表示觸摸事件被View處理了,result就被設(shè)置為了true
                result = true;
            }
        }
        ......
        return result;
    }

從上面可以看出,首先會(huì)判斷有沒(méi)有設(shè)置onTouchListener,如果onTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不會(huì)被執(zhí)行,由此可見(jiàn)onTouchListener的優(yōu)先級(jí)是高于onTouchEvent,這樣做的好處是,方便在外界處理點(diǎn)擊事件。
下面看下onTouchEvent方法,當(dāng)view 是不可用的時(shí)候點(diǎn)擊事件的處理過(guò)程,在不可用的狀態(tài)下,View照樣會(huì)消耗事件,只不過(guò)不會(huì)觸發(fā)onClick方法。

       final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        //首先判斷當(dāng)前View是不是DISABLED不可用狀態(tài)
        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有代理會(huì)執(zhí)行這個(gè)方法
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

當(dāng)然如果設(shè)置有代理,還會(huì)執(zhí)行TouchDelegate的onTouchEvent方法,此處onTouchEvent的工作機(jī)制和onTouchListener的類(lèi)似。
onTouchEvent對(duì)點(diǎn)擊事件的具體處理如下:

        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)) {
                                    performClick();
                                }
                            }
                        }

                        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;

                case MotionEvent.ACTION_DOWN:
                    ......
                    break;
                case MotionEvent.ACTION_CANCEL:
                   ......
                    break;
                case MotionEvent.ACTION_MOVE:
                   ......
                    break;
            }
            return true;
        }

如果view是可以點(diǎn)擊的,就會(huì)進(jìn)入switch語(yǔ)句中,當(dāng)ACTION_UP事件觸發(fā),就會(huì)執(zhí)行performClick方法。

    public boolean performClick() {
        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;
    }

如果我們?cè)O(shè)置了點(diǎn)擊事件就出執(zhí)行OnClickListener的onClick方法。點(diǎn)擊信息封裝在ListenerInfo中,當(dāng)我們?yōu)関iew設(shè)置點(diǎn)擊事件的時(shí)候就會(huì)調(diào)用,可以看下setOnClickListener方法和setOnLongClickListener方法的源碼:

    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;
    }

當(dāng)我們?cè)O(shè)置點(diǎn)擊事件的時(shí)候,會(huì)通過(guò)setClickable和setLongClickable方法將view設(shè)置成可點(diǎn)擊的。

本文學(xué)習(xí)到此,如果文章哪里有錯(cuò)誤,歡迎指出,謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容