自定义文本编辑器是指虽然不是 EditText
组件或 WebView
文本 widget,但仍通过实现 onCreateInputConnection()
回调来支持文本输入的视图,该回调在视图获得焦点且系统为视图请求 InputConnection
时调用。
从自定义文本编辑器调用 onCheckIsTextEditor()
时,应返回 true
。
支持在自定义文本编辑器中使用触控笔手写
Android 14(API 级别 34)及更高版本默认支持标准 Android 文本输入组件中的触控笔输入(请参阅文本字段中的触控笔输入)。但是,自定义文本输入字段(或编辑器)需要额外的开发工作。
如需创建自定义文本编辑器,请执行以下操作:
- 启用手写功能
- 声明手写支持
- 支持手写手势(选择、删除、插入等)
- 向 IME 提供光标位置和其他位置数据
- 显示触控笔手写悬停图标
启用手写功能
如果某个视图只包含一个文本编辑器,则视图系统可以自动为该视图启动触控笔手写功能。否则,视图必须实现自己的手写启动逻辑。
自动开始手写
如果某个视图只显示一个文本编辑器,而没有显示其他内容,则该视图可以通过调用 setAutoHandwritingEnabled(true)
选择启用视图系统的自动手写功能。
启用自动手写功能后,只要在视图的手写边界内任意位置开始触控笔运动,系统即会自动启动手写模式。输入法 (IME) 接收触控笔动作事件并提交识别出的文本。
自定义手写启动
如果某个视图除了单个文本编辑器外还包含多个文本编辑器或内容,则该视图必须实现自己的手写启动逻辑,如下所示:
通过调用
setAutoHandwritingEnabled(false)
停用视图系统的自动手写功能。跟踪视图中显示的所有文本编辑器。
在
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 提交识别出的手写文本。如果 IME 无法进入手写模式,则此方法调用为空操作。
声明手写支持
填写 View#onCreateInputConnection(EditorInfo)
的 EditorInfo
参数时,调用 setStylusHandwritingEnabled()
以告知 IME 文本编辑器支持手写。使用 setSupportedHandwritingGestures()
和 setSupportedHandwritingGesturePreviews()
声明支持的手势。
支持手写手势
IME 可以支持各种手写手势,例如环绕文本将其选中,或在文本上涂画即可将其删除。
自定义编辑器会实现 InputConnection#performHandwritingGesture()
和 InputConnection#previewHandwritingGesture()
以支持不同的 HandwritingGesture
类型,如 SelectGesture
、DeleteGesture
和 InsertGesture
。
在填充 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)
以访问系统的触控笔手写悬停图标。