Skip to content

UI 库架构总览

UI 库 :ui 的核心设计目标是作为缺省 UI 实现对第三方应用开放。第三方应用可以直接使用库中的 Compose 组件和 KeyboardViewModel 构建完整的输入法界面,无需自行实现视图层或 ViewModel。同时,UI 库的设计遵循「可替换」原则:所有 UI 组件仅依赖 :engine 的公开 API(StateFlowImeIntentEditorAction),不依赖引擎内部实现,因此第三方应用可以完全用自定义 UI 替换 :ui 而不影响引擎功能。


1 设计目标

定位说明
缺省实现提供完整的、即插即用的输入法 UI,第三方应用引入后开箱即用
可替换所有 UI 组件仅依赖引擎公开 API,第三方应用可自行替换任意组件
可组合组件粒度合理,第三方应用可选择性使用部分组件(如只用键盘不用候选栏)
可定制通过主题系统(KeyboardColors)和配置参数控制外观和行为

UI 库的「缺省实现」定位意味着它必须提供功能完备的组件,覆盖输入法界面的所有交互场景。第三方应用无需实现任何 UI 逻辑,只需引入 :engine + :ui 两个库,即可获得完整的输入法能力与界面。「可替换」定位则要求组件边界清晰、接口稳定,所有组件仅依赖 :engine 公开 API,确保第三方应用可替换任意组件而不影响引擎运行。

「可组合」定位要求组件粒度合理,既不过度拆分导致组合复杂度飙升,也不过度聚合导致无法复用。KeyboardHost 作为一站式集成组件提供了最便捷的使用方式,而 KeyLayoutPanelCandidateListPanel 等面板组件则允许第三方应用按需组合。「可定制」定位通过 KeyboardColors 主题系统和 ImeConfig 配置参数实现,外观和行为均可在不修改组件代码的前提下调整。四个设计目标之间没有优先级之分,它们共同构成了 UI 库的架构约束,任何设计决策都需要在这四个维度上取得平衡。


2 组件清单

2.1 原子组件

最小粒度 UI 组件,由上层组合使用,也可单独使用。

组件包路径说明
KeyViewkeyboard单个按键渲染(纯展示,无触摸)
CandidateItemcandidate单个候选项
CharInputItem / GapInputIteminput输入栏中的字符 / 间隙项
PopupTipItempanel弹出提示内容项(支持 MessageAction 两种类型)
ToolItempanel工具按钮项(含编辑功能键)

原子组件是 UI 库中最小的可复用单元,每个原子组件仅负责单一视觉元素的渲染。KeyView 渲染单个按键的视觉状态(标签、背景、激活/禁用等持续性状态),不处理触摸事件。CandidateItem 渲染单个候选项的文本和选中态。CharInputItemGapInputItem 分别渲染输入栏中的已输入字符和光标间隙。PopupTipItem 渲染弹出提示的内容,根据 PopupTipState.MessagePopupTipState.Action 类型呈现不同的交互方式:Message 类型仅展示文本信息并自动消失,Action 类型展示文本和可点击的操作按钮。ToolItem 渲染工具栏中的功能按钮(如全选、复制、粘贴等),点击后发送关联的 ImeIntent

2.2 面板组件

由原子组件组合而成,可独立使用。

组件包路径说明
GestureInputPanelpanel透明手势拦截层,唯一触摸事件接收者
KeyLayoutPanelpanel按键布局渲染层,纯展示
GestureFeedbackPanelpanel透明反馈绘制层(归一化坐标绘制),唯一视觉反馈绘制者
CandidateListPanelcandidate候选栏(含内建 IndicatorOverlay
PopupTipPanelpanel弹出提示面板(订阅 PopupTipState,支持 MessageAction 两种类型)
ToolListPanelpanel工具列表面板(含编辑功能键,含内建 IndicatorOverlay
InputListPanelinput输入栏(含内建 IndicatorOverlay

面板组件是原子组件的上层组合,每个面板组件负责一个完整功能区域的渲染和交互协调。GestureInputPanelKeyLayoutPanelGestureFeedbackPanel 构成三层分离架构的核心,分别负责触摸事件接收、按键状态渲染和手势视觉反馈绘制(详见 020-面板三层分离与屏幕布局)。CandidateListPanel 展示候选词列表,PopupTipPanel 展示弹出提示,ToolListPanel 展示工具按钮列表,InputListPanel 展示当前输入内容。CandidateListPanelToolListPanelInputListPanel 均内建了指示器绘制能力,通过 showIndicator 参数控制是否在面板内部绘制输入动作播放的指示器动画。

2.3 集成组件

一站式解决方案,将多个面板组合为完整输入法界面。

组件包路径说明
KeyboardHostintegration统一集成组件,通过 KeyboardLayoutMode 参数支持 Stacked/Separated 两种布局
KeyboardInputActionPlayerHostintegration演示集成组件,支持 Animation/DirectInput 两种 UseMode
InputActionPlayerPanelintegration输入练习播放控制面板(注:ExerciseScreen 属于 :app 模块)

集成组件是 UI 库提供的最高层组合,将多个面板组件组装为功能完整的输入法界面。KeyboardHost 是最核心的集成组件,它根据 KeyboardLayoutMode 决定 StackedSeparated 布局,将 CandidateListPanelPopupTipPanelToolListPanel/InputListPanelKeyLayoutPanelGestureFeedbackPanelGestureInputPanel 按照三层分离架构和三行结构组装。KeyboardInputActionPlayerHostKeyboardHost 基础上叠加输入动作播放引擎,支持演示和输入辅助两种模式。InputActionPlayerPanel 提供播放控制 UI(播放、暂停、进度条等),与 KeyboardInputActionPlayerHost 配合使用。

2.4 ViewModel 组件

组件包路径说明
KeyboardViewModelviewmodelUI 协调中心,持有 ImeEngine,暴露 StateFlow<ImeState>,将 InputGesture 转换为 ImeIntent,管理 GestureFeedbackStatePopupTipStateToolListStatelayoutModeInputActionPlayer,在 gestureToIntent() 中直接处理音效/触觉/按键弹出提示

KeyboardViewModel 是 UI 层的协调中心,桥接 Compose UI 组件与 :engine 引擎。它不属于 :app 模块,而是归属于 :ui 模块,确保任何引入 :engine + :ui 的第三方应用都能获得即插即用的 ViewModel。ViewModel 的核心职责包括:手势/意图分发(handleGesture()handleIntent())、状态暴露(stateconfiglayoutModefeedbackState)、弹出提示管理(订阅引擎 ImeEffect 通道,驱动 PopupTipPanel)、工具列表管理(根据 keyboard.type 动态配置)、布局模式管理(KeyboardLayoutMode 运行时切换)、输入动作播放器集成(InputActionPlayer)以及布局状态缓存(供播放器坐标解析)。ViewModel 仅依赖引擎公开 API,不持有 InputConnectionBridge,不执行配置持久化,不创建或销毁引擎(详见 030-键盘视图模型)。

2.5 主题系统

组件包路径说明
KeyboardColorstheme颜色定义(键盘 / 候选 / 输入栏 / 弹出提示 / 工具栏)
KeyboardThemestheme预置主题(Light/Night
KeyboardThemetheme主题 Composable(支持跟随系统)
LocalKeyboardColorsthemeCompositionLocal 提供颜色

主题系统为所有 UI 组件提供统一的颜色体系。KeyboardColors 定义了键盘界面各区域的颜色变量,包括按键背景色、按键前景色、按键激活色、候选栏背景色、候选栏前景色、输入栏背景色、输入栏前景色、弹出提示背景色、弹出提示前景色、工具栏背景色等。KeyboardThemes 提供两套预置主题——亮色(Light)和暗色(Night),分别对应 KeyboardColors 的不同配色方案。KeyboardTheme 是顶层 Composable,根据 ImeConfig.ui.keyboardThemeType 选择主题,通过 CompositionLocalKeyboardColors 向下传递。第三方应用可以通过实现自定义的 KeyboardColors 完全替换配色方案,也可以通过 ImeConfig.ui 调整部分颜色参数。

2.6 按键生成组件

组件包路径说明
KeyTableGeneratorkeyboard按键布局生成器接口,根据键盘类型、输入模式、键盘状态和相关数据生成按键布局矩阵
KeyTableContextkeyboard按键生成上下文,包含 configkeyboardinputListcandidateList

KeyTableGenerator 是按键布局生成器接口(完整定义见 §5),归属于 :ui 模块。不同 KeyboardInputModeHexGridRectGrid)下实现不同——HexGrid 生成六边形排列的按键矩阵,RectGrid 生成矩形排列的按键矩阵。

2.7 工具组件

组件包路径说明
CoordinateNormalizerutil归一化坐标转换工具,在逻辑坐标与像素坐标之间双向转换

CoordinateNormalizer 是坐标归一化工具,在绝对像素坐标与归一化坐标 [0,1]x[0,1] 之间进行双向转换。归一化坐标使得手势反馈数据可以跨面板、跨 Zone 使用,无需关心面板的实际像素尺寸。GestureInputPanel 将触摸事件坐标归一化后写入 GestureFeedbackStateGestureFeedbackPanel 读取归一化坐标后根据面板实际尺寸反归一化绘制。ComposeInputActionPositionResolver 在解析候选项和输入项位置时,将像素坐标归一化后返回。归一化坐标的约定:(0, 0) 对应面板左上角,(1, 1) 对应面板右下角,X 轴向右为正,Y 轴向下为正。


3 组件层次关系

以下为组件层次树,展示 :ui 模块各组件之间的组合关系(详细设计见 020-面板三层分离与屏幕布局)。

KeyboardHost 是最顶层的集成组件,通过 KeyboardLayoutMode 统一 Stacked/Separated 入口。KeyboardHost 订阅 KeyboardViewModelstatelayoutModefeedbackStatepopupTipStatetoolListState 等状态,根据布局模式选择不同的组件部署方式。在 Stacked 布局下,所有面板集中在 Zone B 的三行结构中;在 Separated 布局下,KeyLayoutPanel 迁移到 Zone A,其余面板仍在 Zone B。KeyboardInputActionPlayerHostKeyboardHost 基础上叠加播放引擎,专用于输入动作演示。

组件层次的核心设计原则是「三层分离」——GestureInputPanel(触摸)、KeyLayoutPanel(渲染)、GestureFeedbackPanel(反馈)各自独立,通过 InputGestureGestureFeedbackStateImeState 解耦通信。指示器已内建到 CandidateListPanelInputListPanelToolListPanel 中,通过 showIndicator 参数控制,消除了独立覆盖层组件。PopupTipPanel 订阅 PopupTipState,支持 Message(纯文本,自动消失)和 Action(含操作按钮,可点击触发 ImeIntent)两种类型的弹出提示。

KeyboardHost
├── KeyboardTheme
│   └── KeyboardLayout (Stacked / Separated)
│       ├── Zone A (Separated 模式)
│       │   ├── KeyLayoutPanel
│       │   └── GestureFeedbackPanel
│       └── Zone B
│           ├── Row 1: CandidateListPanel + PopupTipPanel (叠加)
│           ├── Row 2: ToolListPanel ↔ InputListPanel (互斥)
│           └── Row 3: KeyLayoutPanel + GestureFeedbackPanel + GestureInputPanel (三层叠加)

KeyboardInputActionPlayerHost
├── KeyboardHost (含指示器参数)
└── InputActionPlayerPanel

KeyboardViewModel
├── state: StateFlow<ImeState>
├── config: ImeConfig
├── layoutMode: StateFlow<KeyboardLayoutMode>
├── feedbackState: GestureFeedbackState
├── popupTipState: StateFlow<PopupTipState?>
├── toolListState: StateFlow<ToolListState>
├── isInputting: Boolean
├── actionPlayer: InputActionPlayer
└── 布局状态缓存

4 引擎依赖

UI 库的所有组件仅依赖 :engine 的公开 API:

依赖的引擎 APIUI 库中的使用场景
ImeEngine.state: StateFlow<ImeState>所有 Compose 组件通过 collectAsState() 订阅状态驱动重组
ImeEngine.effect: SharedFlow<ImeEffect>KeyboardViewModel 订阅副作用通道,驱动 PopupTipState;收藏确认通过 PopupTip.Action 实现
ImeIntentKeyboardViewModelInputGesture 转换为 ImeIntent 后发送给引擎,ToolItem 点击直接发送 ImeIntentPopupTipState.Action 点击触发 ImeIntent
EditorAction不直接使用(通过 ImeEditorBridge 分发)
ImeEditorBridge / BaseImeEditorBridgeEditTextBridge 实现用于非系统 IME 场景
ImeEffectKeyboardViewModel 订阅引擎副作用通道,仅处理 PopupTip.MessagePopupTip.Action;音效与触觉在 gestureToIntent() 中直接处理
ImeConfig / ImeConfig.UiConfig主题系统、配置 UI 组件读取配置驱动界面呈现
KeyboardInputMode 枚举KeyLayoutPanel 布局策略选择、GestureInputPanel 手势识别逻辑。KeyboardInputModeKeyboardType:engine 中两个不同的概念:KeyboardType 是引擎的键盘分类(Pinyin/Latin/Symbol/Emoji/Number/Math),决定按键集合的语义内容;KeyboardInputMode 是输入交互范式分类(HexGrid/RectGrid),决定按键的几何排列和手势交互方式。二者正交组合
InputActionPlayerStateInputActionPlayer 播放状态管理
InputActionFingerIndicator手指指示器渲染,绘制代表手指的图形并跟随滑行轨迹移动,以及手指的点击动画(供 CandidateListPanel/InputListPanel/ToolListPanel 内建绘制及 GestureFeedbackPanel 绘制)
AudioType / HapticTypefeedback
AudioPlayer / HapticPlayerfeedback
InputActionPathInterpolatorInputActionPlayer 轨迹插值计算
InputActionPositionResolverComposeInputActionPositionResolver 实现的接口,将语义标识解析为归一化坐标
OffsetF / RectF归一化坐标类型,CoordinateNormalizerGestureFeedbackPanel 使用

这种依赖隔离是 UI 库「可替换」定位的技术保障。第三方应用可以用自己的 ViewModel 替换 KeyboardViewModel,只要正确将用户操作转换为 ImeIntent 发送给 ImeEngine、正确订阅 ImeEffect 副作用通道;同理,可以替换任何 :ui 组件,只要订阅 StateFlow<ImeState> 并正确渲染。UI 库不依赖引擎内部实现细节(如状态机的 reduce 逻辑、字典查询机制、剪贴板访问方式等),仅依赖引擎公开的类型和 API,确保引擎内部重构不会影响 UI 层。


5 KeyTableGenerator 接口

KeyTableGenerator 是按键布局生成器接口,根据 KeyTableContext 中的上下文信息生成按键布局矩阵。该接口的设计使得按键布局逻辑可以按 KeyboardInputModeKeyboardType 的组合进行策略分发——不同组合可以注册不同的 KeyTableGenerator 实现,由 KeyLayoutPanel 根据当前 config.ui.keyboardInputModekeyboard.type 选择合适的生成器。

kotlin
/**
 * 按键布局生成器接口。
 *
 * 根据 KeyTableContext 中的键盘类型、输入模式、键盘状态和相关数据
 * 生成按键布局矩阵 List<List<InputKey>>。
 *
 * 不同 KeyboardInputMode 和 KeyboardType 的组合可以注册不同的实现,
 * 由 KeyLayoutPanel 根据当前键盘状态选择合适的生成器。
 */
interface KeyTableGenerator {
    fun generate(context: KeyTableContext): List<List<InputKey>>
}

/**
 * 按键生成上下文。
 *
 * 包含按键布局生成所需的全部信息,
 * 生成器根据这些信息决定按键的数量、排列、标签和状态。
 */
data class KeyTableContext(
    /** 运行时配置 */
    val config: ImeConfig,
    /** 当前键盘实例(含 type、mode、state) */
    val keyboard: Keyboard,
    /** 当前输入列表 */
    val inputList: InputList,
    /** 当前候选列表 */
    val candidateList: CandidateList,
)

KeyTableGeneratorgenerate() 方法接收 KeyTableContext,返回 List<List<InputKey>> 二维矩阵。矩阵的外层 List 对应键盘的行,内层 List 对应每行中的按键。InputKey 是按键的语义标识,可以是 InputKey.Char(字符键)、InputKey.Ctrl(控制键)、InputKey.Candidate(候选键)、InputKey.MathOp(数学运算键)、InputKey.Symbol(符号键)或 InputKey.Null(空占位键)。KeyTableContext 提供了生成器所需的全部上下文:config 提供手模式、功能开关等配置信息;keyboard 提供当前键盘的类型、输入模式和状态机状态;inputList 提供当前输入内容,用于确定删除键、光标键等功能键的状态;candidateList 提供当前候选词列表,用于确定候选键的内容。

HexGrid 模式下,KeyTableGenerator 生成六边形排列的按键矩阵,相邻按键之间呈 60 度夹角,使得手指在六边形区域间滑行时路径更短。RectGrid 模式下,KeyTableGenerator 生成传统的矩形排列按键矩阵,每行按键等高对齐,适合 QWERTY 式布局。两种模式共享相同的 KeyTableContext 输入,但生成的按键矩阵在行列数和按键排列上有所不同,由 KeyLayoutPanel 负责将矩阵渲染为对应的视觉布局。