事件分發(fā)(源碼分析)

老規(guī)矩,看源碼一定要帶著問(wèn)題、推測(cè)或結(jié)論去看,不能看到太深,要能剎得車(chē)~不然會(huì)陷在源碼中。像我這樣的小菜鳥(niǎo),總是在看過(guò)源碼之后才知道Android源碼設(shè)計(jì)的強(qiáng)大,所以在此總結(jié)一下源碼中是如何處理事件分發(fā)的。

注意:本文中所有源碼分析部分均基于 API25 版本,由于安卓系統(tǒng)源碼改變很多,可能與之前版本有所不同,但基本流程都是一致的。
本文將對(duì)ViewGroup的dispatchTouchEvent()做了一個(gè)比較全面的注釋?zhuān)⑿纬闪撕?jiǎn)化后的偽代碼輔助理解。

單個(gè)View分析

首先我們需要先引發(fā)一個(gè)問(wèn)題。需要自定義一個(gè)View,重載構(gòu)造方法,并在Activity中實(shí)現(xiàn)兩個(gè)接口,然后來(lái)看一下他都點(diǎn)擊處理情況.

        button= (WidgetButton) findViewById(R.id.btn_content);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG,"事件");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG,"Touch");
                return false;
            }
        });

當(dāng)我們點(diǎn)擊這個(gè)自定義控件時(shí)

I/widget: Touch
I/widget: Touch
I/widget: Touch
I/widget: 事件

但是如果我們將OnTouchListener中的OnTouch返回true

        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i(TAG,"Touch");
                return true;
            }
        });

點(diǎn)擊控件的結(jié)果是

I/widget: Touch
I/widget: Touch
I/widget: Touch
好了問(wèn)題出現(xiàn)

為什么setOnTouchListener中的onTouch返回true后OnClick就不執(zhí)行了呢?我們點(diǎn)進(jìn)去看setOnTouchListener和setOnClickListener做了什么?

    public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

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

這里發(fā)現(xiàn)將監(jiān)聽(tīng)賦給了mOnTouchListener和mOnClickListener,我們暫時(shí)記住這兩個(gè)變量名。
我們要知道當(dāng)我們點(diǎn)擊自定義View控件時(shí),首先會(huì)調(diào)用父類(lèi)(View)的dispatchTouchEvent方法。然后看一下這里邊幾句重要的代碼。

9999    boolean result = false;
...
10017  if (li != null && li.mOnTouchListener != null
10018      && (mViewFlags & ENABLED_MASK) == ENABLED
10019      && li.mOnTouchListener.onTouch(this, event)) {
10020     result = true;
10021   }
  
10023  if (!result && onTouchEvent(event)) {
            result = true;
10025   }

這里我們可以看到之前記住的那個(gè)變量名mOnTouchListener,這里判斷如果它的onTouch返回true的時(shí)候result賦值為ture。(在源碼中如果有些變量不太明白干什么的,千萬(wàn)不要糾結(jié),這里判斷的這幾個(gè)條件都會(huì)滿(mǎn)足)繼續(xù)往下看,如過(guò)result為false,因?yàn)橛眠@里用的是&&所以在第一個(gè)條件未滿(mǎn)足的情況下是不會(huì)調(diào)用第二的條件的,但是重點(diǎn)就在這個(gè)第二個(gè)條件中(onTouchEvent方法)。

11185    case MotionEvent.ACTION_UP:
...
11216      performClick();

在performClick方法的5637行會(huì)調(diào)用 li.mOnClickListener.onClick(this); 是不是之前賦值的那個(gè)變量名。所以這里可以知道,當(dāng)mOnTouchListener.onTouch()為true時(shí)就不會(huì)調(diào)用onTouchEvent()方法,但是mOnClickListener.onClick()在onTouchEvent() - up事件 - performClick()方法中,所以也不會(huì)調(diào)用。

Activity - ViewGroup 分析

為什么要從Activity說(shuō)起呢。事件收集之后最先傳遞給 Activity, 然后依次向下傳遞。
首先自定義ViewGroup,我這里繼承的是RelativeLayout因?yàn)樗彩抢^承自ViewGroup并且沒(méi)有對(duì)觸摸事件進(jìn)行處理,然后重載構(gòu)造方法,并重寫(xiě)三個(gè)與觸摸事件有關(guān)的方法

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG,"  WidgetViewGroup    onTouchEvent   "+event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG,"  WidgetViewGroup    onInterceptTouchEvent  "+ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG,"  WidgetViewGroup    dispatchTouchEvent"+  ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

在MainActivity中重寫(xiě)兩個(gè)與觸摸事件有關(guān)的方法,為什么是兩個(gè)呢,因?yàn)樵贏ctivity中沒(méi)有Intercept事件,因?yàn)闆](méi)有意義,如果攔截了會(huì)導(dǎo)致點(diǎn)擊什么效果都沒(méi)有。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG,"   MainActivity    dispatchTouchEvent   "+  ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG,"   MainActivity    onTouchEvent   "+  event.getAction());
        return super.onTouchEvent(event);
    }

點(diǎn)擊屏幕時(shí)輸出如下 這里0表示Down 2表示Move 1表示Up

I/widget:    MainActivity    dispatchTouchEvent   0
I/widget:   WidgetViewGroup    dispatchTouchEvent0
I/widget:   WidgetViewGroup    onInterceptTouchEvent  0
I/widget:   WidgetViewGroup    onTouchEvent   0
I/widget:    MainActivity    onTouchEvent   0
I/widget:    MainActivity    dispatchTouchEvent   2
I/widget:    MainActivity    onTouchEvent   2
I/widget:    MainActivity    dispatchTouchEvent   1
I/widget:    MainActivity    onTouchEvent   1

這樣就知道了觸摸事件之間執(zhí)行順序的情況。

好了問(wèn)題出現(xiàn)

為什么會(huì)這樣調(diào)用呢?那么先從Activity的dispatchTouchEvent看起。

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//這個(gè)方法是個(gè)空方法,是給用戶(hù)進(jìn)行重寫(xiě)的
        }
        //這里說(shuō)明如果這個(gè)判斷返回true就不執(zhí)行Activity的onTouchEvent()方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

這里有簡(jiǎn)單的注釋?zhuān)桥袛嘀械姆椒ㄊ鞘裁茨??進(jìn)去看一下。

    public abstract boolean superDispatchTouchEvent(MotionEvent event);

可以發(fā)現(xiàn)他是Window抽象類(lèi)的一個(gè)抽象方法,在文件開(kāi)始的注釋中說(shuō)明了Window抽象類(lèi)僅有一個(gè)實(shí)現(xiàn)類(lèi)PhoneWindow,那么就得去PhoneWidow找這個(gè)方法的實(shí)現(xiàn)。
PhoneWindow

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

這里調(diào)用的DecorView的方法

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

這里調(diào)用的父類(lèi)的方法,但是父類(lèi)FrameLayout并沒(méi)有這個(gè)方法,只好再去FrameLayout的類(lèi)ViewGroup中去找。最終在ViewGroup中找到了相應(yīng)的方法(dispatchTouchEvent),并做了很多操作,那么我們看一下做了什么操作。2145行開(kāi)始

        //判斷是否有觸摸設(shè)備 比如觸摸筆一類(lèi)的
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        //這是一個(gè)賦值功能 AccessibilityService 可以不用手指進(jìn)行點(diǎn)擊   比如搶紅包
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {//事件安全檢查
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
          /**        
            * 第一步:對(duì)于ACTION_DOWN進(jìn)行處理(Handle an initial down)
            * 因?yàn)锳CTION_DOWN是一系列事件的開(kāi)端,當(dāng)是ACTION_DOWN時(shí)進(jìn)行一些初始化操作.
            * 從源碼的注釋也可以看出來(lái):清除以往的Touch狀態(tài)(state)開(kāi)始新的手勢(shì)(gesture)
            * cancelAndClearTouchTargets(ev)中有一個(gè)非常重要的操作:
            * 將mFirstTouchTarget設(shè)置為null!!!!
            * 隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識(shí)
            * */
            // Handle an initial down.
            //Down為事件的開(kāi)始,所以這里判斷是不是事件的開(kāi)始
            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.
                //這個(gè)方法用于清理標(biāo)志,里邊有一個(gè)TouchTarget判讀是否是第一次觸發(fā)Down,如果是什么都不處理,如果不是將一些標(biāo)志全部置為初始化
                cancelAndClearTouchTargets(ev);
                //重置所有接觸狀態(tài),準(zhǔn)備一個(gè)新的觸摸循環(huán)
                resetTouchState();
            }
            /**
             * 第二步:檢查是否要攔截(Check for interception)
             * 在dispatchTouchEvent(MotionEventev)這段代碼中
             * 使用變量intercepted來(lái)標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
             * 該變量在后續(xù)代碼中起著很重要的作用.
             *
             * 攔截    intercepted =true
             */
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {//正因?yàn)檫@個(gè)||所以在move事件是也能夠攔截
                 //這里的 (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0是否為true取決于 requestDisallowInterceptTouchEvent(?)
                 // 這個(gè)方法是用于是否允許父類(lèi)進(jìn)行攔截 true 為不讓父控件攔截。這里個(gè)人理解為判斷子View是否有調(diào)用這個(gè)方法。
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //這里的攔截并沒(méi)有返回 只是將intercepted 設(shè)為 實(shí)現(xiàn)方法中返回的值 標(biā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;
            }

            /**
             * 第三步:檢查cancel(Check for cancelation)
             *
             */
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;

            /**
             * 第四步:事件分發(fā)(Update list of touch targets for pointer down, if needed)
             */
            //不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為false(不攔截)

            //intercepted  未被攔截  如果攔截了會(huì)跳過(guò)這里邊的方法
            if (!canceled && !intercepted) {

                //之前說(shuō)的那個(gè)賦值功能
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //判斷是否是Down事件   還有一個(gè) 多手指的判斷
                //第二次Move事件時(shí)不會(huì)執(zhí)行這里的代碼 所以不會(huì)遍歷子控件,由于move事件頻繁調(diào)用 這是對(duì)move事件的一個(gè)優(yōu)化
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    // 移除所有的多手指操作
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //如果當(dāng)前含有子控件
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        //重排序  按找Z來(lái)排序
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        //是否是用戶(hù)自己排序
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //事件分發(fā)   倒序 遍歷 重排序后的集合   后添加的先接收
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            //拿到第i個(gè)View  相當(dāng)于 preorderedList.get(childIndex )
                            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;
                            }
                            //這里判斷這個(gè)View能不能夠接收事件
                            //clickable   Invisiable   點(diǎn)擊事件不在  view范圍內(nèi)(通過(guò)pointInView)  正在動(dòng)畫(huà)
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                             /**
                             *  執(zhí)行到了下面
                             *  child  絕對(duì)會(huì)接受到事件
                             */
                            //如果只分析Down操作 這里返回空  如果是move它就不為null
                            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.
                                //如果能夠找到接收事件的target直接break
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            /**
                             * 真正做事件分發(fā)
                             * child  不為空
                             *
                             *
                             * 如果子類(lèi)   onTouch  返回true  (根據(jù)子類(lèi)onTouchEvent 來(lái)返回)
                             */
                            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();
                                 //將child添加到Target  并賦給mFirstTouchTarget 做為將要接收move事件的view
                                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);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            
            // Dispatch to touch targets.
            //如果該ViewGroup攔截事件為true 那么mFirstTouchTarget 為null (會(huì)跳過(guò)上面的方法直接到這)
            //如果沒(méi)有攔截并且點(diǎn)擊鎖定子view那么mFilrstTouchTarget不為null
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
               /**
                 * 真正做事件分發(fā)
                 * child  為空
                 */
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //如果第三個(gè)參數(shù)為null將會(huì)調(diào)用ViewGroup的onTouchEvent(),如果不為null將不會(huì)調(diào)用ViewGroup的onTouchEvent()調(diào)用的是子View的onTouchEvent()
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;

這就是整個(gè)dispatchTouchEvent()的代碼,重要的都做了注釋。還有一部分重要的代碼是

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        ...
        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);
        }
}

之前一直提到的真正的事件分布,傳view和不傳view 的情況,如過(guò)view為Null調(diào)用ViewGroup的super.dispatchTouchEvent(),相當(dāng)于調(diào)用ViewGroup的onTouchEvent(),如果不為null這調(diào)用子view的dispatchTouchEvent()。
經(jīng)過(guò)源碼的分析,可以簡(jiǎn)化一個(gè)偽源碼,如下:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;

        if (!onInterceptTouchEvent(ev)) {//如果沒(méi)有攔截
              //之前會(huì)判斷是否存在這個(gè) 符合要求的child
              //這個(gè)返回結(jié)果相當(dāng)于子View的onTouchEvent的返回結(jié)果
              if(child.dispatchTouchEvent(transformedEvent)){
                      //target是鎖定事件的那個(gè)view之后的move事件就發(fā)生在它身上    mFirstTouchTarget 默認(rèn)為null
                      mFirstTouchTarget = target;
              }
        }
        //這個(gè)判斷相當(dāng)于沒(méi)有攔截或不存在符合條件的子view
        if(mFirstTouchTarget == null){
              handled = onTouchEvent();//調(diào)用自身的onTouchEvent();
        }else{
              handled = true;
        }

        return handled;
    }

通過(guò)源碼可以直接的了解出 Activity - ViewGroup - View 的一個(gè)事件傳遞情況。這樣也就解釋了上面的問(wèn)題(個(gè)人理解,如果是ViewGroup - ViewGroup 將會(huì)是一個(gè)遞歸的情況)

總結(jié)

  1. 事件分發(fā)中用到了責(zé)任鏈模式,上層View可以攔截事件自己處理,也可以發(fā)布給子View,如果子View處理不了還可以返回到上層View進(jìn)行處理,既保證了事件的有序性,又非常的靈活。
  2. View 的 dispatchTouchEvent 主要用于調(diào)度自身的監(jiān)聽(tīng)器和 onTouchEvent。
  3. View的事件的調(diào)度順序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
  4. 不論 View 自身是否注冊(cè)點(diǎn)擊事件,只要 View 是可點(diǎn)擊的就會(huì)消費(fèi)事件。
  5. 事件是否被消費(fèi)由返回值決定,true 表示消費(fèi),false 表示不消費(fèi),與是否使用了事件無(wú)關(guān)。
  6. ViewGroup 中可能有多個(gè) ChildView 時(shí),將事件分配給包含點(diǎn)擊位置的 ChildView。
  7. 只要接受 ACTION_DOWN 就意味著接受所有的事件,拒絕 ACTION_DOWN 則不會(huì)收到后續(xù)內(nèi)容。
  8. (源碼理解)ViewGroup的dispatchTouchEvent 方法處理了所有觸摸操作,onInterceptTouchEvent和onTouchEvent這兩個(gè)方法并沒(méi)有返回結(jié)果,只是返回true、false告訴dispatchTouchEvent應(yīng)該做啥。

這篇文章是在我學(xué)習(xí)的基礎(chǔ)上進(jìn)行了總結(jié),可想而知我還是個(gè)很小的菜鳥(niǎo),如果其中有錯(cuò)誤還請(qǐng)指出,我會(huì)盡快修改文章,并改正自己的理解,謝謝。

最后推薦大神作品

安卓自定義View教程目錄

最后編輯于
?著作權(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)容