Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android中事件的分發機制

Android中事件的分發機制

編輯:關於android開發

Android中事件的分發機制


 

Android事件構成

在Android中,事件主要包括點按、長按、拖拽、滑動等,點按又包括單擊和雙擊,另外還包括單指操作和多指操作。所有這些都構成了Android中的事件響應。總的來說,所有的事件都由如下三個部分作為基礎:

按下(ACTION_DOWN)移動(ACTION_MOVE)抬起(ACTION_UP)

所有的操作事件首先必須執行的是按下操作(ACTIONDOWN),之後所有的操作都是以按下操作作為前提,當按下操作完成後,接下來可能是一段移動(ACTIONMOVE)然後抬起(ACTION_UP),或者是按下操作執行完成後沒有移動就直接抬起。這一系列的動作在Android中都可以進行控制。

我們知道,所有的事件操作都發生在觸摸屏上,而在屏幕上與我們交互的就是各種各樣的視圖組件(View),在Android中,所有的視圖都繼承於View,另外通過各種布局組件(ViewGroup)來對View進行布局,ViewGroup也繼承於View。所有的UI控件例如Button、TextView都是繼承於View,而所有的布局控件例如RelativeLayout、容器控件例如ListView都是繼承於ViewGroup。所以,我們的事件操作主要就是發生在View和ViewGroup之間,那麼View和ViewGroup中主要有哪些方法來對這些事件進行響應呢?記住如下3個方法,我們通過查看View和ViewGroup的源碼可以看到:

 

View.java

public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event) 

ViewGroup.java

   
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event) 
public boolean onInterceptTouchEvent(MotionEvent event)

在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中還有一個onInterceptTouchEvent方法,那這些方法都是干嘛的呢?別急,我們先看看他們的返回值。這些方法的返回值全部都是boolean型,為什麼是boolean型呢,看看本文的標題,“事件傳遞”,傳遞的過程就是一個接一個,那到了某一個點後是否要繼續往下傳遞呢?你發現了嗎,“是否”二字就決定了這些方法應該用boolean來作為返回值。沒錯,這些方法都返回true或者是false。在Android中,所有的事件都是從開始經過傳遞到完成事件的消費,這些方法的返回值就決定了某一事件是否是繼續往下傳,還是被攔截了,或是被消費了。

接下來就是這些方法的參數,都接受了一個MotionEvent類型的參數,MotionEvent繼承於InputEvent,用於標記各種動作事件。之前提到的ACTIONDOWN、ACTIONMOVE、ACTION_UP都是MotinEvent中定義的常量。我們通過MotionEvent傳進來的事件類型來判斷接收的是哪一種類型的事件。到現在,這三個方法的返回值和參數你應該都明白了,接下來就解釋一下這三個方法分別在什麼時候處理事件。

dispatchTouchEvent方法用於事件的分發,Android中所有的事件都必須經過這個方法的分發,然後決定是自身消費當前事件還是繼續往下分發給子控件處理。返回true表示不繼續分發,事件沒有被消費。返回false則繼續往下分發,如果是ViewGroup則分發給onInterceptTouchEvent進行判斷是否攔截該事件。onTouchEvent方法用於事件的處理,返回true表示消費處理當前事件,返回false則不處理,交給子控件進行繼續分發。onInterceptTouchEvent是ViewGroup中才有的方法,View中沒有,它的作用是負責事件的攔截,返回true的時候表示攔截當前事件,不繼續往下分發,交給自身的onTouchEvent進行處理。返回false則不攔截,繼續往下傳。這是ViewGroup特有的方法,因為ViewGroup中可能還有子View,而在Android中View中是不能再包含子View的。

到目前為止,Android中事件的構成以及事件處理方法的作用你應該比較清楚了,接下來我們就通過一個Demo來實際體驗實驗一下。

Android事件處理

首先在Eclipse新建一個工程,並新建一個類RTButton繼承Button,用來實現我們對按鈕事件的跟蹤。

RTButton.java

 

public class RTButton extends Button {
	public RTButton(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			System.out.println("RTButton---dispatchTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			System.out.println("RTButton---dispatchTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			System.out.println("RTButton---dispatchTouchEvent---UP");
			break;
		default:
			break;
		}
		return super.dispatchTouchEvent(event);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			System.out.println("RTButton---onTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			System.out.println("RTButton---onTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			System.out.println("RTButton---onTouchEvent---UP");
			break;
		default:
			break;
		}
		return super.onTouchEvent(event);
	}
}

 

在RTButton中我重寫了dispatchTouchEvent和onTouchEvent方法,並獲取了MotionEvent各個事件狀態,打印輸出了每一個狀態下的信息。然後在activity_main.xml中直接在根布局下放入自定義的按鈕RTButton。

activity_main.xml


    
    

接下來在Activity中為RTButton設置onTouch和onClick的監聽器來跟蹤事件傳遞的過程,另外,Activity中也有一個dispatchTouchEvent方法和一個onTouchEvent方法,我們也重寫他們並輸出打印信息。

MainActivity.java

public class MainActivity extends Activity {
	private RTButton button;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		button = (RTButton)this.findViewById(R.id.btn);
		button.setOnTouchListener(new OnTouchListener() {
			
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					System.out.println("RTButton---onTouch---DOWN");
					break;
				case MotionEvent.ACTION_MOVE:
					System.out.println("RTButton---onTouch---MOVE");
					break;
				case MotionEvent.ACTION_UP:
					System.out.println("RTButton---onTouch---UP");
					break;
				default:
					break;
				}
				return false;
			}
		});
		
		button.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				System.out.println("RTButton clicked!");
			}
		});
		
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
		  System.out.println("Activity---dispatchTouchEvent---DOWN");
		  break;
		case MotionEvent.ACTION_MOVE:
	          System.out.println("Activity---dispatchTouchEvent---MOVE");
		  break;
		case MotionEvent.ACTION_UP:
		  System.out.println("Activity---dispatchTouchEvent---UP");
		  break;
		default:
		  break;
		}
		return super.dispatchTouchEvent(event);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
		  System.out.println("Activity---onTouchEvent---DOWN");
		  break;
		case MotionEvent.ACTION_MOVE:
		  System.out.println("Activity---onTouchEvent---MOVE");
		  break;
		case MotionEvent.ACTION_UP:
		  System.out.println("Activity---onTouchEvent---UP");
		  break;
		default:
		  break;
		}
		return super.onTouchEvent(event);
	}
}

代碼部分已經完成了,接下來運行工程,並點擊按鈕,查看日志輸出信息,我們可以看到如下結果:

\

通過日志輸出可以看到,首先執行了Activity的dispatchTouchEvent方法進行事件分發,在MainActivity.java代碼第55行,dispatchTouchEvent方法的返回值是super.dispatchTouchEvent(event),因此調用了父類方法,我們進入Activity.java的源碼中看看具體實現。

Activity.java

	/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     * 
     * @param ev The touch screen event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    

從源碼中可以看到,dispatchTouchEvent方法只處理了ACTIONDOWN事件,前面提到過,所有的事件都是以按下為起點的,所以,Android認為當ACTIONDOWN事件沒有執行時,後面的事件都是沒有意義的,所以這裡首先判斷ACTION_DOWN事件。如果事件成立,則調用了onUserInteraction方法,該方法可以在Activity中被重寫,在事件被分發前會調用該方法。該方法的返回值是void型,不會對事件傳遞結果造成影響,接著會判斷getWindow().superDispatchTouchEvent(ev)的執行結果,看看它的源碼:

Activity.java

    /**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

通過源碼注釋我們可以了解到這是個抽象方法,用於自定義的Window,例如自定義Dialog傳遞觸屏事件,並且提到開發者不需要去實現或調用該方法,系統會完成,如果我們在MainActivity中將dispatchTouchEvent方法的返回值設為true,那麼這裡的執行結果就為true,從而不會返回執行onTouchEvent(ev),如果這裡返回false,那麼最終會返回執行onTouchEvent方法,由此可知,接下來要調用的就是onTouchEvent方法了。別急,通過日志輸出信息可以看到,ACTION_DOWN事件從Activity被分發到了RTButton,接著執行了onTouch和onTouchEvent方法,為什麼先執行onTouch方法呢?我們到RTButton中的dispatchTouchEvent看看View中的源碼是如何處理的。

View.java

	/**
     * 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 (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags &
 ENABLED_MASK) == ENABLED  && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

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

挑選關鍵代碼進行分析,可以看代碼第16行,這裡有幾個條件,當幾個條件都滿足時該方法就返回true,當條件li.mOnTouchListener不為空時,通過在源碼中查找,發現mOnTouchListener是在以下方法中進行設置的。

View.java

	/**
     * Register a callback to be invoked when a touch event is sent to this view.
     * @param l the touch listener to attach to this view
     */
    public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

這個方法就已經很熟悉了,就是我們在MainActivity.java中為RTButton設置的onTouchListener,條件(mViewFlags & ENABLED_MASK) == ENABLED判斷的是當前View是否是ENABLE的,默認都是ENABLE狀態的。接著就是li.mOnTouchListener.onTouch(this, event)條件,這裡調用了onTouch方法,該方法的調用就是我們在MainActivity.java中為RTButton設置的監聽回調,如果該方法返回true,則整個條件都滿足,dispatchTouchEvent就返回true,表示該事件就不繼續向下分發了,因為已經被onTouch消費了。

如果onTouch返回的是false,則這個判斷條件不成立,接著執行onTouchEvent(event)方法進行判斷,如果該方法返回true,表示事件被onTouchEvent處理了,則整個dispatchTouchEvent就返回true。到這裡,我們就可以回答之前提出的“為什麼先執行onTouch方法”的問題了。到目前為止,ACTIONDOWN的事件經過了從Activity到RTButton的分發,然後經過onTouch和onTouchEvent的處理,最終,ACTIONDOWN事件交給了RTButton得onTouchEvent進行處理。

當我們的手(我這裡用的Genymotion然後用鼠標進行的操作,用手的話可能會執行一些ACTIONMOVE操作)從屏幕抬起時,會發生ACTIONUP事件。從之前輸出的日志信心中可以看到,ACTIONUP事件同樣從Activity開始到RTButton進行分發和處理,最後,由於我們注冊了onClick事件,當onTouchEvent執行完畢後,就調用了onClick事件,那麼onClick是在哪裡被調用的呢?繼續回到View.java的源代碼中尋找。由於onTouchEvent在View.java中的源碼比較長,這裡就不貼出來了,感興趣的可以自己去研究一下,通過源碼閱讀,我們在ACTIONUP的處理分支中可以看到一個performClick()方法,從這個方法的源碼中可以看到執行了哪些操作。

View.java

	/**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    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;
    }

在if分支裡可以看到執行了li.mOnClickListener.onClick(this);這句代碼,這裡就執行了我們為RTButton實現的onClick方法,所以,到目前為止,可以回答前一個“onClick是在哪裡被調用的呢?”的問題了,onClick是在onTouchEvent中被執行的,並且,onClick要後於onTouch的執行。

到此,點擊按鈕的事件傳遞就結束了,我們結合源代碼窺探了其中的執行細節,如果我們修改各個事件控制方法的返回值又會發生什麼情況呢,帶著這個問題,進入下一節的討論。

Android事件攔截

從上一節分析中,我們知道了在Android中存在哪些事件類型,事件的傳遞過程以及在源碼中對應哪些處理方法。我們可以知道在Android中,事件是通過層級傳遞的,一次事件傳遞對應一個完整的層級關系,例如上節中分析的ACTIONDOWN事件從Activity傳遞到RTButton,ACTIONUP事件也同樣。結合源碼分析各個事件處理的方法,也可以明確看到事件的處理流程。

之前提過,所有事件處理方法的返回值都是boolean類型的,現在我們來修改這個返回值,首先從Activity開始,根據之前的日志輸出結果,首先執行的是Activity的dispatchTouchEvent方法,現在將之前的返回值super.dispatchTouchEvent(event)修改為true,然後重新編譯運行並點擊按鈕,看到如下的日志輸出結果。

\

可以看到,事件執行到dispatchTouchEvent方法就沒有再繼續往下分發了,這也驗證了之前的說法,返回true時,不再繼續往下分發,從之前分析過的Activity的dispatchTouchEvent源碼中也可知,當返回true時,就沒有去執行onTouchEvent方法了。

接著,將上述修改還原,讓事件在Activity這繼續往下分發,接著就分發到了RTButton,將RTButton的dispatchTouchEvent方法的返回值修改為true,重新編譯運行並查看輸出日志結果。

\

從結果可以看到,事件在RTButton的dispatchTouchEvent方法中就沒有再繼續往下分發了。接著將上述修改還原,將RTButton的onTouchEvent方法返回值修改為true,讓其消費事件,根據之前的分析,onClick方法是在onTouchEvent方法中被調用的,事件在這被消費後將不會調用onClick方法了,編譯運行,得到如下日志輸出結果。

\

跟分析結果一樣,onClick方法並沒有被執行,因為事件在RTButton的onTouchEvent方法中被消費了。下圖是整個事件傳遞的流程圖。

\

到目前為止,Android中的事件攔截機制就分析完了。但這裡我們只討論了單布局結構下單控件的情況,如果是嵌套布局,那情況又是怎樣的呢?接下來我們就在嵌套布局的情況下對Android的事件傳遞機制進行進一步的探究和分析。

Android嵌套布局事件傳遞

首先,新建一個類RTLayout繼承於LinearLayout,同樣重寫dispatchTouchEvent和onTouchEvent方法,另外,還需要重寫onInterceptTouchEvent方法,在文章開頭介紹過,這個方法只有在ViewGroup和其子類中才存在,作用是控制是否需要攔截事件。這裡不要和dispatchTouchEvent弄混淆了,後者是控制對事件的分發,並且後者要先執行。

那麼,事件是先傳遞到View呢,還是先傳遞到ViewGroup的?通過下面的分析我們可以得出結論。首先,我們需要對工程代碼進行一些修改。

RTLayout.java

public class RTLayout extends LinearLayout {
	public RTLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
		  System.out.println("RTLayout---dispatchTouchEvent---DOWN");
		  break;
		case MotionEvent.ACTION_MOVE:
		  System.out.println("RTLayout---dispatchTouchEvent---MOVE");
		  break;
		case MotionEvent.ACTION_UP:
		  System.out.println("RTLayout---dispatchTouchEvent---UP");
		  break;
		default:
		  break;
		}
		return super.dispatchTouchEvent(event);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			System.out.println("RTLayout---onInterceptTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			System.out.println("RTLayout---onInterceptTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			System.out.println("RTLayout---onInterceptTouchEvent---UP");
			break;
		default:
			break;
		}
		return super.onInterceptTouchEvent(event);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			System.out.println("RTLayout---onTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			System.out.println("RTLayout---onTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			System.out.println("RTLayout---onTouchEvent---UP");
			break;
		default:
			break;
		}
		return super.onTouchEvent(event);
	}
}

同時,在布局文件中為RTButton添加一個父布局,指明為自定義的RTLayout,修改後的布局文件如下。

activity_main.xml



    

        
    

最後,我們在Activity中也為RTLayout設置onTouch和onClick事件,在MainActivity中添加如下代碼。

MainActivity.java

	rtLayout.setOnTouchListener(new OnTouchListener() {
			
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					System.out.println("RTLayout---onTouch---DOWN");
					break;
				case MotionEvent.ACTION_MOVE:
					System.out.println("RTLayout---onTouch---MOVE");
					break;
				case MotionEvent.ACTION_UP:
					System.out.println("RTLayout---onTouch---UP");
					break;
				default:
					break;
				}
				return false;
			}
		});
		
	rtLayout.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				System.out.println("RTLayout clicked!");
			}
		});

代碼修改完畢後,編譯運行工程,同樣,點擊按鈕,查看日志輸出結果如下:

\

從日志輸出結果我們可以看到,嵌套了RTLayout以後,事件傳遞的順序變成了Activity->RTLayout->RTButton,這也就回答了前面提出的問題,Android中事件傳遞是從ViewGroup傳遞到View的,而不是反過來傳遞的。

從輸出結果第三行可以看到,執行了RTLayout的onInterceptTouchEvent方法,該方法的作用就是判斷是否需要攔截事件,我們到ViewGroup的源碼中看看該方法的實現。

ViewGroup.java

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

該方法的實現很簡單,只返回了一個false。那麼這個方法是在哪被調用的呢,通過日志輸出分析可知它是在RTLayout的dispatchTouchEvent執行後執行的,那我們就進到dispatchTouchEvent源碼裡面去看看。由於源碼比較長,我將其中的關鍵部分截取出來做解釋說明。

ViewGroup.java

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

從這部分代碼中可以看到onInterceptTouchEvent調用後返回值被賦值給intercepted,該變量控制了事件是否要向其子控件分發,所以它起到攔截的作用,如果onInterceptTouchEvent返回false則不攔截,如果返回true則攔截當前事件。我們現在將RTLayout中的該方法返回值修改為true,並重新編譯運行,然後點擊按鈕,查看輸出結果如下。

\

可以看到,我們明明點擊的按鈕,但輸出結果顯示RTLayout點擊事件被執行了,再通過輸出結果分析,對比上次的輸出結果,發現本次的輸出結果完全沒有RTButton的信息,沒錯,由於onInterceptTouchEvent方法我們返回了true,在這裡就將事件攔截了,所以他不會繼續分發給RTButton了,反而交給自身的onTouchEvent方法執行了,理所當然,最後執行的就是RTLayout的點擊事件了。

總結

以上我們對Android事件傳遞機制進行了分析,期間結合系統源碼對事件傳遞過程中的處理情況進行了探究。通過單布局情況和嵌套布局情況下的事件傳遞和處理進行了分析,現總結如下:

Android中事件傳遞按照從上到下進行層級傳遞,事件處理從Activity開始到ViewGroup再到View。事件傳遞方法包括dispatchTouchEventonTouchEventonInterceptTouchEvent,其中前兩個是View和ViewGroup都有的,最後一個是只有ViewGroup才有的方法。這三個方法的作用分別是負責事件分發、事件處理、事件攔截。onTouch事件要先於onClick事件執行,onTouch在事件分發方法dispatchTouchEvent中調用,而onClick在事件處理方法onTouchEvent中被調用,onTouchEvent要後於dispatchTouchEvent方法的調用。  

1.Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承於View。

2.ViewGroup和View組成了一個樹狀結構,根節點為Activity內部包含的一個ViwGroup。

3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,可以為0個。

4.當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷可以看成是遞歸的。分發的目的是為了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結果返回true。

5.當某個子View返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。由於子View是保存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup保存的會是真實處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會返回true,被保存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。

6.當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch事件。觸發的方式是調用super.dispatchTouchEvent函數,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發。2.中止Up和Move事件向目標View傳遞,使得目標View所在的ViewGroup捕獲Up和Move事件。

 

補充:

 

Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能夠響應這些方法的控件包括:ViewGroup 及其子類、Activity。方法與控件的對應關系如下表所示:

Touch 事件相關方法 方法功能 ViewGroup Activity public boolean dispatchTouchEvent(MotionEvent ev) 事件分發 Yes Yes public boolean onInterceptTouchEvent(MotionEvent ev) 事件攔截 Yes No public boolean onTouchEvent(MotionEvent ev) 事件響應 Yes Yes

從這張表中我們可以看到 ViewGroup 及其子類對與 Touch 事件相關的三個方法均能響應,而 Activity 對onInterceptTouchEvent(MotionEvent ev)也就是事件攔截不進行響應。另外需要注意的是 View 對dispatchTouchEvent(MotionEvent ev)和onInterceptTouchEvent(MotionEvent ev)的響應的前提是可以向該 View 中添加子 View,如果當前的 View 已經是一個最小的單元 View(比如 TextView),那麼就無法向這個最小 View 中添加子 View,也就無法向子 View 進行事件的分發和攔截,所以它沒有dispatchTouchEvent(MotionEvent ev)和onInterceptTouchEvent(MotionEvent ev),只有onTouchEvent(MotionEvent ev)。


 

?事件分發:public boolean dispatchTouchEvent(MotionEvent ev)

Touch 事件發生時Activity 的dispatchTouchEvent(MotionEvent ev) 方法會以隧道方式(從根元素依次往下傳遞直到最內層子元素或在中間某一元素中由於某一條件停止傳遞)將事件傳遞給最外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法,並由該 View 的 dispatchTouchEvent(MotionEvent ev) 方法對事件進行分發。dispatchTouchEvent的事件分發邏輯如下:

  • 如果return true,事件會分發給當前 View 並由 dispatchTouchEvent 方法進行消費,同時事件會停止向下傳遞;
  • 如果return false,事件分發分為兩種情況:
    1. 如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給 Activity 的 onTouchEvent 進行消費;
    2. 如果當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的 onTouchEvent 進行消費。
      • 如果返回系統默認的super.dispatchTouchEvent(ev),事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。

        ?事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)

        在外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系統默認的 super.dispatchTouchEvent(ev) 情況下,事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent的事件攔截邏輯如下:

        • 如果 onInterceptTouchEvent 返回true,則表示將事件進行攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理;
        • 如果 onInterceptTouchEvent 返回false,則表示將事件放行,當前 View 上的事件會被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發;
        • 如果 onInterceptTouchEvent 返回super.onInterceptTouchEvent(ev),事件默認會被攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理。

          ?事件響應:public boolean onTouchEvent(MotionEvent ev)

          在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 並且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情況下 onTouchEvent 會被調用。onTouchEvent的事件響應邏輯如下:

          • 如果事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回了 false,那麼這個事件會從當前 View 向上傳遞,並且都是由上層 View 的 onTouchEvent 來接收,如果傳遞到上面的 onTouchEvent 也返回 false,這個事件就會“消失”,而且接收不到下一次事件。
          • 如果返回了 true 則會接收並消費該事件。
          • 如果返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。

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