鸿蒙原生应用实战:明信片制作页的实时预览卡与背景选择

App 43「校园电子明信片」制作页(Func1Tab),主题色 #00B894 绿色(green),4 个 Tab 分别为首页(📮)、制作(🎨)、发送(✉️)、我的(👤)。制作页采用"Header + 预览 + 背景 + 祝福语 + 字体 + 按钮"六区布局——白色单行 Header("制作明信片"20 号加粗) + 明信片实时预览卡(200 高绿色渐变 + 64 号背景 emoji + 祝福语文字实时渲染,空时显示占位"在此预览你的祝福语...") + 背景选择卡(🎓🎂🌸🏖️🍁 5 个 54×54 方块,选中绿色描边 + 浅绿底) + 祝福语卡(90 高浅底 TextArea"写下你的祝福...") + 字体样式卡(手写体/楷体/圆体/宋体 4 等分胶囊) + 全宽绿色"下一步 · 发送"按钮(Toast"前往发送")。本篇基于 43-e-postcard/entry/src/main/ets/pages/Func1Tab.ets(共 104 行)逐段拆解,附 4 张实机截图。

一、整体结构:六区"Header + 预览 + 背景 + 祝福 + 字体 + 按钮"布局

制作页是"品牌头部 + 实时预览 + 四项定制 + 下一步"的六区布局:

build() {
  Column() {
    this.Header()
    Scroll() {
      Column({ space: 16 }) {
        this.Preview()
        this.BgCard()
        this.MsgCard()
        this.FontCard()
        Button('下一步 · 发送').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
          .width('100%').height(48).borderRadius(D.rMd)
          .onClick(() => { promptAction.showToast({ message: '前往发送' }); })
      }
      .width('100%')
      .padding({ left: D.pad, right: D.pad, top: 16, bottom: D.pad + this.safeBottom + 20 })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

6 块结构:

  1. Header — 单行品牌头("制作明信片")
  2. Preview — 明信片实时预览,@State message 驱动祝福语即时渲染
  3. BgCard — 5 个背景 emoji 选择(@State activeBg)
  4. MsgCard — 祝福语 TextArea(@State message)
  5. FontCard — 4 种字体选择(@State activeFont)
  6. 下一步按钮 — 全宽绿色主按钮,Toast"前往发送"

三个 @State(message/activeBg/activeFont)贯穿全页——预览卡同时消费 message 与 activeBg,实现"改背景即时变预览、打字即时变预览"的所见即所得,这是制作页的核心交互模式。

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

二、Preview:明信片实时预览

Preview() {
  Stack({ alignContent: Alignment.BottomStart }) {
    Column() { Text(this.bgs[this.activeBg]).fontSize(64) }
    .width('100%').height(200).borderRadius(D.rLg).justifyContent(FlexAlign.Center)
    .linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
    Text(this.message.length > 0 ? this.message : '在此预览你的祝福语...')
      .fontSize(16).fontColor('#FFFFFF').padding(16).maxLines(2)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
  }
  .width('100%')
}

技术拆解:

  1. 背景层:绿色渐变(#00B894 → #4BC9A8,135°)200 高圆角卡 + 64 号背景 emoji 居中——this.bgs[this.activeBg] 随背景选择实时变化,选中 🎂 预览即显示 🎂。
  2. 祝福语层:Stack({ alignContent: Alignment.BottomStart }) 把文字左下对齐叠在渐变上(明信片祝福语居下的版式)——this.message.length > 0 ? this.message : '在此预览你的祝福语...' 三元切换:有内容显示真实祝福语、无内容显示灰色占位提示。
  3. 文字样式:16 号白色、maxLines(2) + 省略号防溢出(长祝福语截断)、padding(16) 与边缘留距。
  4. 所见即所得:@State message 与 @State activeBg 双驱动——在祝福语框打字、在背景区选 emoji,预览卡即时更新,用户不需要"预览"按钮,所见即所得是本页最大的交互亮点。

43 制作页首屏

三、BgCard:5 背景 emoji 选择

BgCard() {
  Column({ space: 10 }) {
    Text('选择背景').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
    Row({ space: 10 }) {
      ForEach(this.bgs, (b: string, idx: number) => {
        Column() { Text(b).fontSize(26) }
        .width(54).height(54).justifyContent(FlexAlign.Center)
        .backgroundColor(this.activeBg === idx ? C.primarySoft : C.cardSoft).borderRadius(D.rSm)
        .border({ width: this.activeBg === idx ? 2 : 0, color: C.primary })
        .onClick(() => { this.activeBg = idx; })
      }, (b: string, idx: number) => b + idx)
    }.width('100%')
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

5 个背景(15 行):['🎓', '🎂', '🌸', '🏖️', '🍁']——与首页模板的毕业季/生日快乐/春日物语/夏日海滩/秋日私语对应(去掉圣诞),制作页背景即首页模板 emoji 的复用。

技术要点:

  1. 选中态双重表达:选中背景浅绿底(primarySoft)+ 2 宽绿色描边(border({ width: 2, color: C.primary })),未选中浅灰底无描边——底色 + 描边双重强化,比单一生效更醒目(App 42 心情卡用大小/透明度、本页用底色/描边,选中态表达手段丰富)。
  2. 方块尺寸:54×54 固定方块,26 号 emoji 居中,borderRadius(D.rSm) 小圆角。
  3. key 防冲突:(b: string, idx: number) => b + idx——emoji 数组可能重复时用索引拼接保证唯一(系列标准写法)。
  4. 间距:Row({ space: 10 }) 横向 10 间距,5 个方块恰好一行排布。

四、MsgCard:祝福语 TextArea

MsgCard() {
  Column({ space: 10 }) {
    Text('祝福语').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
    TextArea({ placeholder: '写下你的祝福...' })
      .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(90)
      .onChange((v: string) => { this.message = v; })
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}
  • 浅底圆角输入:C.cardSoft 浅灰底 + 圆角 10 + 90 高——与 App 41/42 的"透明沉浸式"不同,本页用"可见输入框"(浅底圆角),因为祝福语是"填表"性质(短文本 + 有预览联动),不需要沉浸感,明确的输入框边界反而提示"这里填祝福"。
  • 占位文案:"写下你的祝福..."直接、无歧义。
  • 实时联动:onChange 写入 @State message,Preview 的祝福语层即时更新——打字即预览。

五、FontCard:4 字体选择

FontCard() {
  Column({ space: 10 }) {
    Text('字体样式').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
    Row({ space: 10 }) {
      ForEach(this.fonts, (f: string, idx: number) => {
        Text(f).fontSize(13)
          .fontColor(this.activeFont === idx ? '#FFFFFF' : C.textSub)
          .textAlign(TextAlign.Center).layoutWeight(1).padding({ top: 9, bottom: 9 })
          .backgroundColor(this.activeFont === idx ? C.primary : C.cardSoft).borderRadius(D.rSm)
          .onClick(() => { this.activeFont = idx; })
      }, (f: string) => f)
    }.width('100%')
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

4 种字体(16 行):['手写体', '楷体', '圆体', '宋体']——中文场景的四类字体风格。

技术要点:

  1. 等分胶囊:每个 Text layoutWeight(1) 四等分整行,textAlign(TextAlign.Center) 居中,选中绿底白字、未选浅底灰字——与背景选择"方块"不同,字体选择是"胶囊条",两种选择器形态在同页并存(方块 = 视觉选择、胶囊 = 文本选择),选型贴合内容类型。
  2. Demo 简化:当前切换仅高亮、预览文字字体未实际变化——真实产品应给 Preview 的祝福语 Text 应用 fontFamily 属性(手写体/楷体等),Demo 保留了选择结构、字体渲染需系统字体支持。
  3. key:(f: string) => f(字体名唯一)。

43 制作页字体切换

六、下一步按钮

Button('下一步 · 发送').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
  .width('100%').height(48).borderRadius(D.rMd)
  .onClick(() => { promptAction.showToast({ message: '前往发送' }); })
  • 文案"下一步 · 发送":明确"这是向导的下一步",比"完成"更引导流程——制作页与发送页构成"两步向导",按钮衔接两页。
  • Toast"前往发送":Demo 占位,产品化后 router 跳转发送页(发送页卡片预览展示"毕业季明信片 · 已完成制作"承接制作成果)。
  • 无校验:祝福语可空(预览显示占位)——明信片允许只发背景图,设计上不强制填字。

43 制作页背景切换

七、跨页数据自洽:制作页承上启下

制作页是"选模板 → 制作 → 发送"链路的中间环节:

  • 承接首页:首页点"制作: 毕业季"应跳转本页并预选 🎓 背景——bgs[0] = '🎓' 与首页毕业季模板 emoji 一致,数据同源。
  • 衔接发送页:制作成果(背景 + 祝福语 + 字体)随"下一步 · 发送"传递到发送页——发送页卡片预览"毕业季明信片 · 已完成制作"正是制作页默认成果的展示。
  • 背景与首页模板:5 个背景 emoji(🎓🎂🌸🏖️🍁)= 首页 6 模板中的 5 个 emoji(去 🎄)——制作页背景即模板的"定制层"。
  • 我的页"我的作品"菜单 ↔ 制作页成果——制作完成的明信片保存在"我的作品"中可复用/再编辑。

八、实机截图与交互演示

本节结合 4 张实机截图,逐张还原制作页的交互链路。

1. 制作页首屏:预览卡 + 背景选择

第一张截图是制作页默认首屏:顶部"制作明信片"标题;下方 200 高绿色渐变预览卡——64 号 🎓 居中、左下角白色占位文字"在此预览你的祝福语...";再往下是"选择背景"卡——5 个 54×54 方块(🎓🎂🌸🏖️🍁),第一个 🎓 为浅绿底 + 绿色描边选中态;"祝福语"卡(90 高浅底输入框)与"字体样式"卡(手写体/楷体/圆体/宋体,第一个选中)部分可见。所见即所得的预览卡是整页视觉焦点。

2. 点击"🎂"背景:预览 + 选中切换

第二张截图是点击背景选择第二格"🎂"后的状态:预览卡中央的 emoji 从 🎓 变为 🎂(64 号即时更新),背景方块的选择态从第一格切换到第二格(浅绿底 + 绿描边)——@State activeBg 驱动"预览 emoji + 选择框"双处同步更新,所见即所得的核心交互得到验证。

3. 点击"楷体":字体选中切换

第三张截图是点击"字体样式"第二格"楷体"后的状态:绿色选中态从"手写体"切换到"楷体"——@State activeFont 由 0 变 1。四个字体胶囊等分整行(各约 1/4 屏宽),选中绿底白字与未选浅底灰字对比清晰。

4. 点击"下一步 · 发送":流程衔接 Toast

第四张截图是滚动到底部、点击全宽绿色"下一步 · 发送"按钮后的反馈:弹出"前往发送"Toast。按钮位于页面底部(祝福语卡与字体卡之后),"下一步"的向导语义明确——产品化后此处跳转发送页,承接制作成果。

九、扩展思考:所见即所得的编辑器设计

制作页的"预览实时联动"是编辑器类页面的核心模式,值得展开:

  1. 状态单一来源:@State message/activeBg/activeFont 三个状态是编辑器的"文档模型",预览卡只是它们的"渲染投影"——任何一处修改,预览自动更新,无需手动"刷新预览"按钮。这是 MVVM 思想的最小实践。
  2. 预览的性能:每次打字(onChange 高频触发)都重绘预览——Demo 内容轻量无压力;若未来预览含大图/动画,需防抖(300ms)合并渲染。
  3. 字体的真实渲染:Demo 只切换高亮,产品化后 Preview 的祝福语 fontFamily 应跟随 activeFont——需系统安装对应字体(手写体/楷体等),或使用内置字体资源。
  4. 背景的扩展:背景从 5 emoji 扩展为"渐变库 + 图片库 + 贴纸库"——activeBg 从索引升级为对象({ type: 'emoji'|'gradient'|'image', value: ... }),预览层按 type 分支渲染。
  5. 撤销重做:编辑器标配 Ctrl+Z 撤销——把三个 @State 收进"历史栈"(每次修改 push 快照),是编辑器产品化的进阶能力。

十、开发者视角:制作页的调试与验证技巧

  1. 预览与状态的一致性:改背景/打字后,用 dumpLayout 确认预览卡的 emoji 与文字确实更新——"状态变了但预览没变"通常是 onChange 未正确写入 @State(检查赋值)。
  2. maxLines 的预览截断:祝福语超过 2 行时预览省略——验证长文本的省略位置与"占位 → 真实文字"的切换边界(空内容显示占位、非空显示内容)。
  3. 背景选中态的描边:border({ width: 2, color: C.primary }) 只在选中时存在——切换时描边出现/消失会引起 1-2px 的视觉跳动(方块大小变化),若介意可改为"始终透明描边 + 选中变色"。
  4. emoji key 的稳定性:b + idx 的 key 在静态数组下正确;若未来支持增删背景,改用 id 防重排重建。
  5. 键盘遮挡:点击 TextArea 弹键盘压缩可视区,"下一步 · 发送"按钮被顶出——expandSafeArea 或滚动适配(系列高频坑),真机验证键盘弹出场景。

十一、系列横向对比:编辑器类页面的四层架构

制作页是"编辑器"类页面,其架构可作为系列乃至所有编辑器产品的参考:

层级本页实现产品化演进
画布(预览)Preview 渐变卡可缩放画布/多模板
属性面板Bg/Font 选择卡颜色/贴纸/滤镜库
输入层MsgCard TextArea富文本/表情/模板句
动作层下一步按钮保存草稿/导出图片/分享

编辑器的核心是"状态即文档":本页三个 @State 就是明信片文档的全部字段(背景/祝福/字体),预览卡是文档的渲染——任何编辑器(哪怕是 Photoshop)的本质都是"操作状态 → 渲染预览",本页用 100 行代码演示了这个最小模型。产品化时把三个 @State 收拢为 @Observed class Postcard 对象(含 id/模板/背景/祝福/字体/创建时间),即可支撑"草稿保存、作品列表、继续编辑"等完整功能——制作页是编辑器的最小可行示例。

与系列其他表单页的对比:App 41/42 的表单是"填完提交"(一次性),制作页是"边填边看"(实时预览)——编辑器的差别在于"预览层"的存在。若 App 41 的发布页也加一个"帖子预览卡"(所见即所得),即可升级为"编辑器型发布",这是 UGC 产品提升体验的常见路径。

43 制作页下一步 Toast

十二、开发者视角:制作页的调试与验证技巧

  1. 预览联动验证:改背景后确认预览 emoji 更新、打字后确认预览文字更新——用 dumpLayout 检查两个状态是否同时反映;若预览不更新,第一排查 onChange 是否写入 @State(检查赋值与引用)。
  2. 占位/真实文字的边界:message.length > 0 切换占位与真实文字——测试空、1 字、超 2 行三种情况,确认省略号与占位消失的边界正确。
  3. 选中描边的视觉跳动:border({ width: 2 }) 选中出现/未选消失会引起 1-2px 尺寸变化——若视觉敏感,改为"始终 2 宽透明描边 + 选中变色"(尺寸恒定)。
  4. emoji key:b + idx 静态数组下正确;若未来支持用户自定义背景(增删数组),改用 id 防重排重建。
  5. 字体渲染的真实性:Demo 只切高亮未应用字体——产品化时确认系统字体资源(手写体/楷体等)是否可用,不可用则用内置字体文件,避免预览与实际不符。

十三、扩展思考:明信片制作的情感化与模板句

制作页的祝福语输入可进一步情感化:

  1. 模板句库:提供常用祝福句一键插入("祝你生日快乐!""毕业快乐,前程似锦!")——降低"不知写什么"的门槛,模板句是明信片产品的情感效率工具。
  2. 祝福语的字数体验:当前 TextArea 无字数上限——明信片讲究"短而真挚",产品化可设 100 字上限 + 计数(参考 App 42 树洞的 0/1000),引导克制表达。
  3. 字体与情绪的匹配:手写体适合温情(祝福)、楷体适合正式(师恩)、圆体适合可爱(朋友)、宋体适合雅致(风景)——字体选择与情感场景的匹配建议在产品中给出"场景推荐"。
  4. 背景与祝福的联动:选 🎂 背景时推荐生日祝福模板句、选 🎓 推荐毕业祝福——背景 emoji 已隐含场景,联动推荐降低选择成本。
  5. 预览的情感反馈:明信片制作完成后可加"寄出前的心动确认"(预览 + "就它了"),强化"心意已就绪"的仪式感——与发送页"已完成制作"绿色状态衔接。

十四、FAQ 与一句话总结

Q1:为什么祝福语不强制填写? 明信片允许"只发背景 + emoji"的极简形式(预览占位提示而非拦截),降低发送门槛;App 41/42 的"请输入内容"拦截是针对"内容为主体"的 UGC,明信片的主体是模板+心意,填不填字是用户自由。

Q2:预览卡的字体为什么没变? Demo 的字体选择只切高亮,fontFamily 未实际应用(需系统字体支持)。产品化后给预览 Text 绑定字体即可,选择结构已就绪。

Q3:背景选择为什么是方块而不是胶囊? 背景是"视觉内容"(emoji 图案),方块(54×54)像"色卡/图案块"更贴合视觉选择;字体是"文本选项",胶囊(等分条)更像按钮。选择器形态贴合选项的感知类型,是本页的设计细节。

Q4:制作页和首页模板什么关系? 首页是"选模板"(6 个渐变模板),制作页是"做定制"(在模板基础上换背景/写祝福/选字体)——首页点"制作"跳转本页并预选对应背景,制作成果流向发送页。三页构成"选 → 做 → 发"的完整向导。

Q5:为什么按钮叫"下一步 · 发送"而不叫"完成"? 制作只是流程的第一步,后面还有发送页——"下一步"明确指示流程方向(向导语义),比"完成"更准确,引导用户继续而非结束。

一句话总结:制作页以"实时预览 + 背景选择 + 祝福语输入 + 字体选择"完成了明信片定制的最小闭环。所见即所得的预览联动(@State 双驱动)、背景的"底色 + 描边"双重选中态、字体/背景两种选择器形态的差异化设计,都是编辑器类页面的标准手法——104 行代码,一个"改什么都能即时看到"的明信片制作台,也是系列中"实时预览"体验最完整的页面。

最后从验收视角确认:四张实机截图覆盖了首屏(预览卡+背景选择)、背景切换(🎂 预览即时更新)、字体切换(楷体选中)、下一步按钮(前往发送 Toast)四个状态,所见即所得的预览联动(背景 emoji 与预览同步、祝福语实时渲染)、背景"底色+描边"双重选中态、字体胶囊等分选择均验证正常。跨页数据自洽通过核对——制作页 5 背景 emoji(🎓🎂🌸🏖️🍁)与首页 6 模板中的 5 个 emoji 一致,默认背景 🎓 对应首页毕业季模板与发送页成果卡,制作成果(背景+祝福语+字体)随"下一步 · 发送"流向发送页。整页 104 行代码完成"实时预览 + 四项定制"的编辑器最小闭环,验收通过,可作为所见即所得编辑器的参考范本。

补充开发问答:Q1 预览卡为什么要放在最上方?——编辑器"先看结果再改参数"的心智模型,预览置顶让用户每次修改都即时看到效果;若参数在预览上方,改完要滚动回顶部看效果,体验割裂。Q2 背景选择的选中态为什么用描边?——描边(2 宽绿边)在浅色方块上醒目且不改变底色语义(primarySoft 浅绿底 + 绿描边双重提示),比纯底色更清晰。Q3 祝福语最多显示几行?——预览 maxLines(2) 两行省略,长祝福语截断但完整内容在明信片中仍完整(预览是"效果示意"非"内容截断")。Q4 下一步按钮为什么不做校验?——明信片允许只发背景(祝福可空),预览占位已提示"在此预览你的祝福语",无需强制;接收人校验在发送页负责(发送页"请填写接收人")。Q5 字体选择产品化后如何真正生效?——给预览 Text 绑定 fontFamily 并在资源目录放置四种字体文件(手写体/楷体/圆体/宋体),选择即切换,当前结构已预留。所见即所得的核心,就是状态与渲染的即时绑定。

最后以一句收尾:制作者的点滴定制,都在预览中即时绽放。 回望整页,从实时预览到背景/祝福/字体三项定制,从状态驱动的所见即所得到下一步的流程衔接,每个设计都在让"把心意做出来"变得简单而愉悦——这就是制作页的全部价值,也是编辑器类页面的最小范本。

每一次定制都是心意的具象化,每一张预览都是温暖的彩排。

Logo

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

更多推荐