Tuesday, January 16, 2018

如何處理觸控事件

文章攢寫時間︰2018/01/16 11:54

本篇參考來源
1. STACKOVERFLOW

文章開始

觸控頁面與元件的關係
When a touch event occurs, first everyone is notified of the event, starting at the Activity and going all the way to the view on top.
Then everyone is given a chance to handle the event, starting with the view on top and going all the way back to the Activity. So the Activity is the first to hear of it and the last to be given a chance to handle it.
當一個觸控事件發生時,每個元件都會在第1時間被通知,從Activity層開始層層往上,通知到最頂層的View。
每個元件在這段被通知的過程中,都有機會來處理這個事件,Activity是第一個聆聽到事件的元件,但是它卻是在最後才被處理到的元件。

If the Activity or some ViewGroup wants to handle the touch event right away (and not give anyone else down the line a chance at it) then it can just return true in its onInterceptTouchEvent().
如果Activity或某些ViewGroup想要立即處理觸控事件(並且不給其它元件任何處理的機會),那麼可以在該元件的onInterceptTouchEvent()中返回true。

If a View (or a ViewGroup) has an OnTouchListener, then the touch event is handled by OnTouchListener.onTouch(). Otherwise it is handled by onTouchEvent(). If onTouchEvent() returns true for any touch event, then the handling stops there. No one else down the line gets a chance at it.
如果一個View(或ViewGroup)想要擁有OnTouchListener事件,基本上該觸控事件是由OnTouchListener.onTouch()來處理,否則則由onTouchEvent()事件來處理。如果onTouchEvent()針對每個觸控事件皆返回true,那麼事件將被處理到該階層而停止,後面剩餘的元件將無法得到任何觸控的機會。

-------------------------------
More detailed explanation
詳細解說

The above diagram makes things a little more simple than they actually are. For example, between the Activity and ViewGroup A (the root layout) there is also the Window and the DecorView. I left them out above because we generally don't have to interact with them. However, I will include them below. The description below follows a touch event through the source code. You can click a link to see the actual source code.
下面的圖例也許能把整件事變得稍微簡單些,舉例來說,在Activity與ViewGroup A(根佈局)之間還有Window與DecorView。我們把它們排除在上面,因為我們一般來說不會與這兩個階層互動。不過,我會在下面的範例中引入它們。下面的描述是觸控事件的程式碼,你可以點擊連結來看源始碼。

1.The Activity's dispatchTouchEvent() is notified of a touch event. The touch event is passed in as a MotionEvent, which contains the x,y coordinates, time, type of event, and other information.
Activity的dispatchTouchEvent()被通知有一個觸控事件,這個觸控事件藉由MotionEvent傳入,過程中包含了x、y的坐標值、時間。事件類型與其它的信息。

2.The touch event is sent to the Window's superDispatchTouchEvent(). Window is an abstract class. The actual implementation is PhoneWindow.
觸發事件發到superDispatchTouchEvent()到Window階層,Window是一件抽象類別,實際實作的是PhoneWindow。

3.The next in line to get the notification is DecorView's superDispatchTouchEvent(). DecorView is what handles the status bar, navigation bar, content area, etc. It is actually just a FrameLayout subclass, which is itself a subclass of ViewGroup.
接下來我們取到的通知是DecorView的superDispatchTouchEvent()。DecorView是處理上方狀態列(status bar) 、下方導覽列(navigation bar)以及內容...等等的東西。它實際上只是一個FrameLayout的子類別,它本身就是ViewGroup的子類。

4.The next one to get the notification (correct me if I'm wrong) is the content view of your activity. That is what you set as the root layout of your activity in xml when you create the layout in the Android Studio's Layout Editor. So whether you choose a RelativeLayout, a LinearLayout, or a ConstraintLayout, they are all subclasses of ViewGroup. And ViewGroup gets notified of the touch event in dispatchTouchEvent(). This is the ViewGroup A in my diagrams above.
下一個得到通知(如果我錯了請糾正我)的是你的Activity裡的content view。這個就是你在Android Studio佈局編輯器裡編輯的、你設為根佈局的layout xml。因此,無論你選擇竹旳是RelativeLayout、LinearLayout、或是ConstraintLayout,它們都隸屬於ViewGroup的子類別。ViewGroup在dispatchTouchEvent()中獲得到觸控事件。這是我上圖裡的ViewGroup A。

5.The ViewGroup will notify any children it has of the touch event, including any ViewGroup children. This is ViewGroup B in my diagrams above.
ViewGroup將會開始通知觸控事件至底下的任何子元件項目,這也包含了ViewGroup的所有子項。在圖例中我以ViewGoup B來做舉例。

6.Anywhere along the way, a ViewGroup can short-circuit the notification process by returning true for onInterceptTouchEvent().
在任何情況下,隸屬於ViewGroup的元件皆可透過onInterceptTouchEvent()回傳true來讓流程"短路"。

7.Assuming no ViewGroup cut the notifications short, the natural end of the line for the notifications is when the View's dispatchTouchEvent() get's called.
假設沒有ViewGroup讓通知流程縮短,那麼通知事件自然結束的點就是在View的dispatchTouchEvent()事件被調用的時候。

8.Now it is time, to start handling the events. If there is an OnTouchListener, then it gets the first chance at handling the touch event with onTouch(). Otherwise, the View's onTouchEvent() gets to handle it.
通知事件的流程到此結束,是時候開始往上處理觸控事件了。如果在View層有一個OnTouchListner,那麼它會在onTouch()中得到第1次的觸控處理的機會。否則會由View的onTouchEvent()來處理觸控事件。

9.Now all the ViewGroups recursively up the line get a chance to handle the touch event in the same way that View did. Although, I didn't indicate this in the diagram above, a ViewGroup is a View subclass, so everything I described about OnTouchListener.onTouch() and onTouchEvent() also applies to ViewGroups.
接下來所有的ViewGroups都像View層一樣,皆以遞迴的方式向上處理觸控事件。儘管我沒有在上圖指出ViewGroup是View的子類別,但我描述有關OnTouchListener.onTouch()和onTouchEvent()也皆適用於ViewGroups。

10.Finally, if no one else wanted it, the Activity also gets the last chance to handle the event with onTouchEvent().
1最後,如果沒有任何的元件需要它,Acitivy也會在onTouchEvent()中得到最後一次的機會來處理觸控事件。

FAQ
1.When would I ever need to override dispatchTouchEvent()?
我何時需要覆寫dispatchTouchEvent()?

答:
Probably you wouldn't need to, unless you need to do some extra routing that doesn't occur by default. To monitor touch event notifications, you can override onInterceptTouchEvent() instead.
也許你不需要這樣做,除非你需要做一些額外的路由,否則默認狀況下這個情況是不會發生的。要監控觸控事件,你可以覆寫onInterceptTouchEvent()即可。

2.When would I ever need to override onInterceptTouchEvent()?
我何時需要覆寫onInterceptTouchEvent()?

答:
If you just want to spy of the touch notifications that are coming in, you can do it here and return false.
如果你只是想監控正在進入的觸控事件,你可以在此時處理並返回false。

However, the main purpose of overriding this method is to let the ViewGroup handle a certain type of touch event while letting the child handle another type. For example, a ScrollView does this to handle scrolling while letting its child handle something like a Button click. Conversely, if the child view doesn't want to let its parent steal its touch event, it can call requestDisallowTouchIntercept().
但是,覆寫這個函式的主要目的是為了讓ViewGroup能處理特定類型的觸控事件,同時讓子元件處理其它類型的事件。舉例來說,ScrollView這樣子實作來處理滾動,同時它的子元件(像是Button)則是處理按鈕點擊事件。相反地,如果子視圖不想要讓父母竊取其觸控事件,則可以調用requestDisallowTouchIntercept()。

3.What are the touch event types?
觸控類型有哪些?

答:
The main ones are 主要的有
    ACTION_DOWN - This is the start of a touch event. You should always return true for the ACTION_DOWN event in onTouchEvent if you want to handle a touch event. Otherwise, you won't get any more events delivered to you.
    ACTION_DOWN - 這是觸控事件的開始,在onTouchEvent()裡ACTION_DOWN的時候你也許永遠要返回true。否則,你不會再收到任何更多的事件。
    ACTION_MOVE - This event is continuously fired as you move your finger across the screen.
    ACTION_MOVE - 當你的手指在螢幕上不斷的滑動時,該事件會不斷的被觸發。
    ACTION_UP - This is the last event of a touch event.
    ACTION_UP - 這是觸控事件的最後一個事件。

A runner up is ACTION_CANCEL. This gets called if a ViewGroup up the tree decides to intercept the touch event.
第二類型的是ACTION_CANCEL,如果ViewGroup樹狀上決定攔截觸控事件,則會收到該事件。

You can view the other kinds of MotionEvents here. Since Android is multi-touch, events are also fired when other fingers ("pointers") touch the screen.
你可以在這裡參考其它類型的MotionEvents,因為Android是多點觸控的,當其它手指("點擊")螢幕時,事件也會被觸發。

延伸閱讀

No comments: