Android 和 ChromeOS 提供多種 API,可協助您建構提供
絕佳的觸控筆體驗
MotionEvent
類別會公開
觸控筆與螢幕互動的相關資訊,包括觸控筆壓力
方向、傾斜度、懸停和手掌偵測。低延遲圖像和動態
預測程式庫能強化觸控筆在螢幕上算繪的功能
就像使用紙筆一樣自然
MotionEvent
MotionEvent
類別代表使用者輸入內容的互動情形,例如位置
以及畫面上的觸控指標移動如為觸控筆輸入,請MotionEvent
也會提供壓力、方向、傾斜度和懸停資料。
事件資料
如要存取觸控筆 MotionEvent
物件,請將 pointerInteropFilter
修飾符新增至繪圖介面。實作 ViewModel
類別,並使用處理動作事件的方法;將方法做為 pointerInteropFilter
修飾符的 onTouchEvent
lambda 傳遞:
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun DrawArea(modifier: Modifier = Modifier) {
Canvas(modifier = modifier
.clipToBounds()
.pointerInteropFilter {
viewModel.processMotionEvent(it)
}
) {
// Drawing code here.
}
}
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 和 y 座標:
MotionEvent#getAxisValue(AXIS_X)
或MotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
或MotionEvent#getY()
![畫面上的觸控筆繪圖與對應的 x 和 y 座標。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_x_y_coordinates.png?authuser=1&hl=zh-tw)
氣壓
您可以使用
MotionEvent#getAxisValue(AXIS_PRESSURE)
或針對第一個指標
MotionEvent#getPressure()
。
觸控螢幕或觸控板的壓力值介於 0 (否) 壓力) 和 1 之間,但可能會傳回更高的值 (視螢幕而定) 校正。
![代表從低到高持續施壓的觸控筆觸。左側筆觸較窄且顏色較淡,表示筆觸壓力較低。筆觸由左到右變寬且顏色變深,至畫面最右側達到最寬且顏色最深的筆觸,表示壓力最高。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_pressure.png?authuser=1&hl=zh-tw)
方向
方向是指觸控筆指向的方向。
您可以使用 getAxisValue(AXIS_ORIENTATION)
或
getOrientation()
(用於第一個指標)。
針對觸控筆,系統會以弧度值傳回 0 到 pi (π) 的弧度值 順時針或逆時針方向,或 0 到 -pi。
您可以透過方向實作逼真的筆刷體驗。舉例來說, 觸控筆代表平坦筆刷,筆刷的寬度則取決於 觸控筆方向
![](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_orientation.png?authuser=1&hl=zh-tw)
垂直運鏡
傾斜度指的是觸控筆相對於螢幕的傾斜度。
傾斜度會傳回觸控筆的正角 (以弧度為單位),其中 0 是 垂直方向與 π/2 垂直。
您可以使用 getAxisValue(AXIS_TILT)
擷取傾斜度 (
第一個指標)。
傾斜度可用來盡可能重現真實工具,例如 使用傾斜的鉛筆來模擬陰影。
![觸控筆從螢幕表面傾斜約 40 度。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_tilt.png?authuser=1&hl=zh-tw)
懸停
可透過以下方式取得觸控筆與螢幕間的距離:
getAxisValue(AXIS_DISTANCE)
。此方法會傳回 0.0 (與
螢幕),並在觸控筆離開螢幕時調出更高的值。懸停
螢幕和觸控筆的六邊形 (point) 之間的距離取決於
同時具備螢幕和觸控筆的製造商資訊由於導入方式可能
不同,請勿仰賴精確的值來提供應用程式的重要功能。
觸控筆懸停功能可用來預覽筆刷大小,或表示 按鈕。
![](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_hover.png?authuser=1&hl=zh-tw)
注意:Compose 提供的修飾符會影響 UI 元素的互動狀態:
hoverable
:將元件設為可透過指標的進入/離開事件,成為可懸停的元件。indication
:在發生互動時為此元件繪製視覺效果。
防止誤觸、導覽及不必要的輸入
多點觸控螢幕有時可能會註冊不必要的觸控動作,例如
使用者在手寫時自然會把手放在螢幕上,以便尋求支援。
防止誤觸機制可偵測這種行為,並通知您
最後一個 MotionEvent
集應取消
因此,您必須保留使用者輸入內容的記錄,以免不必要的觸控行為 就可以從畫面上移除正確的使用者輸入內容 重新轉譯。
ACTION_CANCEL 和 FLAG_CANCELED
ACTION_CANCEL
和
FLAG_CANCELED
是
這兩種 ID 都會用來通知您,先前的 MotionEvent
組合應該
取消了上一個 ACTION_DOWN
,因此您可以復原這項操作
特定指標的繪圖應用程式的筆觸。
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
如果已設定此標記,您需要從最後 MotionEvent
的最後一個集上,
從這個指標選取 ACTION_DOWN
。
和 ACTION_CANCEL
一樣,您可以透過 getPointerId(actionIndex)
找到指標。
MotionEvent
集。系統會取消手掌觸控動作,並重新轉譯螢幕畫面。全螢幕、無邊框設計和導覽手勢
如果應用程式為全螢幕模式且在邊緣附近設有可操作元素 (例如 現在可以從繪圖或記事應用程式的畫布 顯示導覽畫面或將應用程式移至背景都可能造成 在這幅畫布上做出不必要的觸控動作
如要防止手勢在應用程式中觸發不必要的觸控動作,您可以採取下列做法:
利用插邊和
ACTION_CANCEL
。
另請參閱「防止誤觸、導覽及不必要的輸入」。 專區。
使用
setSystemBarsBehavior()
敬上
方法和
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
。
/
WindowInsetsController
如何避免導覽手勢造成不必要的觸控事件:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
如要進一步瞭解插邊和手勢管理,請參閱:
低延遲
延遲時間是指硬體、系統和應用程式所需的處理時間 以及轉譯使用者輸入內容
延遲時間 = 硬體和 OS 的輸入處理 + 應用程式處理 + 系統組合
- 硬體轉譯
![延遲會導致轉譯的筆觸落後於觸控筆的位置。轉譯後的筆觸與觸控筆位置之間的時間差即為延遲時間。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_latency.png?authuser=1&hl=zh-tw)
延遲的來源
- 正在透過觸控螢幕 (硬體) 註冊觸控筆:初始無線連線 觸控筆和 OS 通訊完成註冊及同步時。
- 觸控取樣率 (硬體):觸控螢幕的每秒次數 檢查指標是否觸碰表面,範圍從 60 到 1000Hz。
- 輸入處理 (應用程式):套用色彩、圖形效果及轉換 使用者輸入內容時
- 圖形轉譯 (OS + 硬體):緩衝區互換、硬體處理。
低延遲圖形
Jetpack 低延遲圖形程式庫 減少使用者輸入內容與畫面算繪作業之間的處理時間。
程式庫會避免多緩衝區轉譯作業,並 採用前端緩衝區轉譯技術,也就是直接寫入 。
前端緩衝區轉譯
前端緩衝區是螢幕用於轉譯的記憶體。距離最近的 應用程式可以直接在螢幕上繪圖。低延遲程式庫能 直接轉譯至前端緩衝區。這麼做可提高成效 防止緩衝區互換,這種情況在一般多緩衝區轉譯作業中可能會發生 或雙緩衝區轉譯 (最常見的情況)。
![應用程式將資料寫入螢幕緩衝區,並從螢幕緩衝區讀取資料。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/front_buffer_rendering.png?authuser=1&hl=zh-tw)
![應用程式寫入多緩衝區,而多緩衝區與螢幕緩衝區進行互換。應用程式從螢幕緩衝區讀取資料。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/multi-buffer_rendering.png?authuser=1&hl=zh-tw)
雖然前端緩衝區轉譯是絕佳的 但不適合用來重新整理整個螢幕畫面。取代為 前端緩衝區轉譯,應用程式會將內容轉譯到緩衝區中 螢幕正在閱讀中。因此,系統可能會 出現錯誤或撕裂 (請見下方說明)。
低延遲程式庫適用於 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()
}
}
轉譯的注意事項
在螢幕的一小塊區域中進行手寫、繪圖和素描。
在全螢幕模式下更新、平移及縮放內容。這麼做可能導致畫面撕裂。
撕裂
當螢幕在重新整理時,畫面緩衝區出現撕裂 同時修改兩者。螢幕畫面的一部分顯示新資料,另一個部分則顯示 顯示舊資料
![由於螢幕畫面重新整理,Android 圖片的上下部分並未對齊。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/tearing.png?authuser=1&hl=zh-tw)
動作預測
Jetpack 動作預測 程式庫減少 可預估使用者的筆觸路徑,並提供暫時性的 以人工方式指向轉譯器
動作預測程式庫會以 MotionEvent
物件形式接收使用者的實際輸入內容。
這些物件中包含 x 和 y 座標、壓力和時間
動態預測器所運用的這些結果來預測未來的MotionEvent
如需儲存大量結構化物件
建議使用 Cloud Bigtable
預測的 MotionEvent
物件只是預估值。預測事件能減少
使用者感知的延遲時間,但必須以實際的 MotionEvent
取代預測的資料
再加以接收
動作預測程式庫適用於 Android 4.4 (API 級別 19) 版和 以及搭載 Android 9 (API 級別 28) 以上版本的 ChromeOS 裝置。
![延遲會導致轉譯的筆觸落後於觸控筆的位置。系統會透過預測點填補筆觸與觸控筆彼此的時間差。剩下的時間差即為感知延遲時間。](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_reduced_latency.png?authuser=1&hl=zh-tw)
依附元件
動作預測程式庫提供預測實作。
程式庫會新增為應用程式模組 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 上註冊應用程式做為記事,請參閱建立記事 app。
Android 14 (API 級別 34) 導入了
ACTION_CREATE_NOTE
敬上
意圖,可讓應用程式在鎖定時啟動筆記活動
。
透過 ML Kit 辨識數位墨水
使用 ML Kit 數位墨水 辨識, 您的應用程式可以辨識數位表面的手寫文字 語言。也可以對素描進行分類。
ML Kit 提供
Ink.Stroke.Builder
敬上
類別,用來建立可由機器學習模型處理的 Ink
物件
將手寫內容轉換成文字
除了手寫辨識外,模型還能 手勢 例如刪除和圈選
請參閱數位墨水 辨識 。