1. 程式人生 > >Android OnTouchEvent和OnClick、OnLongClick、OnTouch、TouchDelegate關係

Android OnTouchEvent和OnClick、OnLongClick、OnTouch、TouchDelegate關係

說明:本部落格為原創,轉載請註明出處 http://blog.csdn.net/gucun4848
由於作者水平有限,錯誤在所難免,請見諒,可以留言,本人會及時改正

索引

基於上篇Android Touch事件總結一,本篇介紹Touch事件和Click、LongClick、Touch、TouchDelegate的關係。

OnClick

public void setOnClickListener(OnClickListener l){
//註冊點選事件
}

用法很簡單這裡只介紹下View什麼時候觸發這個回撥事件,原始碼如下:

public boolean
onTouchEvent(MotionEvent event) ... switch (action) { case MotionEvent.ACTION_UP: ... // 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(); } //觸發OnClick事件 if (!post(mPerformClick)) { performClick(); } } } ... // 可以看到,在OnTouchEvent方法中,接收到ACTION_UP事件後,判斷如果View當前不是FocusableInTouchMode狀態,沒有LongPressed狀態,觸發OnClick事件。

OnLongClick

public void setOnLongClickListener(OnLongClickListener l){
//註冊長按事件
}

用法很簡單這裡只介紹下View什麼時候觸發這個回撥事件,原始碼如下:

 public boolean onTouchEvent(MotionEvent event)
 ...
 //還是在OnTouchEvent方法,接收到DOWN事件後,通過PostDelayed()實現
 case MotionEvent.ACTION_DOWN:

    mHasPerformedLongPress = false;

    // Walk up the hierarchy to determine if we're inside a scrolling container.
    boolean isInScrollingContainer = isInScrollingContainer();

    // For views inside a scrolling container, delay the pressed feedback for
    // a short period in case this is a scroll.
    if (isInScrollingContainer) {
        mPrivateFlags |= PFLAG_PREPRESSED;
        if (mPendingCheckForTap == null) {
            mPendingCheckForTap = new CheckForTap();
        }
        mPendingCheckForTap.x = event.getX();
        mPendingCheckForTap.y = event.getY();
        // mPendingCheckForTap中觸發事件
        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;

OnTouchListener

public void setOnTouchListener(OnTouchListener l){
//註冊觸控事件,這個回撥會攔截View自身的OnTouchEvent方法。
}

用法很簡單這裡只介紹下View什麼時候觸發這個回撥事件,原始碼如下:

public boolean dispatchTouchEvent(MotionEvent event) {
...
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        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;
        }
    }
}
// 在分發事件中先觸發OnTouch回撥,如果OnTouch回撥中不消費事件,才會觸發View自身的OnTouchEvent方法。

setTouchDelegate

public void setTouchDelegate(TouchDelegate l){
//註冊觸控委派事件,這個回撥常用於增加自身的可觸控面積。
//例項TouchDelegate物件需要兩個引數Rect bounds, View delegateView
//bounds 觸控區域大小
//delegateView 委派事件的View
//這裡需要**注意**的是需要delegateView的ParentView呼叫setTouchDelegate()方法!
}

這裡看下View的原始碼:

public boolean onTouchEvent(MotionEvent event) {
        ...
        //在View的OnTouchEvent方法中,優先呼叫的委派事件       
        //這裡需要注意的是委派事件的View的ParentView必須保證自身的onTouchEvnet方法會被呼叫!
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        ...
}

//這裡是TouchDelegate中的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
        int x = (int)event.getX();
        int y = (int)event.getY();
        boolean sendToDelegate = false;
        boolean hit = true;
        boolean handled = false;

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Rect bounds = mBounds;
            //這裡判斷當前的觸控位置是不是在委派的邊界內
            if (bounds.contains(x, y)) {
                mDelegateTargeted = true;
                sendToDelegate = true;
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_MOVE:
            sendToDelegate = mDelegateTargeted;
            if (sendToDelegate) {
                Rect slopBounds = mSlopBounds;
                if (!slopBounds.contains(x, y)) {
                    hit = false;
                }
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            sendToDelegate = mDelegateTargeted;
            mDelegateTargeted = false;
            break;
        }
        //觸控位置在邊界內
        if (sendToDelegate) {
            final View delegateView = mDelegateView;

            if (hit) {
                // Offset event coordinates to be inside the target view
                event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
            } else {
                // Offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                int slop = mSlop;
                event.setLocation(-(slop * 2), -(slop * 2));
            }
            //呼叫了委派事件的View的dispatchTouchEvent方法.
            handled = delegateView.dispatchTouchEvent(event);
        }
        return handled;
    }

Demo

GitHub地址: GitHub
環境: Windows7+JAVA8
IDE: AndroidStdio2.2.2
compileSdkVersion:24
測試裝置:Nexus5(6.0.1)

執行Demo的時候需要在Manifest.xml中啟動項設定成MainViewTouchClickActivity

點選灰色區域,可以觸發黃色View的OnClick事件
TouchDelegateListener