首页 / 资讯中心 / 文章详情

蒙古语菜名终于不乱码,营养公式却把卡片撑高:HarmonyOS 7 Noto 字体升级回归实战

蒙古语菜名终于不乱码,营养公式却把卡片撑高:HarmonyOS 7 Noto 字体升级回归实战 ★ FEATURED ARTICLE
蒙古语菜名终于不乱码营养公式却把卡片撑高HarmonyOS 7 Noto 字体升级回归实战升级系统后多语种菜名终于能正常显示了却出现了另一个容易漏掉的问题原本固定 64vp 的列表行开始拥挤蒙古语、天城文和缅甸文的字形更完整行高也可能不同另一张营养公式卡片里数学符号明显变大原来刚好够用的高度把下一行按钮顶了下去。这不是“字体修好了就结束”。HarmonyOS 7 的 UX 变更说明明确指出notofonts 三方件升级后蒙古语、天城体和缅甸语的错误显示得到修复同时 NotoSansMath 的数学符号会变大。对应用来说字符正确只是第一层验收文本测量、截断、卡片高度和列表稳定性才是第二层。本文交付两套可复用方案一套用于多语种菜名列表避免固定高度吞掉字形一套用于营养公式卡片让公式变大后仍能保住标题、说明和操作区。最后给出语言、窗口宽度、字体缩放和字体版本组成的 90 组回归矩阵并把宿主规划模型与目标环境实测严格分开。当前本机只有 API 24 SDK且没有连接 HDC 设备。文中的宿主断言只验证矩阵生成、布局预算和证据文件结构不声称已经完成 API 26 编译、HarmonyOS 7 真机字体渲染或设备性能测量。先确认系统到底改了什么HarmonyOS 7 UX 变更页在“notofonts 三方件小语种字体升级变更”中给出了四个直接影响界面的事实蒙古语字体使用 NotoSansMongolian-Regular.ttf修复此前显示不正确的问题。天城体字体使用 NotoSansDevanagari[wdth,wght].ttf修复此前显示不正确的问题。缅甸语字体使用 NotoSansMyanmar[wdth,wght].ttf修复此前显示不正确的问题。NotoSansMath-Regular.ttf 的数学符号会变大官方适配建议是根据实际布局调整。该变更从 API Level 11 起涉及应用。这里没有把它写成 targetSdkVersion 26 的隔离行为因为当前官方条目没有给出这层条件。文章标题中的 HarmonyOS 7 指系统变更所在版本不等于所有字体 API 都从 API 26 才可调用。字体文件变化会改变字形边界、基线、上伸部和下伸部。即使 fontSize 没变文本组件的实际占用高度也可能变化。固定高度、硬编码基线偏移、按中文样本估算两行高度都会在多语种内容里暴露问题。Text 能提供哪些布局约束ArkUI Text 组件可通过 maxLines 控制最大行数通过 textOverflow 指定超出后的处理lineHeight、lineHeightMultiple、minLineHeight 和 maxLineHeight可以约束行高。需要注意官方文档说明 lineSpacing 与有效的 lineHeightMultiple 同时设置时只有倍数生效不能把两者简单相加。TextController 从 API 12 起可以取得 LayoutManager。LayoutManager 能提供行数、字形位置、行信息和字符绘制区域等布局信息但如果控制器没有绑定 Text或者组件已经销毁getLayoutManager 可能返回 undefined。它适合做目标环境诊断不适合在组件尚未完成布局时被当作同步常量读取。constraintSize 用于限制组件的最小和最大宽高优先级高于 width 和 height。它适合给卡片设定“可伸缩范围”不适合继续复制固定高度问题。默认最小值是 0最大值为无限大所以如果业务需要可预测上限应显式设置并同时考虑可访问性字号。失败写法把中文样本的高度当成所有语言的高度下面这段看起来整齐但风险很集中BuilderrecipeRowBad(item:LocalizedRecipe){Row({space:12}){Image(item.cover).width(48).height(48).borderRadius(12)Text(item.name).fontSize(18).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis}).layoutWeight(1)}.height(64).padding({left:12,right:12})}问题不在 maxLines而在外层 Row 的 64vp 把文本可用高度锁死。中文短标题可能刚好一行蒙古语或天城文两行却需要更完整的上下边界字体升级后错误显示被修复反而更容易把固定高度的错误暴露出来。案例一多语种菜名列表让内容决定高度先定义语言脚本和测量结果。脚本分类用于测试矩阵与日志不用于替代系统字体选择typeScriptKindzh|mn|hi|myinterfaceLocalizedRecipe{id:stringlocale:stringscript:ScriptKind name:stringsubtitle:stringcover:ResourceStr}interfaceTextAreaEvidence{caseId:stringscript:ScriptKind|mathwidth:numberheight:numberfontScale:numbercapturedAt:number}把固定 height 改为最小高度和弹性内容StatetextEvidence:TextAreaEvidence[][]StatecurrentFontScale:number1privaterecordTextArea(caseId:string,script:ScriptKind|math,area:Area):void{constrow:TextAreaEvidence{caseId,script,width:Number(area.width),height:Number(area.height),fontScale:this.currentFontScale,capturedAt:Date.now()}constindexthis.textEvidence.findIndex((item)item.caseIdcaseId)if(index0){this.textEvidence[index]row}else{this.textEvidence.push(row)}}BuilderrecipeRow(item:LocalizedRecipe){Row({space:12}){Image(item.cover).width(48).height(48).objectFit(ImageFit.Cover).borderRadius(12)Column({space:4}){Text(item.name).fontSize(18).fontWeight(FontWeight.Medium).fontColor(#172321).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis}).constraintSize({minHeight:24,maxHeight:64}).onAreaChange((_oldArea:Area,newArea:Area){this.recordTextArea(recipe-name__item.script__item.id,item.script,newArea)})Text(item.subtitle).fontSize(14).fontColor(#667A75).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})}.layoutWeight(1).alignItems(HorizontalAlign.Start)}.width(100%).constraintSize({minHeight:72}).padding({left:12,right:12,top:10,bottom:10})}这里保留了 72vp 的最小触达与视觉节奏但没有设置最大行高给外层容器。标题最多两行副标题一行如果目标语言字形需要更多高度Row 可以随内容增长。图片仍保持 48vp不会跟着文本被拉伸。用 TextController 补充行级诊断onAreaChange 能给出 Text 组件最终区域TextController 则可补充布局行数。控制器必须绑定到对应 Text并在布局完成后读取privatenameController:TextControllernewTextController()privatereadNameLayout(caseId:string):void{constmanagerthis.nameController.getLayoutManager()if(!manager){console.info(layout not ready: caseId)return}constlineCountmanager.getLineCount()console.info(JSON.stringify({caseId,lineCount}))}Text(this.activeRecipe.name,{controller:this.nameController}).fontSize(18).maxLines(2).onAreaChange((_oldArea:Area,newArea:Area){this.recordTextArea(active-name,this.activeRecipe.script,newArea)this.readNameLayout(active-name)})示例只把它作为诊断入口。不同 API 版本的 LayoutManager 具体方法要以当前 SDK 声明为准如果本地 API 24 类型定义与目标 SDK 不一致应先在目标工程里核对类型再写入正式代码。第一组验收不只看“有没有乱码”多语种列表至少检查以下结果检查项失败信号通过条件字符显示方框、替代符、错误连接目标文本字形完整上下边界顶部或底部被切字形绘制区域未触碰裁剪边界两行标题第二行消失maxLines2 时两行可见大字体标题与副标题重叠卡片增长或副标题按规则省略列表稳定滚动时高度跳变同一内容复测高度一致案例二营养公式卡片允许数学符号变大营养计算里经常出现分式、根号、上下标和集合符号。NotoSansMath 升级后符号本身变大。如果公式区域固定 36vp再叠加标题和说明卡片就可能把后续操作挤出。错误写法通常是Column(){Text(每份能量估算)Text(E Σ(mᵢ × qᵢ) / n).fontFamily(Noto Sans Math).height(36)Button(重新计算)}.height(148)更稳妥的写法是给公式一段弹性区间并记录最终高度StatecompactFormula:booleanfalseprivateupdateFormulaDensity(height:number):void{this.compactFormulaheight112}BuildernutritionFormulaCard(formula:string,note:string){Column({space:10}){Row(){Text(每份营养估算).fontSize(18).fontWeight(FontWeight.Bold)Blank()Text(公式).fontSize(12).fontColor(#087767)}.width(100%)Text(formula).fontFamily(Noto Sans Math).fontSize(22).maxLines(3).textOverflow({overflow:TextOverflow.Ellipsis}).constraintSize({minHeight:36,maxHeight:112}).width(100%).onAreaChange((_oldArea:Area,newArea:Area){constheightNumber(newArea.height)this.updateFormulaDensity(height)this.recordTextArea(nutrition-formula,math,newArea)})if(!this.compactFormula){Text(note).fontSize(13).fontColor(#667A75).maxLines(2)}Button(重新计算).width(100%).onClick(()this.recalculateNutrition())}.width(100%).constraintSize({minHeight:152,maxHeight:280}).padding(16).backgroundColor(#F7FBFA).borderRadius(16)}公式区最多三行卡片本身允许在 152vp 到 280vp 之间增长。公式高度超过 112vp 时说明文字隐藏把空间留给公式和主操作。这个降级顺序比缩小公式字体更合理因为数学符号辨识度是主要内容不应先牺牲。公式字符串也要进入证据文件只记录高度还不够。公式不同符号边界也不同。建议给每条公式生成稳定摘要interfaceFormulaEvidence{caseId:stringformulaKey:stringwidth:numberfontScale:numbermeasuredHeight:numberlineCount?:numberscreenshotName:string}functionformulaKey(formula:string):string{lethash2166136261for(letindex0;indexformula.length;index){hash^formula.charCodeAt(index)hashMath.imul(hash,16777619)}returnformula-(hash0).toString(16)}证据文件里存摘要和测试输入不必把日志做成不可读的大段转储。截图名称应包含 caseId才能反查是哪种窗口和字号。90 组回归矩阵怎么组成为了同时覆盖小语种修复与数学符号变化矩阵采用五类脚本、三档宽度、三档字体缩放和两个字体版本维度取值脚本zh、mn、hi、my、math内容宽度320、480、720vp字体缩放1.0、1.3、1.8字体版本baseline、current5 × 3 × 3 × 2 90。baseline 代表升级前的对照记录current 代表当前系统记录。没有旧系统设备时不能用估算值冒充 baseline 实测可以先生成待测矩阵等目标环境补齐。typeEvidenceScriptzh|mn|hi|my|mathtypeFontVariantbaseline|currentinterfaceFontRegressionCase{id:stringscript:EvidenceScript width:320|480|720fontScale:1|1.3|1.8variant:FontVariant plannedMinHeight:number}functionbuildFontRegressionMatrix():FontRegressionCase[]{constscripts:EvidenceScript[][zh,mn,hi,my,math]constwidths:Array320|480|720[320,480,720]constscales:Array1|1.3|1.8[1,1.3,1.8]constvariants:FontVariant[][baseline,current]constrows:FontRegressionCase[][]scripts.forEach((script){widths.forEach((width){scales.forEach((fontScale){variants.forEach((variant){rows.push({id:[noto,script,width,fontScale,variant].join(__),script,width,fontScale,variant,plannedMinHeight:planMinHeight(script,fontScale)})})})})})returnrows}宿主规划模型只能做什么没有目标字体引擎时宿主模型不能计算真实字形高度。它只能检查用例完整性、布局预算单调性和异常输入functionplanMinHeight(script:EvidenceScript,fontScale:number):number{if(fontScale0)thrownewError(invalid-font-scale)constbasescriptmath?52:44constscriptReservescriptmn||scripthi||scriptmy?8:0returnMath.ceil((basescriptReserve)*fontScale)}这里的 44、52 和 8 都是用例预算不是系统字形度量。它们的作用是让大字号用例获得更高的初始空间避免测试页面本身因高度过小而无法操作最终通过与否必须由 onAreaChange、LayoutManager、截图和人工检查共同决定。宿主断言应验证生成 90 个唯一 caseId。每种脚本都有 18 条用例。每档宽度有 30 条用例。current 与 baseline 数量相等。fontScale 增大时规划高度不下降。math 的规划高度高于同字号中文用例。非法字号会被拒绝。目标环境复现步骤准备同一应用提交的对照版本记录系统版本、字体版本、设备或模拟器型号和字体缩放。在 320vp、480vp 和 720vp 内容宽度下分别打开多语种菜名列表。使用固定测试文本覆盖中文、蒙古语、天城文和缅甸语确认字符内容与编码来源一致。对每个 Text 记录 onAreaChange 的宽高布局完成后读取 LayoutManager 行数若为 undefined 则记录“未绑定或已销毁”不填伪值。切换 1.0、1.3、1.8 字体缩放检查标题上、下边界和副标题是否重叠。打开营养公式卡片覆盖分式、求和、根号、上下标和长公式五种输入。对 baseline/current 使用相同输入和相同窗口保存截图、Text 区域高度、卡片高度和行数。检查公式变大时卡片是否增长、说明是否按规则隐藏、主按钮是否仍可达。把每个 caseId 的期望状态、实际数据、截图名和结论写入 JSON缺少对照设备的 baseline 保持 pending。只有目标环境数据完成后才能下结论“字体升级未造成裁剪”或“公式卡片已适配”。建议的结果文件结构{environment:{systemVersion:待填写,sdkVersion:待填写,deviceModel:待填写,fontScale:1.3},caseId:noto__mn__320__1.3__current,input:{script:mn,widthVp:320,variant:current},measurement:{textWidth:null,textHeight:null,lineCount:null},visual:{glyphComplete:null,clipped:null,overlap:null,screenshot:noto__mn__320__1.3__current.png},result:pending}用 null 表示未测而不是填 0。0 会被误解成实际测量值null 才能明确区分“没有证据”。常见误区误区一字不乱码就算通过显示正确只是字符层验收。列表裁剪、行高跳变、公式卡片溢出仍然可能存在。误区二给所有语言统一 lineHeight统一 lineHeight 可能重新裁掉高字形。除非目标环境有完整证据否则优先使用自然行高加 min/max 约束。误区三同时设置 lineSpacing 和 lineHeightMultiple官方文档说明有效的 lineHeightMultiple 会使 lineSpacing 不生效。团队应选一种策略并在代码检查中固定。误区四在 build 阶段读取 LayoutManager控制器尚未绑定时可能返回 undefined。应在布局完成后的回调或明确的诊断动作里读取并处理空值。误区五把宿主预算写成设备结论宿主模型没有 Noto 字体引擎和目标系统不能证明实际字形高度。它只保证矩阵和证据结构没有遗漏。误区六只测中文和英文本次变化直接涉及蒙古语、天城文、缅甸语和数学符号。缺少这些输入就没有覆盖变更本身。文件级检查表系统版本、SDK 版本、设备型号和字体缩放已记录。测试文本来源与编码可追溯。中文、蒙古语、天城文、缅甸语和数学公式全部覆盖。列表行使用 minHeight 或 constraintSize没有固定高度裁剪两行文本。公式区允许增长主操作不会被挤出卡片。onAreaChange 记录 Text 实际区域不把父容器高度冒充文本高度。TextController 已绑定LayoutManager 空值有明确处理。320、480、720vp 三档宽度都完成。1.0、1.3、1.8 三档字体缩放都完成。baseline/current 使用相同输入缺少旧环境时保持 pending。截图名与 caseId 一一对应。宿主断言、API 26 编译、模拟器和真机证据分开记录。证据边界与官方资料HarmonyOS 7 UX 变更页证明了 notofonts 小语种字体升级、三类错误显示修复和 NotoSansMath 数学符号变大。Text 参考用于 maxLines、行高约束、TextController 与 LayoutManagerconstraintSize 参考用于卡片的最小/最大尺寸约束onAreaChange 参考用于记录组件实际区域。HarmonyOS 7UX 样式或效果的变更Text 文本组件与 TextControllerconstraintSize最小与最大尺寸约束onAreaChange组件区域变化事件
阅读完成 · 觉得有帮助?
咨询建站