View觸摸事件源碼分析


ViewdispatchTouchEvent方法的省略版源碼

public boolean dispatchTouchEvent(MotionEvent event) {
    //dispatchTouchEvent的返回值
    boolean result = false;
    //...balabala省略前面部分代碼
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;

//判斷是否有onTouchListener監聽,如果有,執行listener

//onTouch方法,
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                
&& li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
//如果onTouch方法返回false,繼續執行onTouchEvent方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    return result;
}

通過上面的代碼可以得出以下結論:

1 如果ViewonTouchListener,則onTouch方法優於onTouchEvent方法先執行

2 如果 ViewonTouchListeneronTouch方法返回了true,onTouchEvent方法不會執行

3 如果ViewonTouchListener,並且onTouch方法返回了true,dispatchTouchEvent方法返回true。如果onTouch返回了false或者沒有onToucheListener,dispatchTouchEvent方法和onTouchEvent的返回值一致。

ViewGroup dispatchTouchEvent方法的省略版源碼

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    //dispatchTouchEvent方法的返回值
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        // Check for interception.是否攔截標誌
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                
|| mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {

//調用攔截方法,該方法默認返回false
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was chang
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
        //如果沒有攔截
        if (!intercepted) {
                       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 (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                     }

                    }
                                   }

              }
        }
    
       return handled;
}

dispatchTransformedTouchEvent方法

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 */
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();
   
    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        
    // Perform any necessary transformations and dispatch.
    if (child == null) {

//調用ViewdispatchTouchEvent方法,ViewdispatchTouchEvent方法如果執行則看前面分析ViewdispatchTouchEvent方法源碼
        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());
        }
//調用子viewdispatchTouchEvent方法
        handled = child.dispatchTouchEvent(transformedEvent);
    }

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

ViewGroupdispatchTouchEvent方法比較複雜,主要是它是個控件容器,可能有很多子控件,要考慮事件如何傳遞,傳遞給哪個子控件,該不該傳遞給子控件等等多種情況。

結論:

1 ViewGroup裏面新增了一個攔截方法onInterceptTouchEvent,控制該事件要不要傳遞給它的子控件。

2 onInterceptTouchEvent方法返回true,則子控件接收不到事件即dispatchTouchEvent方法不會調用,而會調用父類View dispatchTouchEvent方法。若返回false,則會子控件的dispatchTouchEvent方法

現測試檢驗查看

 

MyView繼承View,裏面添加onTouchListener監聽.MyViewGroup 繼承ViewGroup.在相應的方法裏面添加打印語句,返回值全都默認值,經上面分析可知道的,猜想得知執行順序MyViewGroup.dispatchTouchEvent->MyViewGroup.onInterceptTouchEvent->MyView.dispatchTouchEvent->MyView.onTouch->MyView.onTouchEvent

打印結果如下圖,符合猜想;


1 小結:

ViewViewGroupdispatchTouchEvent默認返回都爲false;

ViewGroup默認onInterceptTouchEvent返回也爲false;

action0表示爲當前事件爲ACTION_DOWN事件;

 

but,MOVEUP事件跑哪去了....慢慢往下看;

 

其他都是默認值 現在改變View的監聽onTouch返回值爲true;

這時候ViewonTouchEvent應該就得不到執行了;

打印結果如下圖,發現打印日誌就多了很多,action2表示ACTION_MOVE事件,等於1表示ACTION_UP事件;


2小結:

經上面分享源碼得知,此時MyViewdispatchTouchEvent方法返回的也是true,由日誌也可看出;

由打印日誌可以看出,MyViewGroupdispatchTouchEvent方法也返回了true;

可以看出MyViewGroupdispatchTouchEvent方法返回了true,纔有了後續的ACTION_MOVEACTION_UP事件;

注意看,此時MyViewGrouponTouchEvent方法也不會執行了....Why...繼續往下慢慢分析

 

 

其他都是默認值 改變ViewdispatchTouchEvent方法返回true;

 

這個和上面那個日誌的區別就是會執行ViewonTouchEvent方法,其他的都是一樣的;

打印如下;

 

其他都是默認值,ViewonTouchEvent返回true;

 

日誌和上面的差不多,差別就是前面ViewonTouchEvent返回false,這裏都是true;

沒有設置dispatchTouchEvent的返回值,但是它返回的是true;

前面分析過了,這種情況dispatchTouchEvent的返回值和onTouchEvent是一樣的;

依然ViewGrouponTouchEvent方法沒有執行;

 

 

3小結

通過小結1 和後面的對比發現,如果ViewdispatchTouchEvent返回值爲true,ViewGrouponTouchEvent就不會執行了;

而想要ViewdispatchTouchEvent返回值爲true,除了複寫該方法在dispatchTouchEvent裏面返回true之外,在則可以設置一個監聽onTouchListener返回true,或者在onTouchEvent裏面返回true;

平常我們用的最多的是複寫onTouchEvent方法;

再看View onTouchEvent方法源碼;

public boolean onTouchEvent(MotionEvent event) {
        //前面省略balabala代碼....

final int action = event.getAction();
//還可以設置touch代理
            if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) !=
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {         
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }

//調用onClickListener回調
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                       break;
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                if (isInScrollingContainer) {
                  //檢測是否長按
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);

//檢測是否長按
                    checkForLongClick(0, x, y);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        setPressed(false);
                    }
                }
                break;
        }
//這裏直接返回true
        return true;
    }

    return false;
}

初看一下既然返回false了,那麼switch裏面應該就不會走了;

突發奇想,既然switch都不會走了,難道onClickListener也不會走了???

MyView MyViewGroup所有返回值都默認,添加如下代碼,設置了onClick監聽

 

結果Toast神奇的彈出來了!!!

和上面預期的不一樣啊,既然switch都不走了,那爲啥onClick方法會被調用?

打印如下;

 

View onTouchEvent,dispatchTouchEvent返回值爲true;

ViewGroupdispatchTouchEvent返回爲true了。

 

就是一個加setOnclickListener和不加的區別;

點進去這個方法看;

前面有個setClickable頓時恍然大悟,如夢出醒;

再看onTouchEvent方法

 

也就是默認這個值是false,如果這個值是false,onTouchEvent方法也就直接返回false.這也是導致了dispatchTouchEvent方法返回了false;

google工程師用一個標誌viewFlags判斷當前是否可點擊或者長按或者是contextClickable;

點擊和長按是在onTouchEvent裏面處理的,這個onContextClickListeneronTouchEvent裏面沒看到被調用,用的也比較少,暫時略過,以後發現了用處再來補充

4 小結

在不設置setClickable,或者setLongClickable情況下,onTouchEvent返回false;

若設置setClickable,或者setLongClickabletrue,則onTouchEvent也返回true;

可以通過代碼設置,也可以在佈局文件裏面直接設置ClickableLongClickable;

 

繼續測試,所有值默認,ViewGroupdispatchTouchEvent返回true

 

日誌打印如下圖

 

5 小結

由於ViewonTouchEvent返回了false,所以ViewGrouponTouchEvent纔會被調用;

由於ViewonTouchEventACTION_DOWN就返回了false,所以後續的ACTION_MOVEACTION_UP也就沒有接收到;

即時ViewGrouponTouchEventACTION_DOWN返回了true,他的後續MOVEUP也能接收到;

 

現改成如下方式;

 

輸出日誌如下:

 

6 小結

ViewGroupdispatchTouchEvent方法裏面只要ACTION_DOWN返回了true,其他的MOVEUP事件不受影響

 

繼續測試,onInterceptTouchEven返回true,代碼如下

 

由前面分析ViewGroup源碼得知,此時View接收不到任何事件的;

輸入日誌如下

View裏面onTouchEvent返回true

 ViewGroup onInterceptTouchEventMOVE事件裏面返回true

 

打印日誌如下

 

 

這個時候View onTouchEvent方法只接收到了DOWN CANCEL事件;

ViewGroup 接收到了MOVEUP事件

小結:

ViewGroup若在MOVE方法返回true,子控件只能接收到DOWNCANCEL事件。ViewGroup可以接收到MOVEUP事件

現在在ViewGroup UP事件上攔截

 

輸出日誌如下

 

 

View接收到onTouchEvent 方法接收到DOWNMOVECANCEL事件;

ViewGroupdispatchTouEvent裏面可以接收DOWN,MOVE,UP事件,onTouchEvent沒有執行;

總結

1 如果一個控件能夠接受到事件則最新執行的方法肯定是dispatchTouEvent方法;

2 如果一個控件設置了onTouchLister,則onTouchListener優於onTouchEvent方法執行;

  3 onTouchListener的情況下,如果onTouchListener返回true,dispatchTouEvent的返回值也爲true,並且onTouchEvent方法不會再執行;

4 onTouchListener或者onTouchListener返回false的情況下,dispatchTouEvent的返回值和onTouchEvent是一樣的;

5 ACTION_DOWN,ACTION_MOVE,ACTION_UP事件是一系列連續的,如果某個事件返回了false,則後續事件也不再調用

  6 父容器控件的onInterceptTouchEvent方法也是如此,如果某個事件返回了true,則子控件會接收到ACTION_CANCEL事件,並且子控件後續事件也不在會執行。

  7 字控件的dispatchTouEvent方法如果能夠執行,並且返回true.則父容器控件的onTouchEvent方法就不會執行。

http://www.bubuko.com/infodetail-1466509.html

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章