カスタム テキスト エディタ

カスタム テキスト エディタは、EditText コンポーネントまたは WebView テキスト ウィジェットではないビューですが、onCreateInputConnection() コールバックを実装することでテキスト入力をサポートしません。このコールバックは、ビューがフォーカスされ、システムがビューの InputConnection をリクエストしたときに呼び出されます。

カスタム テキスト エディタから onCheckIsTextEditor() を呼び出すと、true が返されます。

カスタム テキスト エディタでのタッチペン手書き入力のサポート

Android 14(API レベル 34)以降では、標準の Android テキスト入力コンポーネントでのタッチペン入力はデフォルトでサポートされています(テキスト フィールドのタッチペン入力をご覧ください)。ただし、カスタム テキスト入力フィールド(エディタ)には追加開発が必要です。

カスタム テキスト エディタを作成する手順は次のとおりです。

  1. 手書き入力の開始を有効にする
  2. 手書き入力のサポートを宣言する
  3. 手書き入力操作(選択、削除、挿入など)をサポートする
  4. カーソルの位置とその他の位置データを IME に提供する
  5. タッチペンの手書き入力ホバーアイコンを表示する

手書き入力の開始を有効にする

ビューが 1 つのテキスト エディタのみで構成されている場合、ビューシステムはビューに対してタッチペンの手書き入力を自動的に開始できます。それ以外の場合は、ビューに独自の手書き開始ロジックを実装する必要があります。

手書き入力の自動開始

ビューが 1 つのテキスト エディタのみを表示し、他のコンテンツを表示しない場合は、setAutoHandwritingEnabled(true) を呼び出すことで、ビューシステムによる手書き入力の自動開始をオプトインできます。

自動手書き入力を有効にすると、ビューの手書き入力の境界内でタッチペンを動かすと、自動的に手書きモードが開始されます。インプット メソッド エディタ(IME)は、タッチペン モーション イベントを受け取り、認識されたテキストを commit します。

タッチペンのモーション イベントの検出範囲を示す長方形の周囲に表示される入力フィールド。
図 1. EditText フィールドの境界内の手書き入力。

カスタムの手書き入力の開始

ビュー内に 1 つのテキスト エディタに加えて複数のテキスト エディタまたはコンテンツが含まれる場合は、ビュー内で次のように独自の手書き入力開始ロジックを実装する必要があります。

  1. ビューシステムによる手書き入力の自動開始を無効にするには、setAutoHandwritingEnabled(false) を呼び出します。

  2. ビュー内に表示されているすべてのテキスト エディタを管理します。

  3. dispatchTouchEvent() のビューが受信したモーション イベントをモニタリングします。

    • テキスト エディタの手書き入力の境界内でタッチペンの動きが発生したら、テキスト エディタにフォーカスします(まだフォーカスされていない場合)。

    • エディタがまだフォーカスされていない場合は、InputMethodManager#restartInput() を呼び出して、新しいコンテンツでエディタの IME を再起動します。

    • InputMethodManager#startStylusHandwriting() を呼び出して、タッチペン手書き入力セッションを開始します。

テキスト エディタがスクロール可能なビュー内にある場合、エディタの手書き入力の境界内でのタッチペンの移動は、スクロールではなく手書き入力とみなす必要があります。スクロール可能な祖先ビューがテキスト エディタからのタッチイベントをインターセプトしないようにするには、ViewParent#requestDisallowInterceptTouchEvent() を使用します。

API の詳細

  • MotionEvent#getToolType() - MotionEvent がタッチペンからのものかどうかを示します。この場合、戻り値は TOOL_TYPE_STYLUS または TOOL_TYPE_ERASER になります。

  • InputMethodManager#isStylusHandwritingAvailable() - IME がタッチペン手書き入力をサポートしているかどうかを示します。手書き入力を利用できるかどうかが変わる可能性があるため、InputMethodManager#startStylusHandwriting() を呼び出すたびにこのメソッドを呼び出します。

  • InputMethodManager#startStylusHandwriting() - IME を手書きモードに切り替えます。ACTION_CANCEL モーション イベントがアプリにディスパッチされ、現在の操作がキャンセルされます。タッチペン モーション イベントはアプリにディスパッチされなくなります。

    すでにアプリにディスパッチされている現在の操作のタッチペン モーション イベントは、IME に転送されます。IME は、タッチペン インク ウィンドウを表示する必要があります。このウィンドウを通して、IME は以降のすべての MotionEvent オブジェクトを受け取ります。IME は、InputConnection API を使用して、認識された手書き入力テキストを commit します。

    IME が手書きモードに移行できない場合、このメソッド呼び出しは何も行いません。

手書き入力のサポートを宣言する

View#onCreateInputConnection(EditorInfo)EditorInfo 引数を入力する場合は、setStylusHandwritingEnabled() を呼び出して、テキスト エディタが手書き入力をサポートしていることを IME に通知します。サポートされている操作は、setSupportedHandwritingGestures()setSupportedHandwritingGesturePreviews() で宣言します。

手書き入力操作をサポートする

IME は、テキストを丸で囲んで選択したり、テキストをフリーハンドで削除したりするなど、さまざまな手書き入力操作をサポートしています。

図 2.テキストを選択するには円を描きます。
図 3.フリーハンドでテキストを削除します。

カスタム エディタは、InputConnection#performHandwritingGesture()InputConnection#previewHandwritingGesture() を実装して、SelectGestureDeleteGestureInsertGesture などのさまざまな HandwritingGesture タイプをサポートします。

View#onCreateInputConnection(EditorInfo)EditorInfo 引数を入力する際に、サポートされている手書き操作を宣言します(手書き入力のサポートを宣言するのセクションをご覧ください)。

API の詳細

  • InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer) - 操作を実装します。HandwritingGesture 引数には位置情報が含まれています。この情報を使用して、テキスト内のどこでジェスチャーを行うかを判断できます。たとえば、SelectGesture は選択されたテキスト範囲を指定する RectF オブジェクトを提供し、InsertGesture はテキストを挿入するテキスト オフセットを指定する PointF オブジェクトを提供します。

    Executor パラメータと IntConsumer パラメータを使用して、オペレーションの結果を返します。エグゼキュータ引数とコンシューマ引数の両方が指定されている場合は、エグゼキュータを使用して IntConsumer#accept() を呼び出します。次に例を示します。

    
    executor.execute { consumer.accept(HANDWRITING_GESTURE_RESULT_SUCCESS) }
    
    
  • HandwritingGesture#getFallbackText() - 手書き入力操作の領域の下に該当するテキストがない場合に、IME がカーソル位置にコミットする代替テキストを提供します。

    IME は、タッチペン ジェスチャーがジェスチャー操作やテキストの手書き入力を目的としたものかどうかを判断できないことがあります。カスタム テキスト エディタは、ユーザーの意図を判断し、ジェスチャーの位置で(コンテキストに応じて)適切なアクションを実行します。

    たとえば、IME が、ユーザーが下向きのキャレットを引いてスペースの挿入操作や文字「v」を手書きすることを意図したかどうかを確認できない場合、IME は、フォールバック テキスト「v」を含む InsertGesture を送信できます。

    エディタは、まずスペース挿入操作を実行する必要があります。ジェスチャーを実行できない場合(指定された位置にテキストがない場合など)、エディタはフォールバックして、カーソル位置に「v」を挿入します。

  • InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal) - 進行中の操作をプレビューします。たとえば、ユーザーがテキストの周囲に円を描き始めると、その結果の選択内容のライブ プレビューが表示され、ユーザーが描画を続けると継続的に更新されます。プレビューできるのは、特定のジェスチャー タイプのみです(PreviewableHandwritingGesture をご覧ください)。

    IME は CancellationSignal パラメータを使用してプレビューをキャンセルできます。他のイベントによってプレビューが中断された場合(テキストがプログラムで変更された場合や、新しい InputConnection コマンドが発生した場合など)、カスタム エディタでプレビューをキャンセルできます。

    プレビュー操作は表示専用であり、エディタの状態は変更されません。たとえば、SelectGesture プレビューでは、エディタの現在の選択範囲が非表示になり、ジェスチャー プレビュー範囲がハイライト表示されます。ただし、プレビューがキャンセルされると、エディタは以前の選択範囲を復元する必要があります。

カーソルの位置とその他の位置データを提供する

手書き入力モードでは、IME は InputConnection#requestCursorUpdates() を使用してカーソルの位置やその他の位置データをリクエストできます。カスタム エディタは、それに対して InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo) の呼び出しを返します。タッチペンの手書き入力に関連する CursorAnchorInfo のデータは、次の CursorAnchorInfo.Builder メソッドによって提供されます。

  • setInsertionMarkerLocation() - カーソルの位置を設定します。IME はこの値を使用して、カーソルの位置まで手書きインクをアニメーションで表示します。
  • setEditorBoundsInfo() - エディタの境界と手書き入力の境界を設定します。IME はこのデータを使用して、IME の手書き入力ツールバーを画面上に配置します。
  • addVisibleLineBounds() - エディタに表示されるすべての(または部分的に表示されている)テキスト行の境界を設定します。IME は行境界を使用して、手書き操作の認識精度を高めます。
  • setTextAppearanceInfo() - テキスト入力フィールドから取得した情報を使用して、テキストの外観を設定します。IME はこの情報を使用して手書きインクのスタイルを設定します。

タッチペンの手書き入力ホバーアイコンを表示する

カスタム テキスト エディタの手書き入力の境界にタッチペンでカーソルを合わせ、選択した IME がタッチペンによる手書き入力に対応している場合、タッチペン手書き入力のホバーアイコンを表示します(InputMethodManager#isStylusHandwritingAvailable())。

View#onResolvePointerIcon() をオーバーライドして、タッチペン手書き入力のホバーアイコンを取得します。オーバーライドで PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING) を呼び出して、システムのタッチペン手書き入力ホバーアイコンにアクセスします。

参考情報