自定义文本编辑器是指那些不是 EditText 组件或 WebView 文本微件的视图,但它们通过实现 onCreateInputConnection() 回调来支持文本输入,此回调在视图获得焦点且系统为视图请求 InputConnection 时调用。
自定义文本编辑器对 onCheckIsTextEditor() 的调用应返回 true。
在自定义文本编辑器中支持触控笔手写
默认情况下,Android 14(API 级别 34)及更高版本支持在标准 Android 文本输入组件中使用触控笔输入(请参阅文本字段中的触控笔输入)。但是,自定义文本输入字段(或编辑器)需要额外的开发。
如需创建自定义文本编辑器,请执行以下操作
- 启用手写启动
- 声明手写支持
- 支持手写手势(选择、删除、插入等)
- 向 IME 提供光标位置及其他位置数据
- 显示触控笔手写悬停图标
启用手写启动
如果视图仅包含一个文本编辑器,则视图系统可以自动为该视图启动触控笔手写功能。否则,该视图必须实现自己的手写启动逻辑。
自动手写启动
如果视图显示单个文本编辑器且没有其他内容,则可以通过调用 setAutoHandwritingEnabled(true) 来选择加入视图系统的自动手写启动功能。
启用自动手写功能后,触控笔在视图手写边界内任何位置开始移动都会自动启动手写模式。输入法编辑器 (IME) 会接收触控笔移动事件并提交识别的文本。
EditText 字段边界内的手写。自定义手写启动
如果视图包含多个文本编辑器或除单个文本编辑器外的其他内容,则该视图必须按如下方式实现自己的手写启动逻辑
通过调用
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 使用InputConnectionAPI 提交识别的手写文本。如果 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参数返回操作结果。如果同时提供了 executor 和 consumer 参数,请使用 executor 调用IntConsumer#accept(),例如executor.execute { consumer.accept(HANDWRITING_GESTURE_RESULT_SUCCESS) }HandwritingGesture#getFallbackText()— 提供 IME 在光标位置提交的备用文本,前提是手写手势区域下方没有适用文本。有时 IME 无法确定触控笔手势是 intended 执行手势操作还是手写文本。自定义文本编辑器负责确定用户的意图,并在手势位置执行适当的操作(取决于上下文)。
例如,如果 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) 来访问系统的触控笔手写悬停图标。