最近左腦用過度,來寫一篇文章來平衡一下。順便灌一下水。
之前在看到iOS跟Android 都有Gesture相關的API就一直很想來分析一下這兩個設計。
先來粗略地介紹一下各自的機制
iOS
iOS是從3.2,也就是iPad才開始有Gesture相關的API。
大致上的架構如圖
UIGestureRecognizer負責辨識Gesture。你可以依你的需求安裝一個或多個UIGestureRecognizer到目標UIView上(準備接收gesture的view)。當生成UIGestureRecognizer時,你得指定一組target跟action給UIGestureRecognizer,你也可以透過- (void)addTarget:(id)target action:(SEL)action或- (void)removeTarget:(id)target action:(SEL)action增加或移除額外的target跟action。當gesture被辨識出來的時候,這些target跟action就會被觸發。
你也可以透過delegate(UIGestureRecognizerDelegate)的設計來微調部份行為。
目前iOS有支援的recognizer如下
- UITapGestureRecognizer
- UIPinchGestureRecognizer
- UIPanGestureRecognizer
- UISwipeGestureRecognizer
- UIRotationGestureRecognizer
- UILongPressGestureRecognizer
Android
再來看看Android,Android打從一開始(Level 1)就有Gesture相關的API了。
大致上的架構如圖
GestureDetector負責辨識Gesture(單純從介面分析,內部實作尚未查正,也不在本文所要分析的範圍 :P)。你得把收到的touch event 傳遞給GestureDetector,當有特定的Gesture被辨識出來時,相對應的listener的method就會被呼叫到。目前Android有定義兩組listener,分別是GestureDetector.OnGestureListener跟GestureDetector.OnDoubleTapListener(從Leve 3,Android 1.5開始才有)。你可以挑你需要的interface實作,你也可以繼承SimpleOnGestureListener。繼承SimpleOnGestureListener比較方便,因為你可以只要挑你要的gesture去override對應的method,如果是實作listener interface的話你就得實作所有的method,不管你關不關心某些gesture。
目前Android能辨識的Gesture如下
- onDown
- onFling
- onLongPress
- onScroll
- onShowPress
- onSingleTapUp
- onDoubleTap (Since Level 3)
- onSingleTapConfirmed (SinceLevel 3)
接下來就來分析一下各自的優缺點(主觀地)
便利性
基本上兩個在使用上都算是滿容易的,只要生成一些辨識用物件跟實作一些Callback即可。
能力
就能辨識出的Gesture種類與多寡而言,iOS似乎勝出。原因如下:
iOS 針對Tap的設計很優,UITapGestureRecognizer可以指定你想辨識的touch的個數配上tap的次數。舉個例子:如果你想辨識使用者是否用了三根手指在你的view上tap了4次,你只要生成一個UITapGestureRecognizer然後將numberOfTouchesRequired設成3,numberOfTapsRequired設成4即可以達成,類似的設計也在UISwipeGestureRecognizer,UILongPressGestureRecognizer等都可以看到。然而Android只有onDown, onDoubleTap等幾個常見的Gesture而已。或許現實世界用不到太多Tap相關的Gesture,但單就設計來看,iOS提供了較大的彈性。
另外iOS有必殺的UIPinchGestureRecognizer,Android貌似因為專利的關係而無法提供pinch的detector。
貼心程度
貼心程度我就要給Android一票了。以往如果一個view同時要處理single tap跟double tap的時候,你得需要一些技法來處理event(利用timer或類似的方法),因為single tap(第一個tap)一定比double tap(第二個tap)來得早發生,所以確定是single tap的時機一定是在第一個tap發生後的一段時間內(決定double tap的間隔)沒有其他tap event發生的時候。而Android已經幫我們處理,取而代之的是我們只要實作onSingleTapConfirmed就好。
擴充性
iOS又獲勝了(I'm a huge fan of Apple)。iOS的UIGestureRecognizer是abstract class,也就是說我們可以實作自己的UIGestureRecognizer融入整個辨識系統內。然而Android整個辨識的機制被GestureDetector給封裝起來了,也沒開放任何介面讓我們實作其他的演算法,當然我們也可以自己去收touch event然後辨識,但是這就不是在原先的架構/設計中了。
效能
你認為呢?當然又是iOS獲勝。因為在iOS,你可以挑你感興趣的Gesture去安裝相應的Recognizer。但是在Android,我們幾乎沒有選擇,幾乎所有gesture同時在GestureDetector中被辨識著。所以我判斷iOS會有比較好的效能。
其他
iOS的UIGestureRecognizer可以註冊多個target(to-many)而Android的GestureDetector的listener是to-one的關係,所以iOS在這方面提供了比較大的彈性。當然有人會說,我可以生成多個GestureDetector給不同的listener(target),但是我會"掐冷機"地回說
"這樣的話,效率上會輸給iOS"(怎樣,我就是愛iOS)
補充與更正
Android從level 8,也就是2.2後還有ScaleGestureDetector可以用,貌似是pinch,還沒試過,不知道:P
有心~~
回覆刪除有心~~
回覆刪除有心~~
回覆刪除很好看
回覆刪除三Q~
回覆刪除補充與更正
回覆刪除Android從level 8,也就是2.2後還有ScaleGestureDetector可以用,貌似是pinch,還沒試過,不知道:P
最近在研究觀察者模式(Observer pattern)時有個concern:如果observer需要在update()時處理lengthy job,很容易拖到別人收update()的時機。
回覆刪除看到James這篇gusture detector,我查了一下Android GestureDetector,發現它提供了一個解法:
在建構的時候可以傳自己的background thread's Handler進去,GestureDetector有事通知你的時候就會用你的background thread message loop,而不會耽擱了UI thread的進行。
有空來寫個測試程式再跟大家update!
對耶!之前都沒注意到Handler這個parameter,不過很好奇listener跟handler這兩個參數是怎麼搭配使用的?
回覆刪除殘念..
回覆刪除經過實驗證實,Handler參數只有在callback為以下幾個時有效:
OnGestureListener::onShowPress
OnGestureListener::onLongPress
OnDoubleTapListener::onSingleTapConfirmed
使用方法為:
thread = new HandlerThread();
thread.start();
handler = new Handler(thread.getLoooper());
detector = new GestureDetector(listener, handler);
當上述幾個callback時,就會是在thread執行(而不是main thread)
請參考GestureDetector.java line 258 - 277. 不知道為什麼這個Handler參數只在上述情況使用。
當然在OnGestureListener要處理lengthy job還是可以自己sendMessage給background thread handler,這樣UI thread/message就不會被干擾了。只是不太能理解GestureDetector第三個參數是沒有實作完整,還是有其他的理由..
回覆刪除第三個參數會不會只是讓我們包一些沒考慮到的參數 像是struct 包lparam wparam 之類的 (不懂還想硬參與XD)
回覆刪除感謝阿伯的觀點:)
回覆刪除額外參數可以利用繼承GestureDetector之後組合進去新的class。
Handler參數可以確定是為了避免block main thread message queue,但奇怪的是它又只有在幾個特定gesture callback使用..
在我的實驗中,有個TextView利用post message到UI thread不斷更新文字內容,模擬馬錶。又有一個ImageView,在接收長按callback後會透過網路抓一張圖下來。
如果沒有利用Handler參數,在ImageView抓網路照片的過程中,馬錶會停掉(因為update message queue住了)
解法就是create background thread/handler,把這個handler傳給GestureDetector。
但如果我想在onSingleTapConfirmed時下載照片,它就沒有利用我傳給它的handler,而造成馬錶blocking。
解法則是在onSingleTapConfirmed時send message給那個handler,然後在Handler::onHandleMessage中做照片下載的動作。