1. 程式人生 > >android事件之onInterceptTouchEvent,dispatchTouchEvent,onTouchEvent,requestDisallowInterceptTouchEvent

android事件之onInterceptTouchEvent,dispatchTouchEvent,onTouchEvent,requestDisallowInterceptTouchEvent

android 的這個事件的分發傳遞,處理的解決方式,
實質應該是 java設計模式裡面的 責任鏈模式了。

在這裡,想用最少的話,最通俗易懂的方式記錄

  1. View的方法
// 事件分發,預設返回false 
public boolean dispatchTouchEvent(MotionEvent event) 

// 事件處理,預設返回false 
public boolean onTouchEvent(MotionEvent event) 
  1. ViewGroup的方法
// 事件分發,預設返回false 
public boolean dispatchTouchEvent
(MotionEvent event) // 事件處理,預設返回false public boolean onTouchEvent(MotionEvent event) // 攔截預設,預設返回false // 返回true 就不會往下傳遞事件,自己onTouchEvent處理 // 返回false 向下傳遞事件 public boolean onInterceptTouchEvent(MotionEvent ev)

當觸發一個事件,父佈局會優先得到這個事件進行分發,也就是一般的

單獨View (如果設定了OnTouchListener)

dispatchEvent–setOnTouchListener–onTouchEvent

單獨看ViewGroup (如果設定了OnTouchListener)

dispatchTouchEvent –onInterceptTouchEvent–setOnTouchListener– onTouchEvent

3.ViewGroup 巢狀View
也就是我們平常最有用最關心的
前面說了底層的View能夠接收到這次的事件有一個前提條件:在父層級允許的情況下。假設不改變父層級的dispatch方法,在系統呼叫底層onTouchEvent之前會先呼叫父View的onInterceptTouchEvent方法判斷,父層View是不是要截獲本次touch事件之後的action。
所以下面的這個東西一定要分清楚,這個還是很重要的,很多人很模糊,或者說很ran

事件的分發上的執行順序:
(父)dispatchTouchEvent

(父)onInterceptTouchEvent

(子)dispatchEvent

事件的處理執行順序
(子)onTouchEvent

(父)onTouchEvent

4 如果父View 和 子View都設定的點選事件相應的問題了

其實是都可以響應的,不多說,ListView中的adapter item佈局都寫的多了。

5 requestDisallowInterceptTouchEvent

requestDisallowInterceptTouchEvent 是ViewGroup類中的一個公用方法
Called when a child does not want this parent and its ancestors to intercept touch events with ViewGroup.onInterceptTouchEvent(MotionEvent).
This parent should pass this call onto its parents. This parent must obey this request for the duration of the touch (that is, only clear the flag after this parent has received an up or a cancel.

實際的應用中,可以在子view的ontouch事件中注入父ViewGroup的例項,並呼叫requestDisallowInterceptTouchEvent去阻止父view攔截點選事件
原來listView 第一行, 巢狀ViewPager ,其他行巢狀別的中就需要用到這個

需要在子佈局中操作,父佈局不攔截自身事件

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //讓父類不攔截觸控事件就可以了。
        this.getParent().requestDisallowInterceptTouchEvent(true); 
        return super.dispatchTouchEvent(ev);

    }

下面有程式碼例子,看著也很詳細。後面看到補充的
燕子的 http://blog.csdn.net/yanzi1225627/article/details/22592831

View的dispatchTouchEvent()函式

public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);  
        }  

        if (onFilterTouchEventForSecurity(event)) {  
            //noinspection SimplifiableIfStatement  
            ListenerInfo li = mListenerInfo;  

        //在這裡會進行  onTouch 等的判斷,如果此條件成功 就不往下走了
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
                    && li.mOnTouchListener.onTouch(this, event)) {  
                return true;  
            }  

            //如果上面條件不成立,繼續往下執行 onTouchEvent
            if (onTouchEvent(event)) {  
                return true;  
            }  
        }  

        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  
        }  
        return false;  
} 


public boolean onTouchEvent (){

 if (!mHasPerformedLongPress) {  
                   .......
                            // 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)) {  

                     //這裡執行了,我們註冊的 onclick事件 
                                     performClick();
                                }  
                            }  
                        }
                   ........
            }


  public boolean performClick() {  
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  

        ListenerInfo li = mListenerInfo;  
        if (li != null && li.mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
             li.mOnClickListener.onClick(this);  
            return true;  
        }  

        return false;  
    }

總結:

1、事件入口是dispatchTouchEvent(),它會先執行註冊的onTouch監聽,如果一切順利的話,接著執行onTouchEvent,在onTouchEvent裡會執行onClick監聽。


2、無論是dispatchTouchEvent還是onTouchEvent,如果返回true表示這個事件已經被消費、處理了,不再往下傳了。

(1)在dispathTouchEvent的原始碼裡可以看到,如果onTouchEvent返回了true,那麼它也返回true。

(2)如果dispatchTouchEvent 在執行onTouch監聽的時候,onTouch返回了true,那麼它也返回true,這個事件提前被onTouch消費掉了。就不再執行onTouchEvent了,更別說onClick監聽了。



3、我們通常在onTouch監聽了設定圖片一旦被觸控就改變它的背景、透明度之類的,這個onTouch表示事件的時機。而在onClick監聽了去具體幹某些事。

//複寫button
public class TestButton extends Button {  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        switch(event.getAction()){  
        case MotionEvent.ACTION_DOWN:  
            Log.i(tag, "TestButton-onTouchEvent-ACTION_DOWN...");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.i(tag, "TestButton-onTouchEvent-ACTION_UP...");  
            break;  
        default:break;  
        }  
        return super.onTouchEvent(event);  
    }  

    @Override  
    public boolean dispatchTouchEvent(MotionEvent event) {  
        switch(event.getAction()){  
        case MotionEvent.ACTION_DOWN:  
            Log.i(tag, "TestButton-dispatchTouchEvent-ACTION_DOWN...");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.i(tag, "TestButton-dispatchTouchEvent-ACTION_UP...");  
            break;  
        default:break;  
        }  

        return super.dispatchTouchEvent(event);  
    }  

}  

======================================================

Activity 的

public boolean dispatchTouchEvent(MotionEvent ev) {  
        // TODO Auto-generated method stub  
        switch(ev.getAction()){  
        case MotionEvent.ACTION_DOWN:  
            Log.i(tag, "MainActivity-dispatchTouchEvent-ACTION_DOWN...");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.i(tag, "MainActivity-dispatchTouchEvent-ACTION_UP...");  
            break;  
        default:break;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  

@Override  
public boolean onTouchEvent(MotionEvent event) {  
    // TODO Auto-generated method stub  
    switch(event.getAction()){  
    case MotionEvent.ACTION_DOWN:  
        Log.i(tag, "MainActivity-onTouchEvent-ACTION_DOWN...");  
        break;  
    case MotionEvent.ACTION_UP:  
        Log.i(tag, "MainActivity-onTouchEvent-ACTION_UP...");  
        break;  
    default:break;  
    }  
    return super.onTouchEvent(event);  
}


//按鈕註冊 onclick
testBtn.setOnClickListener(new View.OnClickListener() {  

            @Override  
            public void onClick(View v) {  
                // TODO Auto-generated method stub  
                Log.i(tag, "testBtn---onClick...");  
            }  
        });  

//按鈕註冊 onTouch
testBtn.setOnTouchListener(new View.OnTouchListener() {  

    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
    // TODO Auto-generated method stub  
    switch(event.getAction()){  
    case MotionEvent.ACTION_DOWN:  
        Log.i(tag, "testBtn-onTouch-ACTION_DOWN...");  
        break;  
    case MotionEvent.ACTION_UP:  
        Log.i(tag, "testBtn-onTouch-ACTION_UP...");  
        break;  
    default:break;  

    }  
    return false;  
    }  
});

MainActivity-dispatchTouchEvent-ACTION_DOWN...

TestButton-dispatchTouchEvent-ACTION_DOWN...
testBtn-onTouch-ACTION_DOWN...
TestButton-onTouchEvent-ACTION_DOWN...

MainActivity-dispatchTouchEvent-ACTION_UP...

TestButton-dispatchTouchEvent-ACTION_UP...
testBtn-onTouch-ACTION_UP...
TestButton-onTouchEvent-ACTION_UP...
testBtn---onClick...

事件先由Activity的dispatchTouchEvent進行分發,然後TestButton的dispatchTouchEvent進行分發,接著執行onTouch監聽,然後執行onTouchEvent。第二次UP動作的時候,在onTouchEvent裡又執行了onClick監聽。

如果我們想這個TestButton只能執行onTouch監聽不能執行onClick監聽,方法有很多。在onTouch監聽裡預設返回false改為true,如下:


@Override  
    public boolean onTouch(View v, MotionEvent event) {  
    // TODO Auto-generated method stub  
    switch(event.getAction()){  

    }  
    return treu;  
    }  

返回true 以後,表示 ontouch 消費此事件,不向西傳遞了 所以就不走 onTouchEvent了


public boolean dispatchTouchEvent(MotionEvent ev) {  
        if (mInputEventConsistencyVerifier != null) {  
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);  
        }  

        boolean handled = false;  
        if (onFilterTouchEventForSecurity(ev)) {  
            final int action = ev.getAction();  
            final int actionMasked = action & MotionEvent.ACTION_MASK;  

            // 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) {  

            // 在這裡
                    intercepted = onInterceptTouchEvent(ev);  // 預設返回false
                    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;  
            }  

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

        //================================
              if (!canceled && !intercepted) {  
                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;  

              ........
          . ......

        }

 MainActivity-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN... false
 TestButton-dispatchTouchEvent-ACTION_DOWN...
 testBtn-onTouch-ACTION_DOWN...
 TestButton-onTouchEvent-ACTION_DOWN...
---------------------------------------------------------------------------------------------------------------------------
 MainActivity-dispatchTouchEvent-ACTION_UP...
 TestLinearLayout-dispatchTouchEvent-ACTION_UP...
 TestLinearLayout-onInterceptTouchEvent-ACTION_UP...   false
 TestButton-dispatchTouchEvent-ACTION_UP...
 testBtn-onTouch-ACTION_UP...
 TestButton-onTouchEvent-ACTION_UP...
 testBtn---onClick...


2,如果將TestLinearlayout的 onInterceptTouchEvent (攔截) 改成return true,即不讓孩子們知道。

 MainActivity-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-dispatchTouchEvent-ACTION_DOWN...
 TestLinearLayout-onInterceptTouchEvent-ACTION_DOWN... true 自己處理

 testLinelayout-onTouch-ACTION_DOWN...
 TestLinearLayout-onTouchEvent-ACTION_DOWN...

 MainActivity-dispatchTouchEvent-ACTION_UP...
 TestLinearLayout-dispatchTouchEvent-ACTION_UP...
 testLinelayout-onTouch-ACTION_UP...
 TestLinearLayout-onTouchEvent-ACTION_UP...
 testLinelayout---onClick...
1、如果是自定義複合控制元件,如圖片+文字,我再Activity裡給你註冊了onClick監聽,期望點選它執行。那麼最簡單的方法就是將圖片+文字的父佈局,
也即讓其容器ViewGroup的祕書將事件攔下,這樣父親就可以執行onClick了。這時候的父親就像一個獨立的孩子一樣了(View),無官一身輕,
再也不用管它的孩子了,可以正常onClick onTouch.

2、如果希望一個View只onTouch而不onClick,在onTouch裡return true就ok了。

3、dispatch是為了onTouch監聽,onTouchEvent是為了onClick監聽。

4、自定義佈局時,一般情況下:
@Override
    public boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}  

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);
    我們可以複寫,但是最後的super.***是萬萬不能少滴。如果少了,表示連dispatch*** onTouchEvent壓根就不呼叫了,事件就此打住。