


摘要:有經驗的App Inventor 愛好者希望能夠定制系統,增加自己需要的功能。文章以 App Inventor中的畫布組件為例,分析安卓系統中觸控事件的處理機制,詳細介紹畫布組件多點觸控的實現方法,并實現了縮放手勢和旋轉手勢控制功能,為有意定制 App Inventor的愛好者提供參考。
關鍵詞:App Inventor;畫布;多點觸控;手勢控制
中圖分類號:TP319? ? ? 文獻標識碼:A
文章編號:1009-3044(2022)17-0061-03
1 概述
App Inventor 系統簡單易學,可視化的編程體驗圈定了不少愛好者,他們經過一段時間的使用后發現,“簡單”既是App Inventor 的優勢也是其制約。系統封裝后所提供的配置項很少,無法滿足個性化需要。App Inventor 基于Apache2 協議開源[1],是自由定制的必要前提,國內外有很多不同的定制版。
安卓系統在2.0版本時引入多點觸控,在2.2版本重新設計后成為當前使用的版本。然而 App Inventor 的畫布組件一直不支持多點觸控,在基于畫布組件制作游戲時頗為不便。國內的 WxBit 圖形化編程系統在開源App Inventor的基礎上實現了完整的多點觸控支持,本文分析其中畫布組件實現多點觸控的方法。
2 分析設計
2.1 觸控事件的處理機制
在安卓系統中,用戶與APP的交互主要在Activity中完成。用戶觸碰屏幕時,事件從 Activity到ViewGroup再到View,ViewGroup里面可以嵌套ViewGroup,是一個樹形結構,組件結構示意圖如圖1所示。觸控事件產生后,逐級傳遞,直至被某個View消費,或者被某個ViewGroup攔截消費。簡化的事件處理流程如圖2所示,在調用onTouchEvent之前還會檢查是否應該調用注冊的onTouch事件監聽[2],因未在畫布組件中使用,故省略。
2.2 畫布組件的事件處理機制
畫布(Canvas)是可見組件,繼承自基類 AndroidViewComponent,封裝的 CanvasView是自定義的View。CanvasView覆寫了onTouchEvent (MotionEvent[3-4] event)方法。從上一節的分析可以知道,觸控事件發生在畫布的范圍時,就會進入 onTouchEvent之中處理。為保證不被上層容器攔截,調用requestDisallowInterceptTouchEvent(true)設為不允許攔截。
在MotionEvent 中,存儲著觸控點的信息,每個觸控點都有PointerIndex(ActionIndex)和PointerId,PointerIndex會隨著觸控點的數量變化,而只要觸控點保持與屏幕接觸,PointerId就不會改變。在跟蹤觸控點時,需要通過 PointerIndex獲得PointerId。觸控點對 PointerIndex和PointerId 的影響如表1所示。
3 系統實現
3.1 多點觸控
基于2.2節的分析可以知道,CanvasView 接收到的觸控事件是完整的,畫布組件未支持多點觸控的原因是原有的實現沒有做相對應的處理。當觸控點按下時,CanvasView調用畫布的TouchDown事件;當觸控點持續移動時,調用畫布的Dragged事件;當觸控點松開時,調用畫布的TouchUp事件,之后調用Touched事件。事件的處理時序如圖3所示。在其他組件中,也是一樣的處理流程。以水平布局和垂直布局增加多點觸控支持為例,將Canvas換成HVArrangement,CanvasView 換成HVArrangement中的ViewGroup ,調用ViewGroup的setOnTouchListener設置觸控事件的回調函數。
因為PointerId在接觸屏幕期間不變,可以用作區分觸控事件的標識,變更畫布的觸控事件定義增加參數pointerId,如圖4所示。
畫布的Dragged事件中需要用到起點和前點的坐標,在支持多點觸控的實現場景下,坐標點信息保存在數組中,以PointerId為索引訪問。首先,將用于記錄坐標信息的字段定義由單值改為數組,數組長度設置為MAX_FINGER常量,數組定義如圖5的A區代碼所示。然后,在onTouchEvent中取出觸控點編號,再根據編號取出PointerId。設備支持的觸控點數量與硬件有關,不一定能達到最大值,為保證不出現數組越界異常,PointerId達到最大值時直接忽略,實現如圖5的B區代碼所示。
接下來就是根據觸控類型對事件進行封裝,轉發回畫布組件中,調用圖4定義的代碼塊,具體實現如圖6所示。觸控點按下時進入A區,將坐標點信息保存到數組,觸發畫布和所觸碰精靈組件的TouchDown事件;觸控點移動時進入B區,當移動的距離小于閾值則忽略,否則觸發畫布和所觸碰精靈組件的Dragged事件;觸控點離開屏幕時進入C區,觸發所觸碰精靈組件的Touched和TouchUp事件,如果沒有觸發畫布的Dragged事件,則觸發Touched事件,最后觸發TouchUp事件。
需要注意的是在觸控點移動時,getActionIndex不能獲得觸控點的編號,需要遍歷每個觸摸點,再調用event.getPointerId以獲得對應的PointerId。
3.2 觸控手勢
多點觸控的重要應用就是手勢控制,實現非常簡單。當兩個觸控點靠近或遠離時,判斷為縮放手勢,常用于對圖片的放大和縮小。安卓SDK中提供了縮放手勢的檢測類,在重寫的方法中增加對畫布相應代碼塊的調用即可,具體實現如圖7所示。
當兩個觸控點圍繞某個中心點沿相反的方向移動,判斷為旋轉手勢,常用于對圖片的旋轉移動等場景。使用的是 Almeros 開源的旋轉檢測項目[5],具體的實現類似縮放手勢,如圖8所示。
最后,在畫布組件的構造函數中,將縮放手勢類和旋轉手勢類實例化,放進手勢檢測集合。
4 結束語
本文分析的觸控事件的處理機制,在App Inventor系統中都適用。讀者如需親手實現畫布組件的多點觸控,先要熟悉App Inventor系統的源代碼結構和編譯方法,詳見參考文獻[1]鏈接的README.md文件。其次,還需要對安卓應用開發有一定的了解,能夠查閱SDK文檔。在某些組件(如按鈕及其子類)中注冊了onTouch事件監聽,優先于onTouchEvent運行,為了讓事件能夠繼續傳遞,需要返回 false。盡管實現有些差異,原理與本文所描述并不沖突,故本文有較大的參考價值。
參考文獻:
[1] MIT App Inventor Public Open Source[EB/OL]. [2021-10-26].https://github.com/mit-cml/appinventor-sources.
[2] Input events overview[EB/OL]. [2021-10-26].https://developer.android.com/guide/topics/ui/ui-events.
[3] MotionEvent[EB/OL].[2021-10-26].https://developer.android.com/reference/android/view/MotionEvent.
[4] POWELL A.Making Sense of Multitouch[EB/OL].[2021-10-26].https://developer.android.com/reference/android/view/MotionEvent.
[5] Gesture detector framework for multitouch handling on Android, based on Android's ScaleGestureDetector[EB/OL].[2021-10-26].https://github.com/Almeros/android-gesture-detectors.
收稿日期:2022-03-16
作者簡介:楊道全(1982—),男,廣東雷州人,碩士,研究方向為互聯網應用系統與可視化編程技術。