在 2in1 宽屏窗口里,触摸、鼠标和键盘会交替出现。山海万灵的卡片、筛选和详情入口需要把“看见焦点”“暂时悬停”“真正选中”分开处理,才能让鼠标点击、Enter 和 Space 进入同一条业务命令。本篇给出键鼠交互补齐的设计方案,范围覆盖输入状态、焦点顺序、统一激活和窗口变化后的恢复。

一、先确定交互边界

方案服务于首页、图鉴、探索、馆长和“我的”中的可操作元素:一级导航、筛选项、神兽卡片、推荐卡片、详情中的继续探索动作,以及设置项。触摸手势、既有视觉主题和五个一级导航保持原状;本轮不引入新的业务流程,也不把宽屏适配等同于键鼠能力。

鼠标悬停只提供短暂视觉反馈,键盘焦点只服务于可访问性和键盘操作,业务选中态才决定详情、筛选结果或探索目标。三种状态混用时,鼠标移出可能错误清掉业务选择,窗口切换也会让焦点回落到不可操作位置。

二、把输入状态显式建模

type InputMode = 'touch' | 'mouse' | 'keyboard'

interface FocusState {
  inputMode: InputMode
  focusedId: string
  hoveredId: string
  selectedId: string
}

interface ActivationRequest {
  id: string
  source: 'click' | 'keyboard'
}

focusedIdhoveredIdselectedId 分别保存焦点、悬停和业务选择。页面切换、数据刷新或尺寸变化时,只恢复仍然存在且可用的焦点目标;找不到目标时,回退到所属区域第一个可操作控件。

三、统一鼠标与键盘的激活入口

卡片点击与键盘确认不应各自维护导航、发现或统计更新逻辑。组件负责把输入转换为 ActivationRequest,领域命令负责完成一次动作,页面只接收结果并刷新选择、提示和进度。这样可以让后续的新增输入设备继续复用同一条链路。

function onCardKey(event: KeyEvent, id: string): void {
  if (event.keyCode === KEY_ENTER || event.keyCode === KEY_SPACE) {
    activate({ id, source: 'keyboard' })
  }
}

async function activate(request: ActivationRequest): Promise<void> {
  if (!isEnabled(request.id)) return
  const result = await appViewModel.dispatch(request)
  viewState.selectedId = request.id
  notice = result.notice
}

四、键鼠设计链路

下图将输入事件、统一激活、业务状态与可恢复反馈拆开。卡片不再直接根据输入设备修改业务状态;同一个激活命令可以在点击和键盘确认之间复用,禁用项和已卸载目标则进入明确的降级路径。

山海万灵 2in1 键鼠交互补齐设计链路

五、焦点顺序与视觉顺序一致

页面按“一级导航 → 页内筛选 → 主列表或主要内容 → 辅助操作”的阅读路径建立焦点顺序。Tab 前进、Shift+Tab 回退;卡片列表在需要时使用方向键完成同层移动;Escape 返回上级区域或关闭轻量弹层。焦点环应独立于选中背景,深浅色主题分别校验对比度,避免只靠透明度造成不可见反馈。

场景 风险 设计处理
焦点顺序与视觉顺序不一致 键盘用户失去位置感 建立显式顺序并在页面评审中逐项核对
悬停色在深色模式不明显 鼠标目标不可辨识 为 hover 与 focus 分别定义对比度和边框规则
禁用控件仍可聚焦 确认键触发无效动作 禁用同时影响可聚焦性和激活入口
宽窄窗口切换卸载目标 焦点丢失 回退到所属区域首个可操作元素

六、失败与降级路径

输入设备暂不可用、目标项被刷新移除、动作被权限或业务条件拒绝时,页面保留已选业务内容,不把临时 hover 当成选中结果。焦点回退后给出可读提示;重复确认不重复累计发现或印章;无可用目标时将焦点停在页内说明区域,并提供返回或重试入口。

function recoverFocus(previousId: string, candidates: string[]): string {
  if (candidates.includes(previousId)) return previousId
  return candidates.find(isEnabled) ?? ''
}

function onTargetRemoved(previousId: string): void {
  viewState.focusedId = recoverFocus(previousId, visibleActionIds())
  hoverState.clear()
}

七、实施顺序

  1. 盘点五个一级页面的可操作元素和视觉阅读顺序。
  2. 为共用卡片补齐 hover、focus、disabled、selected 四套状态契约。
  3. 接入统一激活命令,再将点击、Enter 与 Space 接到同一入口。
  4. 为 Tab、方向键和 Escape 建立页面级规则。
  5. 在 2in1 或桌面窗口环境进行输入切换与窗口缩放回归。

八、验收清单

  • 鼠标悬停不会修改业务选中态。
  • Tab 顺序与视觉阅读顺序一致,Shift+Tab 能稳定回退。
  • Enter、Space 与点击复用同一业务命令。
  • 禁用控件不会获得可激活焦点。
  • 窄窗与宽窗切换后焦点能恢复到可操作目标。
  • 深浅色主题下的焦点环和悬停态均可辨识。

九、后续验证计划

落地后以键盘事件与 hover/focus 处理代码、2in1 或桌面窗口操作录屏、Tab 顺序与 Enter 激活日志、窗口切换后的焦点恢复画面为验收证据,并完成 HarmonyOS 构建和正式包运行复核。相关能力以实际交互结果为准。

参考:HarmonyOS 响应式布局指南

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐