自定义文本编辑器

自定义文本编辑器是指虽然不是 EditText 组件或 WebView 文本 widget,但仍通过实现 onCreateInputConnection() 回调来支持文本输入的视图,该回调在视图获得焦点且系统为视图请求 InputConnection 时调用。

从自定义文本编辑器调用 onCheckIsTextEditor() 时,应返回 true

支持在自定义文本编辑器中使用触控笔手写

Android 14(API 级别 34)及更高版本默认支持标准 Android 文本输入组件中的触控笔输入(请参阅文本字段中的触控笔输入)。但是,自定义文本输入字段(或编辑器)需要额外的开发工作。

如需创建自定义文本编辑器,请执行以下操作:

  1. 启用手写功能
  2. 声明手写支持
  3. 支持手写手势(选择、删除、插入等)
  4. 向 IME 提供光标位置和其他位置数据
  5. 显示触控笔手写悬停图标

启用手写功能

如果某个视图只包含一个文本编辑器,则视图系统可以自动为该视图启动触控笔手写功能。否则,视图必须实现自己的手写启动逻辑。

自动开始手写

如果某个视图只显示一个文本编辑器,而没有显示其他内容,则该视图可以通过调用 setAutoHandwritingEnabled(true) 选择启用视图系统的自动手写功能。

启用自动手写功能后,只要在视图的手写边界内任意位置开始触控笔运动,系统即会自动启动手写模式。输入法 (IME) 接收触控笔动作事件并提交识别出的文本。

输入字段,周围的矩形指示检测触控笔动作事件的边界。
图 1. EditText 字段边界内的手写内容。

自定义手写启动

如果某个视图除了单个文本编辑器外还包含多个文本编辑器或内容,则该视图必须实现自己的手写启动逻辑,如下所示:

  1. 通过调用 setAutoHandwritingEnabled(false) 停用视图系统的自动手写功能。

  2. 跟踪视图中显示的所有文本编辑器。

  3. dispatchTouchEvent() 中监控视图收到的动作事件。

如果文本编辑器位于可滚动视图中,则触控笔在编辑器的手写边界内的移动应被视为手写,而非滚动。使用 ViewParent#requestDisallowInterceptTouchEvent() 可防止可滚动的祖先视图拦截来自文本编辑器的触摸事件。

API 详细信息

  • MotionEvent#getToolType() - 指示 MotionEvent 是否来自触控笔,在这种情况下,返回值为 TOOL_TYPE_STYLUSTOOL_TYPE_ERASER

  • InputMethodManager#isStylusHandwritingAvailable() - 指示 IME 是否支持触控笔手写。每次调用 InputMethodManager#startStylusHandwriting() 之前,都应调用此方法,因为手写内容的可用性可能已发生变化。

  • InputMethodManager#startStylusHandwriting() - 使 IME 进入手写模式。系统会向应用分派 ACTION_CANCEL 动作事件,以取消当前手势。系统不再将触控笔动作事件分派给应用。

    已分派给应用的当前手势的触控笔动作事件会转发到 IME。IME 需要显示触控笔墨水窗口,IME 可通过该窗口接收所有后续 MotionEvent 对象。IME 使用 InputConnection API 提交识别出的手写文本。

    如果 IME 无法进入手写模式,则此方法调用为空操作。

声明手写支持

填写 View#onCreateInputConnection(EditorInfo)EditorInfo 参数时,调用 setStylusHandwritingEnabled() 以告知 IME 文本编辑器支持手写。使用 setSupportedHandwritingGestures()setSupportedHandwritingGesturePreviews() 声明支持的手势。

支持手写手势

IME 可以支持各种手写手势,例如环绕文本将其选中,或在文本上涂画即可将其删除。

图 2. 圈住相应文字即可将其选中。
图 3.在文字上乱涂即可将其删除。

自定义编辑器会实现 InputConnection#performHandwritingGesture()InputConnection#previewHandwritingGesture() 以支持不同的 HandwritingGesture 类型,如 SelectGestureDeleteGestureInsertGesture

在填充 View#onCreateInputConnection(EditorInfo)EditorInfo 参数时声明支持的手写手势(请参阅声明手写支持部分)。

API 详细信息

  • InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer) - 实现手势。HandwritingGesture 参数包含位置信息,您可以使用该信息来确定要在文本中的哪个位置执行手势。例如,SelectGesture 提供了一个 RectF 对象,用于指定所选文本范围,InsertGesture 提供了一个 PointF 对象,用于指定插入文本时在哪个偏移量处插入文本。

    您可以使用 ExecutorIntConsumer 参数发回操作结果。如果同时提供了执行器参数和使用方参数,请使用执行器调用 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) 以访问系统的触控笔手写悬停图标。

其他资源