2011年1月9日 星期日

跟著我動動你的右腦(其實也會動到左腦,甚至會死很多腦細胞 XD)


最近左腦用過度,來寫一篇文章來平衡一下。順便灌一下水
之前在看到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

12 則留言:

  1. 補充與更正
    Android從level 8,也就是2.2後還有ScaleGestureDetector可以用,貌似是pinch,還沒試過,不知道:P

    回覆刪除
  2. 最近在研究觀察者模式(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!

    回覆刪除
  3. 對耶!之前都沒注意到Handler這個parameter,不過很好奇listener跟handler這兩個參數是怎麼搭配使用的?

    回覆刪除
  4. 殘念..
    經過實驗證實,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參數只在上述情況使用。

    回覆刪除
  5. 當然在OnGestureListener要處理lengthy job還是可以自己sendMessage給background thread handler,這樣UI thread/message就不會被干擾了。只是不太能理解GestureDetector第三個參數是沒有實作完整,還是有其他的理由..

    回覆刪除
  6. 第三個參數會不會只是讓我們包一些沒考慮到的參數 像是struct 包lparam wparam 之類的 (不懂還想硬參與XD)

    回覆刪除
  7. 感謝阿伯的觀點:)

    額外參數可以利用繼承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中做照片下載的動作。

    回覆刪除