Android 和 ChromeOS 提供多種 API,可協助您建構應用程式,為使用者提供卓越的觸控筆體驗。MotionEvent
類別會顯示觸控筆與螢幕互動的相關資訊,包括觸控筆壓力、方向、傾斜度、懸停和手掌偵測資訊。低延遲圖像和動作預測程式庫能讓觸控筆在螢幕上轉譯,帶來有如直接使用紙筆的自然流暢體驗。
MotionEvent
MotionEvent
類別代表使用者輸入內容的互動情形,例如螢幕上觸控點的位置和移動情形。如果是觸控筆的輸入內容,MotionEvent
也會提供壓力、方向、傾斜度和懸停資料。
事件資料
如要存取 MotionEvent
資料,請為元件新增 pointerInput
修飾符:
@Composable
fun Greeting() {
Text(
text = "Hello, Android!", textAlign = TextAlign.Center, style = TextStyle(fontSize = 5.em),
modifier = Modifier
.pointerInput(Unit) {
awaitEachGesture {
while (true) {
val event = awaitPointerEvent()
event.changes.forEach { println(it) }
}
}
},
)
}
MotionEvent
物件提供與 UI 事件以下方面相關的資料:
- 動作:與裝置的實體互動,例如輕觸螢幕、在螢幕表面上移動指標、將指標懸停在螢幕表面上
- 指標:與螢幕互動的物件 ID,包括手指、觸控筆、滑鼠
- 軸:資料類型,包括 x 和 y 座標、壓力、傾斜度、方向和懸停值 (距離)
動作
如要實作觸控筆支援功能,您需要瞭解使用者執行的動作。
MotionEvent
提供多種 ACTION
常數,可用於定義動作事件。觸控筆最重要的動作包括:
動作 | 說明 |
---|---|
ACTION_DOWN ACTION_POINTER_DOWN |
指標已與螢幕接觸。 |
ACTION_MOVE | 指標在畫面上移動。 |
ACTION_UP ACTION_POINTER_UP |
指標不再與螢幕接觸 |
ACTION_CANCEL | 之前或當下的動作集應取消的時機。 |
您的應用程式可以執行以下工作:在 ACTION_DOWN
發生時開始新的筆觸、使用 ACTION_MOVE,
繪製筆觸,以及在觸發 ACTION_UP
時完成筆觸。
針對特定指標,從 ACTION_DOWN
到 ACTION_UP
的一系列 MotionEvent
動作稱為動作集。
指標
大部分的螢幕為多點觸控式:系統會為手指、觸控筆、滑鼠或其他與螢幕互動的物件指派指標。指標索引可讓您取得特定指標的軸資訊,例如第一隻或第二隻手指觸碰的螢幕位置。
指標索引範圍從 0 開始,到 MotionEvent#pointerCount()
傳回的指標數量減去 1。
指標的軸值可使用 getAxisValue(axis,
pointerIndex)
方法存取。省略指標索引時,系統會傳回第一個指標的值,也就是零 (0)。
MotionEvent
物件包含使用中指標類型的相關資訊。您可以疊代指標索引並呼叫 getToolType(pointerIndex)
方法,取得指標類型。
如要進一步瞭解指標,請參閱「處理多點觸控手勢」。
觸控筆輸入
您可以使用 TOOL_TYPE_STYLUS
篩選觸控筆輸入內容:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
您也可以使用 TOOL_TYPE_ERASER
,將觸控筆做為橡皮擦:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
觸控筆的軸資料
ACTION_DOWN
和 ACTION_MOVE
提供觸控筆的軸資料,包括 x 和 y 座標、壓力、方向、傾斜度和懸停距離。
為了讓您能夠存取這項資料,MotionEvent
API 會提供 getAxisValue(int)
,其中參數是下列任一軸 ID:
Axis | getAxisValue() 的傳回值 |
---|---|
AXIS_X |
動作事件的 X 座標。 |
AXIS_Y |
動作事件的 Y 座標。 |
AXIS_PRESSURE |
如果是觸控螢幕或觸控板,則為以手指、觸控筆或其他指標施加的壓力。如果是滑鼠或軌跡球,按下主按鈕的值為 1,否則為 0。 |
AXIS_ORIENTATION |
如果是觸控螢幕或觸控板,則為手指、觸控筆或其他指標相對於裝置垂直面的方向。 |
AXIS_TILT |
觸控筆的傾斜度,以弧度為單位。 |
AXIS_DISTANCE |
觸控筆與螢幕間的距離。 |
例如,MotionEvent.getAxisValue(AXIS_X)
會傳回第一個指標的 x 座標。
另請參閱「處理多點觸控手勢」。
位置
您可以使用下列呼叫擷取指標的 x 和 y 座標:
MotionEvent#getAxisValue(AXIS_X)
或MotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
或MotionEvent#getY()
氣壓
您可以使用 MotionEvent#getAxisValue(AXIS_PRESSURE)
或第一個指標 MotionEvent#getPressure()
擷取指標壓力。
觸控螢幕或觸控板的壓力值介於 0 (無壓力) 和 1 之間,但可以根據螢幕校準傳回較高的值。
方向
方向是指觸控筆指向的方向。
您可以使用 getAxisValue(AXIS_ORIENTATION)
或 getOrientation()
(第一個指標) 擷取指標方向。
關於觸控筆,以順時針傳回的方向值介於 0 到 pi (π) 之間,逆時針傳回的方向值則介於 0 到 -pi 之間,以弧度為單位。
您可以透過方向實作逼真的筆刷體驗。舉例來說,如果觸控筆代表平坦筆刷,則筆刷的寬度取決於觸控筆方向。
垂直運鏡
傾斜度指的是觸控筆相對於螢幕的傾斜度。
傾斜度會傳回觸控筆的正角 (以弧度為單位),其中 0 代表與螢幕垂直,π/2 則代表與螢幕平行。
您可以使用 getAxisValue(AXIS_TILT)
擷取傾斜角度 (第一個指標沒有捷徑)。
傾斜度可用來盡可能重現真實工具的效果,例如使用傾斜的鉛筆來模擬陰影。
懸停
可透過 getAxisValue(AXIS_DISTANCE)
取得觸控筆與螢幕間的距離。這個方法會在觸控筆離開螢幕時,傳回介於 0.0 (與螢幕聯絡) 到較高值的值。觸控筆在螢幕和觸控筆的懸停距離 (Point) 的懸停距離,取決於螢幕和觸控筆的製造商。由於實作方式可能不同,請勿讓應用程式的重要功能仰賴其精確值。
觸控筆懸停功能可用來預覽筆刷大小,或指示即將選取的按鈕。
注意:Compose 提供的修飾符會影響 UI 元素的互動狀態:
hoverable
:將元件設為可透過指標的進入/離開事件,成為可懸停的元件。indication
:在發生互動時為此元件繪製視覺效果。
防止誤觸、導覽及不必要的輸入
有時候,多點觸控螢幕可能會註冊不必要的觸控動作,例如使用者在手寫時將手自然地撐在螢幕上。防止誤觸機制可偵測這種行為,並通知您應取消最後一個 MotionEvent
集。
因此,您必須保留使用者輸入內容的記錄,以便從螢幕上移除不必要的觸控動作,並重新轉譯合理的使用者輸入內容。
ACTION_CANCEL 和 FLAG_CANCELED
ACTION_CANCEL
和 FLAG_CANCELED
都是用來通知您,應從最後一個 ACTION_DOWN
取消先前的 MotionEvent
集,舉例來說,您可以復原繪圖應用程式中特定指標的最後一筆動作。
ACTION_CANCEL
已在 Android 1.0 中新增 (API 級別 1)
ACTION_CANCEL
表示應取消上一組動作事件。
如果偵測到以下任一項目,就會觸發 ACTION_CANCEL
:
- 導覽手勢
- 防止誤觸
觸發 ACTION_CANCEL
時,建議您使用 getPointerId(getActionIndex())
識別使用中的指標。接著,從輸入記錄中移除使用該指標建立的筆觸,然後重新轉譯場景。
FLAG_CANCELED
已在 Android 13 (API 級別 33) 中新增
FLAG_CANCELED
表示指標上移是使用者無意間的觸控動作。這個標記通常是在使用者不小心觸碰螢幕時設定,例如手持裝置或將手掌放在螢幕上時。
您可以透過下列方式存取標記值:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
如果已設定標記,您需要從該指標的最後一個 ACTION_DOWN
中,復原最後一個 MotionEvent
集。
和 ACTION_CANCEL
一樣,您可以透過 getPointerId(actionIndex)
找到指標。
全螢幕、無邊框設計和導覽手勢
如果應用程式為全螢幕模式,且在邊緣附近有可操作的元素 (例如繪圖或筆記應用程式的畫布),只要從螢幕底部滑動即可顯示導覽畫面,或是將應用程式移至背景,可能會造成不必要的觸控動作。
為避免手勢在應用程式中觸發不必要的觸控動作,您可以利用插邊和 ACTION_CANCEL
。
另請參閱「Palm 拒絕、導覽及不必要的輸入」一節。
使用 WindowInsetsController
的 setSystemBarsBehavior()
方法和 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
,可避免導覽手勢造成不必要的觸控事件:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
如要進一步瞭解插邊和手勢管理,請參閱:
低延遲
延遲時間是指硬體、系統和應用程式處理和轉譯使用者輸入內容所需的時間。
延遲時間 = 硬體和 OS 的輸入處理 + 應用程式處理 + 系統組合
- 硬體轉譯
延遲的來源
- 透過觸控螢幕 (硬體) 註冊觸控筆:當觸控筆和 OS 通訊完成註冊和同步時,首次無線連線。
- 觸控取樣率 (硬體):檢查指標是否觸及表面的每秒檢查次數,範圍介於 60 到 1000Hz。
- 輸入處理 (應用程式):針對使用者輸入內容套用色彩、圖形效果及轉換功能。
- 圖形轉譯 (OS + 硬體):緩衝區互換、硬體處理。
低延遲圖形
Jetpack 低延遲圖形程式庫可縮短使用者輸入內容和螢幕算繪之間的處理時間。
程式庫可避免產生多緩衝區轉譯,並運用前端緩衝區轉譯技術 (也就是直接寫入螢幕),藉此縮短處理時間。
前端緩衝區轉譯
前端緩衝區是螢幕用於轉譯的記憶體。此為應用程式最接近直接在螢幕上繪圖的方式。低延遲程式庫可讓應用程式直接轉譯至前端緩衝區。這麼做可藉由防止緩衝區互換來提升效能,緩衝區互換會發生在一般多緩衝區或雙緩衝區轉譯作業 (最常見的情況) 中。
雖然前端緩衝區轉譯是轉譯螢幕較小區域的絕佳技術,但並不適合用來重新整理整個螢幕。透過前端緩衝區轉譯,應用程式會將內容轉譯到螢幕正在讀取的緩衝區。因此可能會產生算繪成品或撕裂 (請見下方說明)。
低延遲程式庫適用於 Android 10 (API 級別 29) 以上版本,以及搭載 Android 10 (API 級別 29) 以上版本的 ChromeOS 裝置。
依附元件
低延遲程式庫提供了用來實作前端緩衝區轉譯的元件。該程式庫會新增為應用程式模組 build.gradle
檔案中的依附元件:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
GLFrontBufferRenderer 回呼
低延遲程式庫包含定義下列方法的 GLFrontBufferRenderer.Callback
介面:
低延遲程式庫不會考量您搭配 GLFrontBufferRenderer
使用的資料類型。
不過,程式庫會將資料處理為包含數百個資料點的資料流。因此,請在設計資料時對記憶體用量與分配進行最佳化調整。
回呼
如要啟用轉譯回呼,請實作 GLFrontBufferedRenderer.Callback
並覆寫 onDrawFrontBufferedLayer()
和 onDrawDoubleBufferedLayer()
。GLFrontBufferedRenderer
會使用回呼,盡可能以最佳方式轉譯資料。
val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> {
override fun onDrawFrontBufferedLayer(
eglManager: EGLManager,
bufferInfo: BufferInfo,
transform: FloatArray,
param: DATA_TYPE
) {
// OpenGL for front buffer, short, affecting small area of the screen.
}
override fun onDrawMultiDoubleBufferedLayer(
eglManager: EGLManager,
bufferInfo: BufferInfo,
transform: FloatArray,
params: Collection<DATA_TYPE>
) {
// OpenGL full scene rendering.
}
}
宣告 GLFrontBufferedRenderer 例項
藉由提供先前建立的 SurfaceView
和回呼,為 GLFrontBufferedRenderer
做好準備。GLFrontBufferedRenderer
會使用回呼,對前端緩衝區和雙緩衝區的轉譯作業進行最佳化調整:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
轉譯
呼叫 renderFrontBufferedLayer()
方法 (觸發 onDrawFrontBufferedLayer()
回呼) 時,系統會啟動前端緩衝區轉譯作業。
呼叫 commit()
函式觸發 onDrawMultiDoubleBufferedLayer()
回呼時,系統會繼續進行雙緩衝區轉譯作業。
在下方範例中,當使用者開始在螢幕上繪圖 (ACTION_DOWN
) 並且移動指標 (ACTION_MOVE
) 時,程序會轉譯至前端緩衝區 (快速轉譯)。當指標離開螢幕表面 (ACTION_UP
) 時,程序會轉譯至雙緩衝區。
您可以使用 requestUnbufferedDispatch()
要求輸入系統不要批次處理動作事件,而是在可用時立即傳送事件:
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
// Deliver input events as soon as they arrive.
view.requestUnbufferedDispatch(motionEvent)
// Pointer is in contact with the screen.
glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
}
MotionEvent.ACTION_MOVE -> {
// Pointer is moving.
glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
}
MotionEvent.ACTION_UP -> {
// Pointer is not in contact in the screen.
glFrontBufferRenderer.commit()
}
MotionEvent.CANCEL -> {
// Cancel front buffer; remove last motion set from the screen.
glFrontBufferRenderer.cancel()
}
}
轉譯的注意事項
在螢幕的一小塊區域中進行手寫、繪圖和素描。
在全螢幕模式下更新、平移及縮放內容。這麼做可能導致畫面撕裂。
撕裂
如果螢幕在重新整理的同時修改螢幕緩衝區,畫面會發生撕裂。螢幕畫面的一部分顯示新資料,另一個部分則顯示舊資料。
動作預測
Jetpack 動作預測程式庫可預估使用者的筆觸路徑,並向轉譯器提供臨時的人為定點,藉此縮短感知延遲時間。
動作預測程式庫會以 MotionEvent
物件形式接收使用者的實際輸入內容。物件中包含 x 和 y 座標、壓力和時間的資訊,可供動態預測程序預測未來的 MotionEvent
物件。
預測的 MotionEvent
物件只是預估值。預測事件可以縮短感知延遲時間,但收到預測的資料後,就必須將其替換為實際的 MotionEvent
資料。
動作預測程式庫適用於 Android 4.4 (API 級別 19) 以上版本,以及搭載 Android 9 (API 級別 28) 以上版本的 ChromeOS 裝置。
依附元件
動作預測程式庫提供預測實作。該程式庫會新增為應用程式模組 build.gradle
檔案中的依附元件:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
導入作業
動作預測程式庫包含了定義下列方法的 MotionEventPredictor
介面:
宣告 MotionEventPredictor
的例項
var motionEventPredictor = MotionEventPredictor.newInstance(view)
為預測程序提供資料
motionEventPredictor.record(motionEvent)
預測
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
動作預測的注意事項
在新增預測點後移除舊的預測點。
請勿使用預測點進行最終算繪。
筆記應用程式
ChromeOS 可宣告應用程式具有某些筆記相關功能。
如要在 ChromeOS 上將應用程式註冊為筆記應用程式,請參閱「輸入相容性」。
如要在 Android 上將應用程式註冊為筆記,請參閱「建立筆記應用程式」。
Android 14 (API 級別 34) 導入了 ACTION_CREATE_NOTE
意圖,可讓應用程式在螢幕鎖定畫面上啟動筆記活動。
透過 ML Kit 辨識數位墨水
有了 ML Kit 數位墨水辨識功能,應用程式就能辨識數位表面上數百種語言的手寫文字。也可以對素描進行分類。
ML Kit 提供 Ink.Stroke.Builder
類別,用來建立 Ink
物件,機器學習模型可藉由處理這些物件,將手寫內容轉換成文字。
除了手寫辨識外,模型還能辨識手勢,例如刪除和畫圓動作。
詳情請參閱「數位墨水辨識」。