鸿蒙 PC Markdown 编辑器搜索选项响应式布局:220 vp 侧栏中的完整中文控件

可调侧栏上线后,一个之前被固定宽度掩盖的问题立即出现:侧栏缩到 220 vp,工作区搜索下方“区分大小写、全词匹配、正则表达式”仍强制放在一行,最右侧文字被裁掉。功能状态仍在,用户却无法完整确认自己勾选的含义。对于搜索这种可能影响全工作区结果的操作,标签不可见不是轻微视觉瑕疵,而是会造成错误判断的可用性问题。

修复已进入公开仓库 https://gitcode.com/VON-/codex_md_oh,实现提交为 0d8d38b,此前可调侧栏基础提交为 358eb3f。本文基于真实 ArkUI 代码和 HarmonyOS MateBook Pro 2in1 模拟器证据,说明为什么采用 300 vp 稳定断点、窄宽两行与宽度单行的组合,怎样保证当前文档搜索和工作区搜索复用同一状态,以及怎样用辅助功能边界而不只是截图验证文字完整。

问题由容器能力触发,而不是由翻译触发

英文标签是 Case / Whole / Regex,默认 264 vp 侧栏中较容易放下;中文标签是“区分大小写 / 全词匹配 / 正则表达式”,总宽度明显更长。固定侧栏时期,部分窗口和字体环境已经接近边界,只是没有稳定复现。用户获得自由调整能力后,220 vp 最小值使隐藏假设成为确定 bug。

不能把原因归咎于中文“太长”。产品既然支持简体中文和 220 vp 侧栏,就必须让两个条件同时成立。缩小字号、截断标签或只在英文允许最小宽度都会降低可读性并制造语言不平等。正确方向是让容器根据可用宽度改变排列。

响应式修复还要避免另一端浪费。480 vp 宽侧栏若仍固定两行,会占用不必要的纵向空间,搜索结果列表可见项减少。因此需要明确断点,在窄侧栏完整展示,在宽侧栏恢复紧凑。

三个选项共享的业务状态

WorkspaceShell 用三个布尔状态表示搜索约束:searchCaseSensitivesearchWholeWordsearchRegularExpression。当前文档搜索和工作区全文搜索复用这组状态;快速打开不使用正文匹配选项。布局不应创建第二组窄版状态。

@State private searchCaseSensitive: boolean = false;
@State private searchWholeWord: boolean = false;
@State private searchRegularExpression: boolean = false;

private updateSearchCaseSensitive(value: boolean): void {
  this.searchCaseSensitive = value;
  this.searchMatchCount = 0;
  if (this.searchPanelMode === SearchPanelMode.WORKSPACE) {
    this.invalidateWorkspaceSearchResults();
  }
}

更新大小写时,当前文档匹配计数归零,下一次查找按新条件执行;工作区模式还会使已有结果失效,因为列表基于旧条件。Whole Word 和 Regex 使用同样的更新语义。响应式分支只选择 Builder 排列,回调仍指向同一更新函数。

这保证侧栏从 299 拖到 300 vp 时,Checkbox 可能重新构建布局,但业务值不重置,已有状态不会悄悄变成 false。布局是视图,不是新的搜索会话。

为什么选择 300 vp 断点

断点不是设备型号,也不是随意的“移动端/桌面端”。它直接基于当前控件宽度、14 vp 左右内容 padding、18 vp Checkbox、4 vp Row 间距和中英文最长标签测得。220 至 299 vp 使用两行,300 vp 及以上三项能在当前字体下稳定单行。

const SEARCH_OPTIONS_SINGLE_ROW_MIN_WIDTH: number = 300;

使用具名常量让意图明确,也方便未来资源文案、字体或显示缩放变化时调整。若把 300 散落在 Builder 分支和测试里,后续会出现阈值不一致。名称描述“搜索选项单行最小宽度”,而不是笼统 BREAKPOINT_SMALL,因为它只服务这一局部布局。

断点判断使用 sidebarWidth,不是整个窗口宽度。窗口可以很宽但用户主动把侧栏调到 220,仍应两行;窗口变窄导致侧栏隐藏时,选项不显示,无需布局。以真正的父容器宽度为条件,是可组合组件的基本原则。

窄侧栏的两行结构

小于 300 vp 时,外层 Column 固定 52 vp 高度,两行各 24 vp,中间 4 vp。第一行放“区分大小写”和“全词匹配”,用 Blank 分隔到两端;第二行只放“正则表达式”。

if (this.sidebarWidth < SEARCH_OPTIONS_SINGLE_ROW_MIN_WIDTH) {
  Column({ space: 4 }) {
    Row() {
      this.searchOption('search-case', $r('app.string.match_case'),
        this.searchCaseSensitive,
        (value: boolean) => this.updateSearchCaseSensitive(value))
      Blank()
      this.searchOption('search-whole', $r('app.string.match_whole_word'),
        this.searchWholeWord,
        (value: boolean) => this.updateSearchWholeWord(value))
    }
    .width('100%')
    .height(24)

    Row() {
      this.searchOption('search-regex',
        $r('app.string.use_regular_expression'),
        this.searchRegularExpression,
        (value: boolean) => this.updateSearchRegularExpression(value))
    }
    .width('100%')
    .height(24)
  }
  .width('100%')
  .height(52)
}

为什么不是让 Flex 自动 wrap?三个选项重要性和长度已知,固定分组能保证中文与英文在临界宽度的顺序稳定,不会因文本测量细微变化产生一项/两项随机换行。用户建立的肌肉记忆是前两项在第一行、Regex 在第二行。

固定高度避免拖过 300 时父布局反复测量造成内容抖动。断点跨越时高度从 52 变为 24,这是明确模式变化;同一模式内标签和选中状态都不会改变高度。

宽侧栏恢复单行紧凑布局

达到 300 vp 后,三个选项进入同一 Row,中间两个 Blank 分配剩余空间。每项保持自身自然宽度,不用固定三等分导致中文标签内部空间不足。

Row() {
  this.searchOption('search-case', $r('app.string.match_case'),
    this.searchCaseSensitive,
    (value: boolean) => this.updateSearchCaseSensitive(value))
  Blank()
  this.searchOption('search-whole', $r('app.string.match_whole_word'),
    this.searchWholeWord,
    (value: boolean) => this.updateSearchWholeWord(value))
  Blank()
  this.searchOption('search-regex', $r('app.string.use_regular_expression'),
    this.searchRegularExpression,
    (value: boolean) => this.updateSearchRegularExpression(value))
}
.width('100%')
.height(24)

408 vp 模拟器证据中,三项文本纵坐标都为 627-651,证明恢复同一行。宽度更大时 Blank 吸收额外空间,选项不会被拉成巨大的点击卡片,也不会聚成难以区分的一团。

一行模式减少 28 vp 垂直占用,结果列表能显示更多内容。PC 工具界面重视信息密度,但密度必须在文字完整之后;断点正是两者的转换条件。

每个选项的内部尺寸必须稳定

searchOption 使用 Row,左侧 Checkbox 固定 18 × 18 vp,右侧 Text 字号 11 且 maxLines(1)。文字本身永不在复选框旁折成两行,换行责任完全由外层 searchOptions 承担。

@Builder
private searchOption(name: string, label: Resource,
  selected: boolean, action: (value: boolean) => void) {
  Row({ space: 4 }) {
    Checkbox({ name: name })
      .select(selected)
      .width(18)
      .height(18)
      .selectedColor('#087A63')
      .onChange(action)

    Text(label)
      .fontSize(11)
      .fontColor($r('app.color.workspace_text_normal'))
      .maxLines(1)
  }
}

如果允许 Text 自己换行,中文“区分大小写”可能在 220 vp 被拆成两行,但 Row 高度仍是 24,文字会裁切或与下一行重叠。maxLines(1) 强制暴露空间不足,再由容器分组解决,职责更清晰。

Checkbox 的 name 稳定且三项不同,方便状态识别和自动化。选中颜色保持工作台绿色强调,不改变尺寸。点击标签是否能切换取决于 Row/Checkbox 交互范围,当前设备路径主要点击复选框;后续可将整项变成统一可点击语义以改善命中。

不能用缩小字体解决

将 11 vp 缩到 9 vp 可能暂时放下中文,但会破坏可读性、显示缩放和无障碍,也无法保证未来术语不再变长。动态按宽度缩放字体更差:侧栏拖动时文字不断变形,三个选项字号可能不同,PC 工具表面显得不稳定。

省略“正则表达式”为“正则”会降低术语精度,英文缩写 Regex 尚可,但中文产品词典已选定完整表达。图标替代也不合适,大小写、全词、正则没有足够普遍且无歧义的单一符号,仍需 tooltip 和无障碍文本。

两行布局只增加有限高度,却保留完整文案和字号,是当前最低复杂度且最稳健的方案。宽侧栏自动回到单行,不让常用桌面尺寸长期承担额外高度。

不能用水平滚动掩盖选项

把选项 Row 改成横向 Scroll 会保留文字,但用户需要滚动才能发现最右侧 Regex,当前设置状态不再一眼可见。搜索条件属于执行前必须确认的信息,不应藏在无滚动条的横向轨道里。

水平滚动还会与侧栏整体滚动、触控板手势和拖动分隔线产生交互竞争。三项只有固定少量,换一行比引入新的滚动容器更直接。PC 操作面板应优先让关键开关同时可见。

同样不应把第三项移到更多菜单。Regex 是专业 Markdown 编辑器高频能力,菜单会增加点击路径,也让当前是否启用难以持续感知。响应式排列保留了完整功能显性。

搜索模式切换与结果失效

搜索面板有当前文档、工作区和快速打开三种模式。Current 和 Workspace 使用三个选项;Quick Open 按文件名模糊排序,不显示或不应用正文匹配语义。模式切换会取消旧任务、清理结果、重置选中索引和匹配计数。

private setSearchPanelMode(mode: SearchPanelMode): void {
  if (this.searchPanelMode !== mode) {
    this.workspaceSearchRequestSequence += 1;
    this.workspaceSearchController.cancel();
    this.workspaceSearchRunning = false;
    this.workspaceSearchResults = [];
    this.workspaceSearchSelectedIndex = 0;
    this.searchMatchCount = 0;
  }
  this.searchPanelMode = mode;
}

响应式布局不参与取消逻辑。侧栏宽度改变不会把正在进行的工作区 TaskPool 搜索当作新查询,也不会重置选项。用户可在搜索后拖宽阅读结果,结果列表继续存在;只有选项值真正变化时才 invalidation。

这一区分避免把视觉断点误当业务断点。否则每次拖过 300 都取消搜索,用户会认为侧栏调节导致数据丢失。

中英文资源让断点接受双重压力

三个标签来自 $r('app.string.match_case')match_whole_worduse_regular_expression。base 分别为 CaseWholeRegex,zh_CN 为完整中文。布局组件不判断语言,只判断实际容器宽度。

当前断点按更长中文设计,所以英文在 220 vp 也使用两行,虽然理论上可能放下一行。这种一致性避免切换语言时布局模式突然变化,也简化测试。若未来希望英文利用更紧凑宽度,应使用实际测量而不是语言 if,但复杂度是否值得需要产品数据。

增加德语等更长语言时,300 vp 可能不够。资源扩展必须重新测试最窄和断点边界,必要时把断点提高或改为更稳健的测量布局。当前只支持中英文,不能把现有值宣传为所有语言通用。

真实模拟器截图与边界数据

下面截图来自 MateBook Pro 2in1 模拟器,侧栏固定到最小 220 vp。前两项位于第一行,“正则表达式”位于第二行,三个中文标签都完整可读,没有与分隔线或编辑器重叠。

在这里插入图片描述

辅助功能树给出精确边界:区分大小写 [706,627][811,651],全词匹配 [926,627][1010,651],正则表达式 [706,680][811,704]。第三项纵坐标明显在第二行且右边界完整位于侧栏内。

把侧栏扩大到 408 vp 后,三项纵坐标均为 627-651,证明布局恢复单行。相比仅凭截图“看起来没截断”,边界数据能验证文本节点完整存在且模式按断点切换。

可调侧栏和窗口变化的联合测试

用户可以用鼠标连续拖动,也可以方向键每次调整 16 vp。测试需要检查 299 与 300 的临界点,避免因为浮点尺寸或 areaChange 在阈值附近反复切换。当前状态使用 number vp 和 < 300 判断,300 精确进入单行。

窗口缩小时动态 clamp 可能收紧侧栏;小于 900 vp 则侧栏整体隐藏。重新展开后使用当前有效宽度选择布局。Preferences 保存的是钳制后的有限数值,重启 220 或 392 都能恢复对应模式。

拖动过程中跨过断点会发生一次高度变化,下面的结果列表起点随之移动。这是明确的响应式变化,不是重载。搜索状态和 Web 编辑器不受影响。后续可评估轻微过渡,但操作型工具优先保证即时、稳定,避免动画延迟命中。

自动化、构建与设备结果

最终验证包括 Playwright 30/30、Web TypeScript 与离线单 HTML 构建、Debug HAP、ArkTS UnitTestBuild、ohosTest HAP、MateBook Pro 2in1 模拟器 ohosTest 7/7、中英文资源键集合一致和 git diff --check

最终 Debug HAP 为 1,520,352 字节,SHA-256 367ab8650479aa1fa8fe73bd1ebadd9a53f46659c850c2e388fc799d5cb88e5b;ohosTest HAP 为 2,360,824 字节,SHA-256 b7230037b51044fe16168d2c835fb891e1c70f675941a1046165bc895217592c。两份都是未签名测试产物。

Playwright 主要保护 Web 搜索和命令回归,ArkTS 构建保护 Builder 类型,模拟器边界数据证明原生布局。当前尚未增加专门的 ArkUI 自动测试在 299/300 自动截图,这是后续质量增强点;本轮已有真实 220 与 408 设备证据。

性能、焦点和无障碍

布局分支只依赖 sidebarWidth,拖动每帧会重新评估并在跨过 300 时切换结构。三个小控件的重建成本固定,不读取文件、不触发搜索。大工作区 TaskPool 和 CodeMirror 文档不参与这次布局。

Checkbox 状态来自外部 @State,重建不会丢选中。Text 完整进入辅助功能树,验证中三个标签都可读。当前应继续增强整行点击和明确的 checked 朗读测试,确保不同输入方式一致。

焦点位于某个 Checkbox 时跨断点重建可能影响焦点连续性,现有设备测试未记录这一极端路径。用户通常在拖动分隔线时焦点位于调整柄,但键盘用户可能先勾选再调整;后续应把它纳入无障碍回归。文章不把未测焦点保持写成已完成。

已知限制与演进

300 vp 是当前中英文、当前字体和布局 padding 下的稳定断点,不是永恒常量。系统大字体、更多语言或新选项可能要求三行、网格或可折叠高级选项。新增搜索条件时不能继续塞进现有 Row。

当前窄模式第二行只有一个选项,视觉上左对齐。未来第四项可与 Regex 配对,但需要按语义组织,不应只追求对称。高级搜索若增加 glob、排除目录等输入,更适合独立区域而不是复选框平铺。

搜索选项还未持久化,切换应用重启会回到 false;这是当前产品选择,避免用户忘记 Regex 打开导致下次搜索异常。若未来持久化,应提供明显状态和恢复默认,不应复用侧栏宽度的持久化假设。

结论

0d8d38b 解决的不是一个中文标签裁切,而是可调容器中的响应式语义:小于 300 vp 用稳定两行保留完整文字,达到 300 vp 恢复单行密度,三个 Checkbox 始终复用同一搜索状态和更新函数。220 与 408 vp 的真实模拟器边界证明两个模式都按预期工作。

对鸿蒙 PC Markdown 编辑器而言,自由调整工作空间必须与本地化、焦点、搜索状态和结果列表一起验收。容器能缩放只是第一步,内部每个高价值控制项都要在最小尺寸仍可读、可点、可解释,这才算真正完成 PC 适配。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐