news 2026/6/15 22:40:50

从一个推荐问题卡片重构,聊聊 HarmonyOS UI 复刻里最容易踩的坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从一个推荐问题卡片重构,聊聊 HarmonyOS UI 复刻里最容易踩的坑

从一个推荐问题卡片重构,聊聊 HarmonyOS UI 复刻里最容易踩的坑

最近在重构一个聊天页里的推荐问题卡片。

这个卡片看起来不复杂:上面是一个带机器人头像的 Header,下面是 4 条推荐问题。点击问题后,把问题填入输入框并发送消息。

但真正开始对照设计稿还原时,发现它并不是“调几个宽高和颜色”这么简单。尤其是渐变背景、渐变文字、卡片裁剪、头像悬浮、列表高度这些细节,很容易一边改 UI,一边把原来的业务逻辑也改乱。

这篇文章就用这个推荐问题卡片作为例子,记录一下这次重构里踩到的几个点。

先看原来的问题

这个卡片的核心业务逻辑其实很简单:

ForEach(this.vm.quickPhrases.slice(0,4),(question:string)=>{Row(){Text(question)Image($r('app.media.ic_arrow_right_thin'))}.onClick(()=>{this.vm.userInput=questionthis.vm.sendMessage(true)})})

也就是说:

  • 推荐问题来自vm.quickPhrases
  • 最多展示 4 条
  • 点击后设置userInput
  • 然后调用sendMessage(true)

这部分本身没有问题。

真正的问题出现在 UI 重构时:我一开始用了函数去动态计算列表高度,但后来发现这个列表本身就是固定只展示 4 条,动态计算反而把问题复杂化了。

如果业务已经明确“最多展示 4 条”,那列表高度完全可以围绕这 4 条去设计,而不是额外引入一套高度计算逻辑。

UI 重构时最重要的一点是:不要为了还原样式,顺手改掉原来的业务结构。

UI 重构不是重写业务

这次重构里,我尽量保留了原来的业务逻辑:

this.vm.quickPhrases.slice(0,4)

这一句没有动。

点击逻辑也没有动:

this.vm.userInput=questionthis.vm.sendMessage(true)

真正改的是展示层:

Text(question).fontSize(14).lineHeight(14).fontWeight(FontWeight.Regular).fontColor(AgentTheme.colorTextHeavy).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})

这里有一个小变化:之前问题文本是maxLines(1),现在改成了maxLines(2)

这个变化不是业务变化,而是 UI 变化。因为设计稿里推荐问题允许两行展示,如果还保持一行,就会导致长问题被过早截断。

所以我的理解是:

  • 数据来源不动
  • 循环方式不动
  • 点击行为不动
  • 只根据设计稿调整展示规则

这才是一次比较安全的 UI 重构。

不要盲目相信 AI:UI 复刻里也会有幻觉

这次还有一个很现实的感受:用 AI 辅助写 UI 代码时,不能完全相信它。

哪怕我已经明确告诉 AI:

只重构 UI,不要动原来的业务逻辑。

它还是可能会偷偷改掉一些看起来“不重要”的东西。

比如原来的列表逻辑其实很明确:

this.vm.quickPhrases.slice(0,4)

也就是最多展示 4 条推荐问题。

但 AI 很容易觉得这里需要“更通用”,于是帮你加动态高度计算,或者封装一个根据列表数量计算高度的函数。

问题是,这个组件在当前设计里就是固定展示 4 条。动态计算不但没有提升代码质量,反而让代码变复杂了。

还有一个例子是文本行数。

设计稿里推荐问题可以展示两行,所以这里应该是:

.maxLines(2)

但 AI 可能会根据常见列表项习惯,自动写成:

.maxLines(1)

从代码上看,这不算明显错误;但从 UI 复刻角度看,它就是错的。因为一行会导致长问题提前截断,和设计稿不一致。

所以我后来意识到,AI 辅助 UI 复刻时最危险的不是语法错误,而是这种“看起来合理,但和当前需求不一致”的改动。

它可能会:

  • 把固定高度改成动态高度
  • 把固定展示 4 条改成适配任意数量
  • maxLines(2)改成常见的maxLines(1)
  • 为了代码“优雅”额外封装函数
  • 为了“通用性”改变原本简单直接的结构
  • 在没理解设计稿的情况下,自动补一些它认为合理的样式

这些都属于 UI 复刻里的幻觉。

因为 UI 复刻不是自由发挥,它的目标不是写一个“差不多能用”的组件,而是尽量贴近设计稿。

所以和 AI 协作时,我觉得要反复强调几件事:

不要改数据来源。 不要改循环逻辑。 不要改点击逻辑。 不要把写死的设计改成动态计算。 不要为了通用性牺牲还原度。 除非设计稿明确不同,否则保留原来的业务代码。

但光说还不够,最后还是要自己逐行检查。

尤其是这些地方:

this.vm.quickPhrases.slice(0,4).maxLines(2).onClick(()=>{this.vm.userInput=questionthis.vm.sendMessage(true)})

这些代码看起来普通,但它们就是组件的业务边界。

UI 可以重构,布局可以调整,渐变可以慢慢调,但这些边界不能随便变。

这也是我这次学到的一个经验:AI 可以帮我更快写代码,但不能替我判断设计意图。复刻 UI 的时候,最终还是要靠自己确认:

  • 这个改动是不是设计稿要求的?
  • 这个改动有没有影响原业务逻辑?
  • 这个封装是不是真的必要?
  • 这个“优化”是不是只是 AI 自己脑补出来的?

AI 很适合帮忙生成第一版代码,也适合解释复杂属性,比如blendMode、渐变、裁剪、层级关系。

但涉及业务边界和设计还原时,不能盲信。

为什么渐变色这么难还原

这次最折磨的是 Header 背景渐变。

设计稿里看起来只是一个很淡的蓝紫色背景,但实际实现时会发现它不是单纯的一个颜色,而是多层效果叠出来的:

Column().width('100%').height(160).backgroundColor('#66FFFFFF').linearGradient({angle:118,colors:[['#1A7385ED',0.0],['#182793FF',0.30],['#0D00AAFF',0.59],['#0000AAFF',0.81],['#0000AAFF',1.0]]})

这里最容易看不懂的是颜色前面的两位透明度。

例如:

#66FFFFFF #1A7385ED #0000AAFF

它们不是普通的 RGB,而是 ARGB:

#66FFFFFF 表示 40% 透明度的白色 #1A7385ED 表示 10% 透明度的蓝紫色 #0000AAFF 表示完全透明的蓝色

也就是说,设计稿里的颜色很多时候不是“蓝色”,而是“带透明度的蓝色”。

如果只看后六位,很容易误判颜色浓度。

比如:

#7385ED

看起来是一个很明显的蓝紫色。

但实际设计稿用的是:

#1A7385ED

它只有大约 10% 的透明度,所以最终看到的是非常淡的蓝紫雾感。

单层渐变不够,就用图层思维

一开始我尝试只用一层渐变去还原 Header。

但很快遇到一个问题:设计稿里上半部分靠近机器人之前已经比较白了,而下半部分的蓝色又要延伸到文字附近。

这意味着它不是一个简单的从左到右渐变。

最后更合理的做法是拆成两层。

第一层负责蓝紫色底色:

Column().width('100%').height(160).backgroundColor('#66FFFFFF').linearGradient({angle:118,colors:[['#1A7385ED',0.0],['#182793FF',0.30],['#0D00AAFF',0.59],['#0000AAFF',0.81],['#0000AAFF',1.0]]})

第二层负责右上角的白色雾化:

Column().width('100%').height(160).linearGradient({angle:65,colors:[['#00FFFFFF',0.0],['#00FFFFFF',0.34],['#26FFFFFF',0.48],['#66FFFFFF',0.62],['#B3FFFFFF',0.78],['#F2FFFFFF',0.92],['#FFFFFFFF',1.0]]})

这样做的好处是:

  • 蓝紫色负责整体氛围
  • 白色渐变负责虚化
  • 两层都覆盖完整 Header,不会产生横向断层

之前我尝试过只给上半部分加白色遮罩,比如height(88),结果中间很容易出现一条明显的分界线。后来才意识到,雾化层最好不要半截结束,而是完整覆盖 Header,通过渐变位置来控制视觉范围。

渐变文字是怎么实现的

这个卡片里还有一处比较难理解的地方:渐变文字。

代码大概是这样:

Column(){Text(this.vm.config.welcomeDescription).width('100%').height(48).fontSize(12).lineHeight(16).fontWeight(FontWeight.Regular).blendMode(BlendMode.DST_IN,BlendApplyType.OFFSCREEN)}.width('100%').linearGradient({direction:GradientDirection.Right,colors:[['#FF7385ED',0.33],['#FF2793FF',0.66],['#FF00AAFF',1.0]]}).blendMode(BlendMode.SRC_OVER,BlendApplyType.OFFSCREEN)

这里不是直接给Text设置渐变色。

它的思路更像是:

  1. 外层先画一层渐变背景
  2. 文字作为遮罩
  3. 只保留文字形状里的渐变颜色

所以会用到BlendMode.DST_INBlendApplyType.OFFSCREEN

简单理解就是:先有渐变,再用文字把渐变裁出来。

这种写法刚开始看会比较绕,但它解决的是一个很常见的问题:普通文字颜色只能设置纯色,而设计稿里需要渐变文字。

卡片裁剪和头像悬浮

这个卡片还有一个细节:机器人头像是悬浮在右上角的,超出了卡片主体。

所以最外层不能直接裁剪:

Stack(){// 卡片主体// 机器人头像}.width('100%').clip(false)

如果最外层设置了clip(true),机器人头像超出的部分就会被裁掉。

但是 Header 自己需要圆角裁剪:

Stack(){// Header 背景// Header 内容}.width('100%').height(160).borderRadius({topLeft:24,topRight:24,bottomLeft:0,bottomRight:0}).clip(true)

这里就有一个层级关系:

  • 最外层Stack不裁剪,保证头像能露出来
  • Header 自己裁剪,保证顶部圆角正确
  • 列表区域自己设置背景和圆角,盖住 Header 底部 24vp

这种结构比单纯一个大Column更适合做悬浮元素。

列表区域为什么要覆盖 Header

设计稿里 Header 和问题列表不是硬切开的,而是列表区域向上覆盖了一点 Header:

.margin({top:-24})

这 24vp 的负边距很关键。

它让下面的白色列表区域“压”到 Header 上面,形成一种卡片融合的效果。

列表本身再设置圆角:

.borderRadius(24).clip(true)

这样看起来就像下面的白色区域从 Header 里长出来,而不是上下两块生硬拼接。

这次重构后的经验

这次卡片重构下来,我最大的感受是:UI 复刻不能只盯着某一个属性改。

尤其是渐变这种东西,单看一行颜色值意义不大,必须结合:

  • 图层顺序
  • 透明度
  • 渐变方向
  • 渐变停靠点
  • 组件裁剪范围
  • 背景色
  • 上层元素遮挡关系

同一个颜色值,在不同背景上显示出来的效果完全不一样。

这也是为什么渐变色会显得特别“阴间”:它不是一个颜色问题,而是一个图层合成问题。

总结

这次推荐问题卡片重构,表面上是在调 UI,实际上让我理解了几个更重要的点。

第一,UI 重构时不要轻易动业务逻辑。像quickPhrases.slice(0, 4)、点击发送这些逻辑本来就是稳定的,应该尽量保留。

第二,固定展示 4 条的问题列表,就不要额外引入动态高度计算。能写简单,就不要把问题复杂化。

第三,使用 AI 辅助 UI 复刻时不能盲信。AI 可能会把固定逻辑改成动态逻辑,也可能会自动把maxLines(2)改成maxLines(1),这些看起来合理的小改动都会影响最终还原效果。

第四,渐变色不能只看 RGB,还要看透明度。#1A7385ED#7385ED不是一个视觉效果。

第五,复杂渐变要用图层思维。底色、蓝紫渐变、白色雾化层应该分开处理,而不是强行塞进一层渐变里。

第六,裁剪要分层处理。外层为了头像悬浮不能裁剪,Header 为了圆角必须裁剪。

这类 UI 复刻一开始会觉得很细、很烦,但真正拆开之后,其实它训练的是对组件层级、图层合成和设计还原的敏感度。

写业务代码时,状态和数据流很重要。

写 UI 时,图层和边界感同样重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 22:38:52

嵌入式通信系统QMC中断与缓冲区管理:原理、实践与避坑指南

1. 项目概述:QMC中断与缓冲区管理的核心价值 在嵌入式通信系统开发,尤其是涉及多路时分复用(TDM)链路(如E1/T1、HDLC协议栈)的场景中,如何高效、可靠地处理来自多个逻辑通道的数据流和异常事件&…

作者头像 李华
网站建设 2026/6/15 22:38:04

阿拉伯文翻譯公司:專業語言解決方案首選

在全球化浪潮中,阿拉伯文翻譯需求日益增長,無論是商務合作、法律文件、技術手冊、醫療報告,還是文學作品、市場推廣材料,皆需要精準且符合文化背景的翻譯服務。阿拉伯文翻譯公司所處理的語種不僅限於標準阿拉伯語(Mode…

作者头像 李华
网站建设 2026/6/15 22:36:57

MPC8555E电源管理与性能监控实战:从原理到嵌入式系统优化

1. 项目概述:为什么我们需要深入理解MPC8555E的电源与性能管理 在嵌入式系统开发,尤其是网络通信、工业控制这类对功耗和实时性都极为敏感的场景里,工程师们常常面临一个核心矛盾:如何在保证系统性能、响应及时的同时,…

作者头像 李华
网站建设 2026/6/15 22:34:46

LabVIEW新手避坑:NI MAX里死活找不到CompactRIO?这5个排查步骤亲测有效

LabVIEW新手避坑:NI MAX里死活找不到CompactRIO?这5个排查步骤亲测有效 第一次打开NI MAX却找不到设备?那种感觉就像精心准备了烧烤派对却找不到炭火——所有工具都摆在那儿,就是点不着火。作为过来人,我完全理解这种挫…

作者头像 李华
网站建设 2026/6/15 22:31:11

2026年微信商城小程序怎么做?

2026年微信商城小程序怎么做,可以按“商品表-商城系统-支付订单-配送履约-会员营销-数据复盘”这条线推进。不要只把它当成一个页面项目,商城真正要解决的是用户能不能顺利购买、商家能不能持续处理订单和复购。微信商城小程序是一种基于微信生态的交易型…

作者头像 李华