自定义文本编辑器不是EditText
组件或WebView
文本控件,但通过实现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 无法进入手写模式,则此方法调用将不起作用。
声明手写支持
在填充EditorInfo
View#onCreateInputConnection(EditorInfo)
调用的参数时,调用setStylusHandwritingEnabled()
以告知 IME 文本编辑器支持手写。使用setSupportedHandwritingGestures()
和setSupportedHandwritingGesturePreviews()
声明支持的手势。
支持手写手势
IME 可以支持各种手写手势,例如圈选文本以选择它或在文本上涂写以删除它。
自定义编辑器实现InputConnection#performHandwritingGesture()
和InputConnection#previewHandwritingGesture()
以支持不同的HandwritingGesture
类型,例如SelectGesture
、DeleteGesture
和InsertGesture
。
在填充EditorInfo
View#onCreateInputConnection(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 无法确定触笔手势是用于执行手势操作还是手写文本。自定义文本编辑器负责确定用户的意图并在手势位置执行相应的操作(取决于上下文)。
例如,如果 IME 无法确定用户是想绘制向下箭头 ⋁ 来执行插入空格手势,还是手写字母“v”,IME 可以发送带有备用文本“v”的
InsertGesture
。编辑器应首先尝试执行插入空格手势。如果无法执行手势(例如,指定位置没有文本),则编辑器应回退到在光标位置插入“v”。
InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
— 预览正在进行的手势。例如,当用户开始围绕某些文本绘制圆圈时,可以选择显示结果选择的实时预览,并在用户继续绘制时持续更新。只有某些手势类型可预览(参见PreviewableHandwritingGesture
)。CancellationSignal
参数可由 IME 用于取消预览。如果其他事件中断预览(例如,以编程方式更改文本或出现新的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)
以访问系统的触笔手写悬停图标。