タッチペンを使用すると、アプリの新しい使い方ができます。たとえば、メモを取ったり、スケッチしたり、精密な作業をしたり、ゲームアプリやエンタメアプリを楽しく簡単に楽しむことができます。
Android と ChromeOS では、卓越したタッチペン エクスペリエンスをアプリで可能にするためのさまざまな API を使用できます。MotionEvent
クラスは、タッチペンの圧力、向き、傾斜、ホバー、手のひら検出など、画面でのユーザー操作に関する情報を提供します。低レイテンシのグラフィックスとモーション予測ライブラリにより、画面上でのタッチペンのレンダリングが強化されて、ペンと紙のように自然に操作できます。
MotionEvent
MotionEvent
クラスは、画面上のタッチポインタの位置や移動など、ユーザー入力操作を表します。タッチペン入力の場合、MotionEvent
は圧力、向き、傾斜、ホバーのデータも表します。
イベントデータ
ビューベースのアプリで MotionEvent
データにアクセスするには、onTouchListener を設定します。
Kotlin
val onTouchListener = View.OnTouchListener { view, event -> // Process motion event. }
Java
View.OnTouchListener listener = (view, event) -> { // Process motion event. };
リスナーは MotionEvent
オブジェクトをシステムから受け取り、アプリで処理できるようにします。
MotionEvent
オブジェクトは、UI イベントの次の要素に関連するデータを提供します。
- アクション: デバイスの物理的操作 - 画面をタップする、画面上でポインタを移動する、画面上でホバーする
- ポインタ: 画面を操作するオブジェクトの識別子(指、タッチペン、マウス)
- 軸: データの種類 - 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
アクションのセットを、モーション セットと呼びます。
ポインタ
ほとんどの画面はマルチタッチです。システムは、画面を操作するそれぞれの指、タッチペン、マウスなどのポインティング オブジェクトにポインタを割り当てます。ポインタ インデックスを使用すると、1 本目の指の画面に接触する指や 2 本目の指の位置など、特定のポインタの軸情報を取得できます。
ポインタ インデックスの範囲は、最小値はゼロ、最大値は MotionEvent#pointerCount()
によって返されるポインタの数から 1 を引いた値になります。
ポインタの軸の値にアクセスするには、getAxisValue(axis, pointerIndex)
メソッドを使用します。ポインタ インデックスを省略すると、最初のポインタであるポインタ 0 の値が返されます。
MotionEvent
オブジェクトには、使用中のポインタの種類に関する情報が含まれています。ポインタの種類を取得するには、ポインタ インデックスに対して反復処理を行い、getToolType(pointerIndex)
メソッドを呼び出します。
ポインタについて詳しくは、マルチタッチ ジェスチャーの処理をご覧ください。
タッチペン入力
TOOL_TYPE_STYLUS
を使用すると、タッチペン入力をフィルタできます。
Kotlin
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
Java
boolean isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex);
TOOL_TYPE_ERASER
を使用すると、タッチペンが消しゴムとして使用されていることも報告できます。
Kotlin
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Java
boolean isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex);
タッチペンの軸データ
ACTION_DOWN
と ACTION_MOVE
は、タッチペンに関する軸データ、すなわち x 座標と y 座標、圧力、向き、傾斜、ホバーのデータを提供します。
このデータへのアクセスのため、MotionEvent
API には getAxisValue(int)
が用意されています。ここで、パラメータは次のいずれかの軸 ID です。
軸 | 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()

圧力
次の呼び出しを使用して、ポインタの圧力を取得できます。
最初のポインタの場合は getAxisValue(AXIS_PRESSURE)
または getPressure()
。
タッチスクリーンまたはタッチパッドの圧力値は 0(圧力なし)~1 の範囲ですが、画面の調整によっては高い値が返される場合があります。

方向
方向とは、タッチペンが向いている方向を示します。
ポインタの向きは、getAxisValue(AXIS_ORIENTATION)
または getOrientation()
(最初のポインタ)を使用して取得できます。
タッチペンの場合、向きは 0~π(時計回り)または 0~-π(反時計回り)のラジアン値として返されます。
向きを使用すると、実際のブラシを実装できます。たとえば、タッチペンがフラットブラシを表す場合、フラットブラシの幅はタッチペンの向きによって異なります。

傾斜
傾斜は、画面に対するタッチペンの傾きを測定したものです。
傾斜として、タッチペンの正の角度がラジアン単位で返されます。ゼロは画面に対して垂直、π/2 は画面上で水平であることを表します。
傾斜角は getAxisValue(AXIS_TILT)
を使用して取得できます(最初のポインタのショートカットはありません)。
傾斜を使用すると、実際のものにできるだけ近くなるように道具を再現できます。たとえば、傾けた鉛筆で陰影を付けることができます。

ホバー
画面からタッチペンまでの距離は getAxisValue(AXIS_DISTANCE)
で取得できます。このメソッドにより返される値の最小値は 0.0(画面に接触)で、タッチペンが画面から離れるほど値が大きくなります。画面とタッチペンのペン先(先端)間のホバー距離は、画面とタッチペンの両方のメーカーによって異なります。実装は異なる場合があるため、アプリの重要機能についてはあまり厳密な値に頼らないようにしてください。
タッチペンのホバーを使用して、ブラシのサイズをプレビューしたり、ボタンが選択されることを明示したりできます。

注: Compose には、UI 要素の状態を変更するための修飾子要素が複数用意されています。
hoverable
: ポインタの開始 / 終了イベントを介してホバーできるようにコンポーネントを構成します。indication
: インタラクションが発生したときに、このコンポーネントの視覚効果を描画します。
パーム リジェクション、ナビゲーション、不要な入力
マルチタッチ スクリーンでは、手書き入力中にサポートのためにユーザーが手を置いた場合などに、不要なタップ操作が登録されることがあります。パーム リジェクションは、この動作を検出するメカニズムであり、最後の MotionEvent
セットをキャンセルする必要があることを通知します。
そのため、ユーザー入力の履歴を保持することにより、不要なタップを画面から削除し、正当なユーザー入力を再レンダリングできるようにする必要があります。
ACTION_CANCELED と FLAG_CANCELED
ACTION_CANCEL
と FLAG_CANCELED
はどちらも、以前の 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
は、ポインタの上移動が、意図しないタップであったことを示します。このフラグは通常、ユーザーが誤って画面に触れたときに発生します。たとえば、ユーザーがデバイスをつかんだり、手のひらを画面に置いた場合などです。
フラグの値には、次の方法でアクセスできます。
Kotlin
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Java
boolean cancel = (event.getFlags() & FLAG_CANCELED) == FLAG_CANCELED;
このフラグが設定されている場合、このポインタの最後の ACTION_DOWN
から、最後の MotionEvent
セットを元に戻す必要があります。
ACTION_CANCEL
と同様に、ポインタは getPointerId(actionIndex)
で見つけることができます。
MotionEvent
セットが作成されます。手のひらでのタップはキャンセルされ、ディスプレイが再レンダリングされます。全画面表示、エッジ ツー エッジ、ナビゲーション ジェスチャー
アプリが全画面表示中で、描画アプリやメモ用アプリのキャンバスなどの操作可能な要素が端付近にある場合、画面の下からスワイプして、ナビゲーションを表示したり、アプリをバックグラウンドに移動したりすると、キャンバスが意図せずタップされることがあります。
アプリでの操作により不要なタップがトリガーされないようにするには、インセットと ACTION_CANCEL
を使用します。
上記のパーム リジェクション、ナビゲーション、不要な入力もご覧ください。
WindowInsetsController
の setSystemBarsBehavior()
メソッドと BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
を使用すると、ナビゲーション ジェスチャーによって不要なタッチイベントが発生しないようにできます。
Kotlin
// Configure the behavior of the hidden system bars. windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Java
// Configure the behavior of the hidden system bars. windowInsetsController.setSystemBarsBehavior( WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE );
インセットとジェスチャー管理について詳しくは、以下をご覧ください。
低レイテンシ
レイテンシとは、ハードウェア、システム、アプリケーションがユーザー入力を処理してレンダリングするのに必要な時間です。
レイテンシ = ハードウェアと OS の入力処理 + アプリ処理 + システム合成 + ハードウェアのレンダリング

レイテンシの原因
- タッチスクリーン(ハードウェア)へのタッチペンの登録: タッチペンと OS の通信により登録および同期する際の最初のワイヤレス接続。
- タップのサンプリング レート(ハードウェア): タッチスクリーンが 1 秒間にポインタに接触している回数(60~1,000 Hz)。
- 入力処理(アプリ): ユーザー入力への色、グラフィック エフェクト、変換の適用。
- グラフィック レンダリング(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
では、コールバックを使用して、可能な限り最適化された方法でデータをレンダリングします。
Kotlin
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. } }
Java
GLFrontBufferedRenderer.Callback<DATA_TYPE> callbacks = new GLFrontBufferedRenderer.Callback<DATA_TYPE>() { @Override public void onDrawFrontBufferedLayer(@NonNull EGLManager eglManager, @NonNull BufferInfo bufferInfo, @NonNull float[] transform, DATA_TYPE data_type) { // OpenGL for front buffer, short, affecting small area of the screen. } @Override public void onDrawDoubleBufferedLayer(@NonNull EGLManager eglManager, @NonNull BufferInfo bufferInfo, @NonNull float[] transform, @NonNull Collection<? extends DATA_TYPE> collection) { // OpenGL full scene rendering. } };
GLFrontBufferedRenderer のインスタンスを宣言する
GLFrontBufferedRenderer
を準備するには、先ほど作成した SurfaceView
とコールバックを指定します。GLFrontBufferedRenderer
は、コールバックを使用してフロント バッファとダブルバッファへのレンダリングを最適化します。
Kotlin
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Java
GLFrontBufferedRenderer<DATA_TYPE> glFrontBufferRenderer = new GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks);
レンダリング
フロント バッファ レンダリングは renderFrontBufferedLayer()
メソッドを呼び出すと開始され、これにより onDrawFrontBufferedLayer()
コールバックがトリガーされます。
commit()
関数を呼び出すと、ダブルバッファ レンダリングが再開され、onDrawMultiDoubleBufferedLayer()
コールバックがトリガーされます。
以下の例では、ユーザーが画面上で描画を開始し(ACTION_DOWN
)、ポインタを動かすと(ACTION_MOVE
)、プロセスがフロント バッファにレンダリングされます(高速レンダリング)。ポインタが画面の表面を離れると、プロセスはダブルバッファにレンダリングされます(ACTION_UP
)。
requestUnbufferedDispatch()
を使用すると、入力システムがモーション イベントをバッチ処理せずに、利用可能になったらすぐに配信するようリクエストできます。
Kotlin
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() } }
Java
switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: { // Deliver input events as soon as they arrive. surfaceView.requestUnbufferedDispatch(motionEvent); // Pointer is in contact with the screen. glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE); } break; case MotionEvent.ACTION_MOVE: { // Pointer is moving. glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE); } break; case MotionEvent.ACTION_UP: { // Pointer is not in contact in the screen. glFrontBufferRenderer.commit(); } break; case MotionEvent.ACTION_CANCEL: { // Cancel front buffer; remove last motion set from the screen. glFrontBufferRenderer.cancel(); } break; }
レンダリングですべきこと、すべきでないこと
すべきこと
画面の小さな部分、手書き、描画、スケッチ。
すべきでないこと
全画面表示の更新、パン、ズーム。テアリングが発生する場合があります。
テアリング
テアリングは、画面の更新と画面バッファの変更が同時に行われると発生します。画面の一部に新しいデータが表示され、別の部分には古いデータが表示されます。

モーション予測
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
インスタンスを宣言する
Kotlin
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Java
MotionEventPredictor motionEventPredictor = MotionEventPredictor.newInstance(surfaceView);
予測子にデータをフィードする
Kotlin
motionEventPredictor.record(motionEvent)
Java
motionEventPredictor.record(motionEvent);
予測
Kotlin
when (motionEvent.action) { MotionEvent.ACTION_MOVE -> { val predictedMotionEvent = motionEventPredictor?.predict() if(predictedMotionEvent != null) { // use predicted MotionEvent to inject a new artificial point } } }
Java
switch (motionEvent.getAction()) { case MotionEvent.ACTION_MOVE: { MotionEvent predictedMotionEvent = motionEventPredictor.predict(); if(predictedMotionEvent != null) { // use predicted MotionEvent to inject a new artificial point } } break; }
モーション予測ですべきこととすべきでないこと
すべきこと
新しい予測ポイントが追加された時点で、予測ポイントを削除する。
すべきでないこと
最終的なレンダリングに予測ポイントを使用しない。
メモ作成アプリ
ChromeOS では、アプリでのメモ作成アクションを宣言できます。
アプリは、アプリ マニフェストでメモ作成アプリとして登録できます。詳細については、入力の互換性をご覧ください。
Android 14(API レベル 34)では、ロック画面でメモ作成アクティビティを開始できる ACTION_CREATE_NOTE
インテントが導入されました。
ML Kit によるデジタルインク認識
ML Kit のデジタルインク認識機能を使用すると、アプリは数百もの言語で、デジタル表示面の手書き文字を認識できます。スケッチを分類することもできます。
ML Kit では、Ink.Stroke.Builder
クラスにより Ink
オブジェクトを作成できます。このオブジェクトを機械学習モデルが処理して、手書き文字をテキストに変換します。
このモデルでは、手書き入力の認識に加え、削除や円などの操作を認識できます。
詳しくは、デジタルインク認識をご覧ください。