Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android事件分發淺談

Android事件分發淺談

編輯:關於Android編程

前言:可能Android的事件分發對於剛學Android的童鞋來說接觸得不多,這樣不奇怪。因為剛學的時候,一般人很難注意到或是會選擇主動去了解。那麼究竟什麼是Android的事件分發呢?   或許剛說出來,有點覺悟的新手會想到就是那些按鈕的點擊事件、或是說監聽。而這些也確實是Android事件分發的其中一部分。由於Android的事件分發其實是可以有很多變化的,特別是當你需要自定義View的時候,很多情況都需要具體分析,所以大體上它都不容易精確的掌握。但如果是主流的,大概的事件分發機制其實也沒那麼難理解,說到底這可以說是一個淺入深出的問題吧。那麼接下來我們來淺談一下這次的主題Android事件分發機制。   1.比喻 打個比方,Android的事件分發中的事件(用戶的觸屏)就像一塊餅。假若有一家3代的人在饑荒的年代裡,如果爺爺有一塊餅,那麼他會先給誰?那當然會是孫子。但爺爺年紀大視力不好,所以他把餅傳遞給了兒子,而兒子又把這塊餅傳給了孫子,最終由孫子吃下了那塊餅。而像這樣的父穿子,子傳孫的方法,就如同我們Android中的事件分發一樣。 \ 接下來我們新建一個工程並寫好如上圖的布局,這是一個最外層包著RelativeLayout(爺爺),中間是LinearLayout(兒子),最裡面是Button(孫子)。 \   圖畫得不是很好,大家就湊合這看吧。上面就是我們布局的上個View。而觸摸事件(餅)就是通過RelativeLayout--->LinearLayout--->Button,這樣一層層的傳遞的。而事件的分發就是這樣通過disppatchTouchEvent(),OnInterceptTouchEvent(),OnTouchEvent()這三個方法去判斷事件是否繼續向下傳遞。   而當其中有一層View自己先截獲了事件消費掉了(可理解為自己用掉了點擊事件),那麼事件則不會向下傳遞,而在OnTouchEvent中返回False則不會自己消費並返回到上一層去處理。   看到這裡大家肯定還是不明白,接著到java代碼裡面,按著 Ctrl+ Shift+ T,查找一下View的API,這裡我選API23的,其實API多少都沒太大變化,我就選一個最新的。 \   3.理解dispatchTouchEvent(),onInterceptTouchEvent(),OnTouchEvent()三個事件   這三個事件理解起來,首先要區分View與ViewGroup兩種情況: * View:dispatchTouchEvent,OnTouchEvent * ViewGroup:dispatchTouchEvent,onInterceptTouchEvent,OnTouchEvent   其中View是沒有onInterceptTouchEvent方法的。在Android中你只要觸摸控件首先都會觸發控件的dispatchTouchEvent方法,所以我們會找View的API,而不是找Button的API,因為這個方法一般在具體的控件類中是找不到的,他是父View的方法。 下面我貼出找到的dispatchTouchEvent源碼,看看官方是怎麼解析的。   理解dispatchTouchEvent()
/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
        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;
            }
        }
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
        return result;
    }

看上面的官方解析,大概意思是:這個View接收的是從屏幕傳遞過來的事件並傳給目標的View,或是這個View就是從屏幕傳達過來的事件的目標。   這句話怎麼理解呢?其實也就對應我上面畫的圖,兩種情況:要麼該View就是目標的View(自己消費掉事件),要麼向下傳遞事件。而這個方法默認是調用super.dispatchTouchEvent(event)的,需傳入super.dispatchTouchEvent(true),需要注意的是他如果直接返回true或flase而不是進過調用supper的都不會向下傳遞。這裡可能有點難理解,不用急,下面會繼續解析。   接著我們看看onInterceptTouchEvent()方法。   理解onInterceptTouchEvent() \   同樣的快捷鍵方法我們來到ViewGroup的源碼:  
/**
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     *
     * 

Using this function takes some care, as it has a fairly complicated * interaction with {@link View#onTouchEvent(MotionEvent) * View.onTouchEvent(MotionEvent)}, and using it requires implementing * that method as well as this one in the correct way. Events will be * received in the following order: * *

  1. *
  2. You will receive the down event here. *
  3. The down event will be handled either by a child of this view * group, or given to your own onTouchEvent() method to handle; this means * you should implement onTouchEvent() to return true, so you will * continue to see the rest of the gesture (instead of looking for * a parent view to handle it). Also, by returning true from * onTouchEvent(), you will not receive any following * events in onInterceptTouchEvent() and all touch processing must * happen in onTouchEvent() like normal. *
  4. For as long as you return false from this function, each following * event (up to and including the final up) will be delivered first here * and then to the target's onTouchEvent(). *
  5. If you return true from here, you will not receive any * following events: the target view will receive the same event but * with the action {@link MotionEvent#ACTION_CANCEL}, and all further * events will be delivered to your onTouchEvent() method and no longer * appear here. *
* * @param ev The motion event being dispatched down the hierarchy. * @return Return true to steal motion events from the children and have * them dispatched to this ViewGroup through onTouchEvent(). * The current target will receive an ACTION_CANCEL event, and no further * messages will be delivered here. */ public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true; } return false; }
  這裡他的代碼不是很多,但官方的注釋卻又很多,我們只看第一段,大概意思是:這個方法他攔截了所有的屏幕事件。他允許用戶監測這些事件,並且可以分發到他的子View裡面,作為子View的一個手勢(或是說任何點)的手勢。   所以從上面我們著重的可以知道,onInterceptTouchEvent()這個方法可以攔截事件的分發。而當onInterceptTouchEvent()返回的是false的時候就說明不攔截這個View的子View,那麼子View就可以獲取到這個事件了。   而OnTouchEvent()方法在View跟ViewGroup都有,所以要分開討論。我們現在可以理解為,若是OnTouchEvent()返回true則代表這一層的View自己消費掉事件,而返回false,那麼事件會重新返回到該View的父View,也就是上一層的View中。 \     當RelativeLayout在onInterceptTouchEvent()裡面不攔截子View的時候,事件就會傳遞到LinearLayout的dispatchTouchEvent()事件裡面。而同樣LinearLayout也是繼承ViewGroup的,所以他也有onInterceptTouchEvent方法。   而LinearLayout的OnTouchEvent()裡面,如果返回true則代表自己消費掉事件,而如果返回false則表示不作處理並返回給上層父View處理 4.結合例子理解   這個是我編寫的上圖的布局,裡面三個都是簡單的自定義View,作用是方便打印出信息

    
        
    

接下來我們在這些View裡面重寫dispatchTouchEvent()和OnTouchEvent()兩個方法,分別在裡面打印Log,查看結果。   RelativeLayout
public class CustomRelativieLayout extends RelativeLayout {
	public CustomRelativieLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public CustomRelativieLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public CustomRelativieLayout(Context context) {
		super(context);
	}
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		return super.dispatchTouchEvent(ev);
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		return false;
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}
}

LinearLayout
public class CustomLinearLayout extends LinearLayout{
	public CustomLinearLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public CustomLinearLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public CustomLinearLayout(Context context) {
		super(context);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		return super.dispatchTouchEvent(ev);
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		return super.onInterceptTouchEvent(ev);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}
}

Button
public class CustomButton extends Button{
	public CustomButton(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public CustomButton(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public CustomButton(Context context) {
		super(context);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		return super.dispatchTouchEvent(event);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}
}

1)首先我們來寫三個View中的三個方法裡的Log日志 RelativeLayout
@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:onInterceptTouchEvent");
		return false;
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "CustomRelativieLayout:onTouchEvent");
		return super.onTouchEvent(event);
	}
LinearLayout
@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomLinearLayout:dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomLinearLayout:onInterceptTouchEvent");
		return super.onInterceptTouchEvent(ev);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "CustomLinearLayout:onTouchEvent");
		return super.onTouchEvent(event);
	}
Button
@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "CustomButton:dispatchTouchEvent");
		return super.dispatchTouchEvent(event);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "CustomButton:onTouchEvent");
		return super.onTouchEvent(event);
	}

這裡需要注意的是,在Activity中其實也含有onInterceptTouchEvent()和OnTouchEvent()這兩個方法,所以我們也需要重寫這兩個方法。 MainActivity
public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Button btn=(Button) findViewById(R.id.btn);
		btn.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Log.d("TouchEvent", "MainActivity:onClick");
			}
		});
	}
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "MainActivity:dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "MainActivity:onTouchEvent");
		return super.onTouchEvent(event);
	}
}
  之後我們按下按鈕。因為點擊事件是由按下和抬起兩部分組成的,所以上述的Log日志會打印兩次。在Android裡面,按下和抬起是分別處理的兩個不同的事件,可以看到打印結果如下: 按下
09-27 17:27:41.222: D/TouchEvent(1493): MainActivity:dispatchTouchEvent
09-27 17:27:41.222: D/TouchEvent(1493): CustomRelativieLayout:dispatchTouchEvent
09-27 17:27:41.222: D/TouchEvent(1493): CustomRelativieLayout:onInterceptTouchEvent
09-27 17:27:41.222: D/TouchEvent(1493): CustomLinearLayout:dispatchTouchEvent
09-27 17:27:41.222: D/TouchEvent(1493): CustomLinearLayout:onInterceptTouchEvent
09-27 17:27:41.222: D/TouchEvent(1493): CustomButton:dispatchTouchEvent
09-27 17:27:41.222: D/TouchEvent(1493): CustomButton:onTouchEvent
抬起
09-27 17:27:41.321: D/TouchEvent(1493): MainActivity:dispatchTouchEvent
09-27 17:27:41.321: D/TouchEvent(1493): CustomRelativieLayout:dispatchTouchEvent
09-27 17:27:41.321: D/TouchEvent(1493): CustomRelativieLayout:onInterceptTouchEvent
09-27 17:27:41.321: D/TouchEvent(1493): CustomLinearLayout:dispatchTouchEvent
09-27 17:27:41.321: D/TouchEvent(1493): CustomLinearLayout:onInterceptTouchEvent
09-27 17:27:41.321: D/TouchEvent(1493): CustomButton:dispatchTouchEvent
09-27 17:27:41.321: D/TouchEvent(1493): CustomButton:onTouchEvent
09-27 17:27:41.331: D/TouchEvent(1493): MainActivity:onClick
  總結1: 配合我畫的圖,結合上面的Log日志可以看出,點擊事件最終被Button的onClick事件所消費。 MainActivity: dispatchTouchEvent()為默認,向下傳遞。 Relative: dispatchTouchEvent()為默認true,向下傳遞。 onInterceptTouchEvent()為默認flase。不攔截事件,向下傳遞。 LinearLayout: dispatchTouchEvent()為默認true,向下傳遞。 onInterceptTouchEvent()為默認flase。不攔截事件,向下傳遞。 Button: dispatchTouchEvent()為默認true,向下傳遞。 OnTouchEvent():為默認false,默認不處理。   事件到了Button的OnTouchEvent()裡面後,由於默認是false,OnTouchEvent()方法不處理。,又向最上層的父View返回了,   看到這裡的Log日志再配合上面的圖,是不是應該能稍微理解了一些呢?若是不了解的話,那還得自己多看幾遍,或是自己也試試打印Log測試一下了。     2)接著我們來討論上面理解dispatchTouchEvent()時出現的一種情況:dispatchTouchEvent()返回true或false時不向下傳遞事件,當只有調用super.dispatchTouchEvent()的時候才會。   在這裡我試著修改RelativeLayout裡面的dispatchTouchEvent(),其余布局不變 RelativeLayout:dispatchTouchEvent()返回true
 @Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		return true;
	}
Log日志:
09-28 01:23:09.349: D/TouchEvent(1420): MainActivity:dispatchTouchEvent
09-28 01:23:09.349: D/TouchEvent(1420): CustomRelativieLayout:dispatchTouchEvent
09-28 01:23:09.630: D/TouchEvent(1420): MainActivity:dispatchTouchEvent
09-28 01:23:09.630: D/TouchEvent(1420): CustomRelativieLayout:dispatchTouchEvent

RelativeLayout:dispatchTouchEvent()返回false
@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		return false;
	}
Log日志:
09-28 01:26:03.632: D/TouchEvent(1470): MainActivity:dispatchTouchEvent
09-28 01:26:03.632: D/TouchEvent(1470): CustomRelativieLayout:dispatchTouchEvent
09-28 01:26:03.632: D/TouchEvent(1470): MainActivity:onTouchEvent
09-28 01:26:03.822: D/TouchEvent(1470): MainActivity:dispatchTouchEvent
09-28 01:26:03.822: D/TouchEvent(1470): MainActivity:onTouchEvent

總結2: 當dispatchTouchEvent()返回true或者flase的時候,事件不向下傳遞,只有返回的是 super.dispatchTouchEvent()才會進行傳遞。並且返回false的時候事件會返回到上層View的onTouchEvent(),但如果父View的onTouchEvent為默認(默認不處理)。那麼最終這個事件會沒有響應,不被任何層的View所消費掉。
3)接下來我們調用super.dispatchTouchEvent(),但不把他返回,而是選擇返回true或者false,觀察情況如何:   RelativeLayout:調用super.dispatchTouchEvent(),返回true:
@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		boolean event = super.dispatchTouchEvent(ev);
		Log.d("TouchEvent", "Touch:"+event);
		return true;
	}
Log日志:
09-28 02:26:36.926: D/TouchEvent(1580): CustomRelativieLayout:dispatchTouchEvent
09-28 02:26:36.926: D/TouchEvent(1580): CustomRelativieLayout:onInterceptTouchEvent
09-28 02:26:36.926: D/TouchEvent(1580): CustomLinearLayout:dispatchTouchEvent
09-28 02:26:36.926: D/TouchEvent(1580): CustomLinearLayout:onInterceptTouchEvent
09-28 02:26:36.926: D/TouchEvent(1580): CustomButton:dispatchTouchEvent
09-28 02:26:36.926: D/TouchEvent(1580): CustomButton:onTouchEvent
09-28 02:26:36.926: D/TouchEvent(1580): Touch:true
09-28 02:26:37.147: D/TouchEvent(1580): MainActivity:dispatchTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): CustomRelativieLayout:dispatchTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): CustomRelativieLayout:onInterceptTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): CustomLinearLayout:dispatchTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): CustomLinearLayout:onInterceptTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): CustomButton:dispatchTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): CustomButton:onTouchEvent
09-28 02:26:37.147: D/TouchEvent(1580): Touch:true
09-28 02:26:37.147: D/TouchEvent(1580): MainActivity:onClick

  RelativeLayout:調用super.dispatchTouchEvent(),返回false:
 @Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		boolean event = super.dispatchTouchEvent(ev);
		Log.d("TouchEvent", "Touch:"+event);
		return false;
	}
Log日志:
09-28 02:30:20.600: D/TouchEvent(1629): MainActivity:dispatchTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): CustomRelativieLayout:dispatchTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): CustomRelativieLayout:onInterceptTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): CustomLinearLayout:dispatchTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): CustomLinearLayout:onInterceptTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): CustomButton:dispatchTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): CustomButton:onTouchEvent
09-28 02:30:20.600: D/TouchEvent(1629): Touch:true
09-28 02:30:20.600: D/TouchEvent(1629): MainActivity:onTouchEvent
09-28 02:30:20.800: D/TouchEvent(1629): MainActivity:dispatchTouchEvent
09-28 02:30:20.800: D/TouchEvent(1629): MainActivity:onTouchEvent

總結3: 從日志來看,當有調用super.dispatchTouchEvent()並且返回true的情況下,跟我們結論1裡,直接返回super.dispatchTouchEvent()的結果是一樣的。 但當super.dispatchTouchEvent()並且返回false時,事件走到一半就停止了,看到日志的第8行後ANCTION_DOWN(點下去)已經執行完了,而抬起來的事件只走到Activity裡面的dispatchTouchEvent()就再也沒有分發下去。 4)接著onInterceptTouchEvent()返回true,則為攔截事件的分發。 RelativeLayout:onInterceptTouchEvent()返回ture:
@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:onInterceptTouchEvent");
		return true;
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "CustomRelativieLayout:onTouchEvent");
		boolean touch = super.onTouchEvent(event);
		Log.d("TouchEvent", "onToucheEvent:"+touch);
		return super.onTouchEvent(event);
	}
Log日志:
09-28 03:07:40.314: D/TouchEvent(1803): MainActivity:dispatchTouchEvent
09-28 03:07:40.314: D/TouchEvent(1803): CustomRelativieLayout:dispatchTouchEvent
09-28 03:07:40.314: D/TouchEvent(1803): CustomRelativieLayout:onInterceptTouchEvent
09-28 03:07:40.314: D/TouchEvent(1803): CustomRelativieLayout:onTouchEvent
09-28 03:07:40.314: D/TouchEvent(1803): onToucheEvent:false
09-28 03:07:40.314: D/TouchEvent(1803): MainActivity:onTouchEvent
09-28 03:07:40.504: D/TouchEvent(1803): MainActivity:dispatchTouchEvent
09-28 03:07:40.504: D/TouchEvent(1803): MainActivity:onTouchEvent

結4: 這裡,我在RelativeLayout裡面的OnTouchEvent()方法打印了super.onTouchEvent的值,可以看到當為false。當onInterceptTouchEvent()為true時,事件不分發給下一層的子View,而選擇走自己的onToucheEvent()方法,但又是默認的false不處理。導致事件回到上一層的父View中,最終父View的onTouchEvent()也是默認為false,不處理事件。最後導致事件沒有任何View響應,也就沒有消費。   5)在Activity裡,給Button設置OnTouchListener,在onTouch()中返回false,默認不攔截。 MainActivity:onTouch()返回false:默認不攔截
	btn.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Log.d("TouchEvent", "MainActivity:onClick");
			}
		});
		
		btn.setOnTouchListener(new OnTouchListener() {
			
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				Log.d("TouchEvent", "MainActivity:onTouch");
				return false;
			}
		});
RelativeLayout:onInterceptTouchEvent()返回false:不攔截事件
@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
 
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.d("TouchEvent", "CustomRelativieLayout:onInterceptTouchEvent");
		return true;
	}
 
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.d("TouchEvent", "CustomRelativieLayout:onTouchEvent");s
		return super.onTouchEvent(event);
	}
Log日志:
09-28 04:01:34.433: D/TouchEvent(2172): MainActivity:dispatchTouchEvent
09-28 04:01:34.433: D/TouchEvent(2172): CustomRelativieLayout:dispatchTouchEvent
09-28 04:01:34.433: D/TouchEvent(2172): CustomRelativieLayout:onInterceptTouchEvent
09-28 04:01:34.433: D/TouchEvent(2172): CustomLinearLayout:dispatchTouchEvent
09-28 04:01:34.433: D/TouchEvent(2172): CustomLinearLayout:onInterceptTouchEvent
09-28 04:01:34.433: D/TouchEvent(2172): CustomButton:dispatchTouchEvent
09-28 04:01:34.433: D/TouchEvent(2172): MainActivity:onTouch
09-28 04:01:34.433: D/TouchEvent(2172): CustomButton:onTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): MainActivity:dispatchTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): CustomRelativieLayout:dispatchTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): CustomRelativieLayout:onInterceptTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): CustomLinearLayout:dispatchTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): CustomLinearLayout:onInterceptTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): CustomButton:dispatchTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): MainActivity:onTouch
09-28 04:01:34.632: D/TouchEvent(2172): CustomButton:onTouchEvent
09-28 04:01:34.632: D/TouchEvent(2172): MainActivity:onClick

總結5: 從上面的結果來看,事件在傳遞到Button的disptchTouchEvent(),然後回到Activity調用自己的onTouch(),dispatchTouchEvent結束後調用Button自己的onTouchEvent()(第8行),到此按下去的事件已經完成。 緊接著到抬起來的up事件。而執行的Log結果大致上也跟按下去的down事件差不多,但不同的是,up事件最終會在Button自己的OnTouchEvent()中響應,而onTouchEvent()會調用Buttton自己的onClick()方法,最後由onClick方法消費。
6)Button的onTouch()方法返回true MainActivity:onTouchEvent返回true
btn.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				Log.d("TouchEvent", "MainActivity:onTouch");
				return true;
			}
		});
Log日志:
09-28 04:23:15.712: D/TouchEvent(2223): MainActivity:dispatchTouchEvent
09-28 04:23:15.712: D/TouchEvent(2223): CustomRelativieLayout:dispatchTouchEvent
09-28 04:23:15.712: D/TouchEvent(2223): CustomRelativieLayout:onInterceptTouchEvent
09-28 04:23:15.712: D/TouchEvent(2223): CustomLinearLayout:dispatchTouchEvent
09-28 04:23:15.712: D/TouchEvent(2223): CustomLinearLayout:onInterceptTouchEvent
09-28 04:23:15.712: D/TouchEvent(2223): CustomButton:dispatchTouchEvent
09-28 04:23:15.722: D/TouchEvent(2223): MainActivity:onTouch
09-28 04:23:15.882: D/TouchEvent(2223): MainActivity:dispatchTouchEvent
09-28 04:23:15.882: D/TouchEvent(2223): CustomRelativieLayout:dispatchTouchEvent
09-28 04:23:15.882: D/TouchEvent(2223): CustomRelativieLayout:onInterceptTouchEvent
09-28 04:23:15.882: D/TouchEvent(2223): CustomLinearLayout:dispatchTouchEvent
09-28 04:23:15.882: D/TouchEvent(2223): CustomLinearLayout:onInterceptTouchEvent
09-28 04:23:15.882: D/TouchEvent(2223): CustomButton:dispatchTouchEvent
09-28 04:23:15.892: D/TouchEvent(2223): MainActivity:onTouch

總結6: 可以看到onTouch()設置為true,事件到最後也沒傳遞到onClick()裡面,而是由Button的onTouch()給消費了。所以onTouch()方法應該是先於onClick執行的,事件到了onTouch()(返回true)已經被消費了,那麼整個方法都已經返回了,事件就不會進一步傳遞,自然沒有onClcik的事。   到這裡我們差不多可以結合源碼來解析一下我們鎖看到的現象了。   7.dispathTouchEvent()和onTouch()源碼理解   找到View中dispathTouchEvent()的源碼:
public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
        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;
            }
        }
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
        return result;
    }

從上面的代碼24行開始可以看出dispathToychEvent()若能返回true的話都是要在,第24行或是第38行的判斷中返回true,dispathTouchEvent才會為true。在這裡其實可以看到前面的第24行的判斷正是onTouch()方法的響應。而我們onTouch()方法的返回值就是寫在Acivity中的代碼,所以當onTouch()返回true的時候,事件在這裡就已經消費了,而dispathToychEvent()也馬上返回false。   而如果onTouch()返回false則事件會去到View自己的OnTouchEvent()方法裡,那麼onClick方法是怎麼才調用到的呢?接著我們再看源碼。   找到View中dispathTouchEvent()的源碼:
public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        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) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 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();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
                    // 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();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    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;
            }
            return true;
        }
        return false;
    }

代碼很多,我們看到第24行,程序進過一些列的判斷後會進入一個 switch()語句判斷,而裡面的case事件就是我們熟悉的ACTION_DOWN,ACTION_UP,ACTION_MOVE等事件的處理。   而我們的setOnClickListener在哪?Ctrl+F搜索一下,發現下面兩段代碼:
 public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
其中有一個變量mOnClickListener.於是再搜索:
public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

到這裡我們可以知道performClick()方法是在OnTouchEvent()裡面的ACTION_UP調用的。所以Android的事件分發都是這樣進過一層層的View,再通過每個View中的dispatchTouchEvent()和onTouchEvent()裡層層的判斷,最終才會決定誰去消費這個事件。而這些層層的判斷條件我已經寫到圖上去了,在這裡不多做解析,想要繼續深究的童鞋可以到源碼中去找。   這次的淺談就到此為止了,如果有問題的童靴歡迎一起探討,互相學習,共同進步~
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved