Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 事件分發系列—View中的dispatchTouchEvent和onTouchEvent分析

事件分發系列—View中的dispatchTouchEvent和onTouchEvent分析

編輯:關於Android編程

dispatchTouchEvent

話不多說直接上源碼

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     * 將屏幕的按壓事件傳遞給目標view,或者當前view即目標view
     * 
     * @param event The motion event to be dispatched.
     * 需要分發的事件
     * 
     * @return True if the event was handled by the view, false otherwise.
     * 如果返回true表示這個事件被這個view處理了,否則反
     */
    public boolean dispatchTouchEvent(MotionEvent event) {

        //系統調試分析相關,沒有影響
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        //過濾是不是能夠傳遞這個touch事件
        if (onFilterTouchEventForSecurity(event)) {

            //首先判斷我們在使用該view的時候是否有實現OnTouchListener,如果有實現就判斷當前的view
             //狀態是不是ENABLED,如果實現的OnTouchListener的onTouch中返回true,並處理事件,則
             //返回true,這個事件在此處理了。
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            //如果沒有在代碼裡面setOnTouchListener的話,就判斷View自身的onTouchEvent方法有沒有
            //處理,沒有處理最後返回false,處理了返回true;
            if (onTouchEvent(event)) {
                return true;
            }

        }
        //系統調試分析相關,沒有影響
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        //如果即沒有setOnTouchListener,也沒有在onTouchEvent中處理,就返回false
        return false;

    }

從上面的源碼可以看出:在View的分發事件方法dispatchTouchEvent中,處理分發的順序是實現OnTouchListener的onTouch(),之後是當前View的onTouchEvent(event)方法。

onTouchEvent

onTouchEvent的源碼有點長,所以要沉下心來仔細閱讀。事件消耗和事件處理都是返回true,事件消耗就相當於占坑不拉屎,雖然有點惡心哈,事件處理當然就是占坑拉屎。

在Android的觸摸消息中,已經實現了三種監測,它們分別是
1)pre-pressed:對應的語義是用戶輕觸(tap)了屏幕
2)pressed:對應的語義是用戶點擊(press)了屏幕
3)long pressed:對應的語義是用戶長按(long press)了屏幕
下圖是觸摸消息隨時間變化的時間軸示意圖:
這裡寫圖片描述


了解onTouchEvent就現需要了解的方法和類

CheckForTap類
該類實現了Runnable接口,在run函數中設置觸摸標識,並刷新Drawable的狀態,同時用於發送一個檢測長按事件的異步延遲消息,代碼如下:
private final class CheckForTap implements Runnable {  
    public void run() {  
        // 進入該函數,說明已經過了ViewConfiguration.getTapTimeout()時間,   
        // 即pre-pressed狀態結束,宣告觸摸進入pressed狀態   
        mPrivateFlags &= ~PREPRESSED;   
        mPrivateFlags |= PRESSED;  
        refreshDrawableState(); // 刷新控件的背景Drawable   
        // 如果長按檢測沒有被去使能,則發送一個檢測長按事件的異步延遲消息   
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
            postCheckForLongClick(ViewConfiguration.getTapTimeout());  
        }  
    }  
}  

private void postCheckForLongClick(int delayOffset) {  
    mHasPerformedLongPress = false;  

    // 實例化CheckForLongPress對象   
    if (mPendingCheckForLongPress == null) {  
        mPendingCheckForLongPress = new CheckForLongPress();  
    }  
    mPendingCheckForLongPress.rememberWindowAttachCount();  
    // 調用PostDelayed函數發送長按事件的異步延遲消息   
    postDelayed(mPendingCheckForLongPress,  
            ViewConfiguration.getLongPressTimeout() - delayOffset);  
}  
CheckForLongPress類
該類定義了長按操作發生時的響應處理,同樣實現了Runnable接口
class CheckForLongPress implements Runnable {  

    private int mOriginalWindowAttachCount;  

    public void run() {  
        // 進入該函數,說明檢測到了長按操作   
        if (isPressed() && (mParent != null)  
                && mOriginalWindowAttachCount == mWindowAttachCount) {  
            if (performLongClick()) {   
                mHasPerformedLongPress = true;  
            }  
        }  
    }  

    public void rememberWindowAttachCount() {  
        mOriginalWindowAttachCount = mWindowAttachCount;  
    }  
}  

public boolean performLongClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);  

    boolean handled = false;  
    if (mOnLongClickListener != null) {  
        // 回調用戶實現的長按操作監聽函數(OnLongClickListener)   
        handled = mOnLongClickListener.onLongClick(View.this);  
    }  
    if (!handled) {  
        // 如果OnLongClickListener的onLongClick返回false   
        // 則需要繼續處理該長按事件,這裡是顯示上下文菜單   
        handled = showContextMenu();  
    }  
    if (handled) {  
        // 長按操作事件被處理了,此時應該給用戶觸覺上的反饋   
        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);  
    }  
    return handled;  
}

了解完,我們來看看onTouchEvent的實現

   /**
     * Implement this method to handle touch screen motion events.
     * 如果需要處理屏幕產生的事件流需要實現這個方法
     * 
     * @param event The motion event.
     * 
     * @return True if the event was handled, false otherwise.
     * 如果返回true表示這個處理了這個事件,false則反
     */
    public boolean onTouchEvent(MotionEvent event) {

        //viewFLags用來記錄當前View的狀態
        final int viewFlags = mViewFlags;

        //如果當前View狀態為DISABLED,如果不清楚DISABLED是一種什麼狀態那你應該用過
        //setEnabled(boolean enabled)這個方法,DISABLED就是ENABLED相反的狀態。
        //DISABLED = 0x00000020 ,ENABLED_MASK = 0x00000020
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
               //如果View的狀態是被按壓過,且當抬起事件產生,重置View狀態為未按壓,刷新Drawable的狀態
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
            }
            //如果當前View是一個DISABLED狀態,且當前View是一個可點擊或者是可長按的狀態則當前事件在
            //此消耗不做處理,返回true。
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }



----------


        //TouchDelegate是一個事件處理邏輯封裝的一個類,也就是說Touch事件處理被委托了,那麼就交由
        //mTouchDelegate.onTouchEvent處理,如果返回true,則事件被處理了,則不會向下傳遞
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }



----------

        //如果當前View的狀態是可點擊或者是可長按的,就對事件流進行細節處理
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    //PREPRESSED = 0x02000000
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    //如果是pressed狀態或者是prepressed狀態,才進行處理   
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        //如果設定了獲取焦點,那麼調用requestFocus獲得焦點
                        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.
                            //在釋放之前給用戶顯示View的prepressed的狀態,狀態需要改變為
                            //PRESSED,並且需要將背景變為按下的狀態為了讓用戶感知到
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                       }
                        // 是否處理過長按操作了,如果是,則直接返回   
                        if (!mHasPerformedLongPress) {
                            //如果不是長按的話,僅僅是一個Tap,所以移除長按的回調
                            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.
                                //UI子線程去執行click,為了讓click事件開始的時候其他視覺發
                                //生變化不影響。
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //如果post消息失敗,直接調用處理click事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }


----------


                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            //ViewConfiguration.getPressedStateDuration() 獲得的是按下效 
                            //果顯示的時間,由PRESSED_STATE_DURATION常量指定,在2.2中為125
                            //毫秒,也就是隔了125毫秒按鈕的狀態重置為未點擊之前的狀態。目的是讓用戶
                            //感知到click的效果
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            //如果通過post(Runnable runnable)方式調用失敗,則直接調用
                            mUnsetPressedState.run();
                        }
                        //移除Tap的回調 重置View的狀態
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    //在觸摸事件中執行按鈕相關的動作,如果返回true則表示已經消耗了down
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    //判斷是否在一個滾動的容器內
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // 如果父容器是一個可滾動的容器
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PREPRESSED;
                        //將view的狀態變為PREPRESSED,檢測是Tap還是長按事件
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        //直接將view狀態轉化為PRESSED,更新Drawable
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        //是否是長按事件的判斷
                        checkForLongClick(0);
                    }
                    break;
                //接收到系統發出的ACTION_CANCLE事件時,重置狀態
                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // 如果移動超出了按鈕的范圍
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            //移除長按的回調
                            removeLongPressCallback();

                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }

        return false;
    }

對於onTouchEvent總的來說,首先受到事件首先判斷當前View的狀態是否為DISABLED,如果是則只需要簡單的做一些狀態的重置,不對事件做細節處理。如果不是DISABLED,就需要對事件細節進行處理,這時候又半路來個程咬金TouchDelegate,如果mTouchDelegate不為空,且返回了true,就表示該事件流有人給你代勞處理了,後面的分析View自己也不用做了。最後如果沒人攔截處理,那就得View自己來。

下面開始是View自己處理事件流的邏輯過程描敘,即switch判斷事件分支的處理:

ACTION_DOWN
判斷是否在觸摸事件中執行按鈕相關的動作,如果是,直接跳出,不是則繼續判斷當前View是否在一個可滑動的容器中,如果是則判斷是否是一個點擊tab事件,還是長按事件的檢查,如果不是則直接轉化狀態為PRESSED並判斷是否為長按事件。

ACTION_MOVE
判斷移動的點是否在當前View中,如果不在其中且當前狀態為PRESSED則重置非PRESSED,且移除長按的回調。

ACTION_UP
當抬起事件產生,首先判斷View的狀態是pressed狀態或者是prepressed狀態(也就是按過),才進行處理,如果是prepressed狀態則變為pressed,並更新Drawable,然後判斷在Down中的mHasPerformedLongPress標志,有沒有變為true,也就是有沒有產生執行長按事件,如果沒有,則把這個事件流當做一個click事件做處理,也就是執行performClick中的代碼,執行完click中的代碼,最後重置View的狀態和刷新Drawable。

ACTION_CANCLE
當系統發送一個action_cancle的事件,則重置View的狀態為非PRESSED,刷新Drawable的狀態,且移除Tab的回調。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved