鸿蒙原生应用 ArkTS 表单工程:表白墙发布页字数计数与匿名开关

App 41「校园表白墙」发布页(Func1Tab),主题色 #FF4757 红色(red),4 个 Tab 分别为首页(💌)、发布(✍️)、互动(💬)、我的(👤)。发布页采用"Header + 编辑卡 + 话题卡 + 匿名开关 + 发布按钮"五区布局——白色单行 Header("发布心声"20 号加粗) + 编辑卡(140 高 TextArea 无边框透明 + "📷 添加图片"入口 + 右侧"0/500"实时字数计数) + 话题卡(表白/感谢/寻人/吐槽/祝福 5 个 # 话题胶囊单选) + 匿名开关卡("匿名发布" + "开启后不会显示你的信息" + Toggle 开关,紫色调 selectedColor) + 全宽红色"匿名发布"按钮(内容为空 Toast"请输入内容",非空 Toast"发布成功")。本篇基于 41-confession-wall/entry/src/main/ets/pages/Func1Tab.ets(共 92 行)逐段拆解,附 4 张实机截图。

一、整体结构:五区"Header + 编辑 + 话题 + 开关 + 按钮"布局

发布页是"品牌头部 + 内容编辑 + 标签选择 + 匿名选项 + 提交"的五区布局:

build() {
  Column() {
    this.Header()
    Scroll() {
      Column({ space: 16 }) {
        this.EditCard()
        this.TopicCard()
        this.OptionCard()
        Button('匿名发布').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
          .width('100%').height(48).borderRadius(D.rMd)
          .onClick(() => { promptAction.showToast({ message: this.content.length > 0 ? '发布成功' : '请输入内容' }); })
      }
      .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)
}

5 块结构:

  1. Header — 单行品牌头("发布心声")
  2. EditCard — 心声输入区,TextArea + 图片入口 + 字数计数
  3. TopicCard — 5 个话题胶囊单选
  4. OptionCard — 匿名发布开关
  5. 发布按钮 — 全宽红色主按钮,按内容是否为空给出不同反馈

三个 @State 贯穿全页:content(正文)、activeTopic(话题索引)、anonymous(匿名开关)——内容、标签、隐私三要素状态分离,职责清晰。

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

二、Header:单行标题 + 安全区避让

Header() {
  Row() { Text('发布心声').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) }
  .width('100%').height(this.safeTop + 56).padding({ top: this.safeTop, left: D.pad, right: D.pad })
  .backgroundColor(C.card).alignItems(VerticalAlign.Bottom)
}

单行 20 号加粗、safeTop + 56 高度、白底、文字底部对齐——系列二级页统一范式。"发布心声"的命名比"发布"更贴产品情绪——用词传达"说出心里话"的氛围,是表白墙品牌语的一部分。

41 发布页首屏

三、EditCard:心声编辑卡,字数计数 + 图片入口

EditCard() {
  Column({ space: 10 }) {
    TextArea({ placeholder: '说出你想说的话,勇敢一点...' })
      .backgroundColor(Color.Transparent).borderRadius(0).height(140)
      .onChange((v: string) => { this.content = v; })
    Row() {
      Text('📷 添加图片').fontSize(13).fontColor(C.primary)
        .onClick(() => { promptAction.showToast({ message: '添加图片' }); })
      Blank()
      Text(this.content.length + '/500').fontSize(12).fontColor(C.textDim)
    }.width('100%')
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

技术拆解:

  1. placeholder 文案:"说出你想说的话,勇敢一点..."——语气鼓励、贴合表白墙的情绪场景,比干巴巴的"请输入内容"更有产品温度。
  2. 透明无边框输入:backgroundColor(Color.Transparent) + borderRadius(0) 去除 TextArea 默认样式,让输入区与白卡融为一体——这是"沉浸式输入"的标准写法(对比 App 40 上传页的浅底圆角 TextArea,视觉语义不同:本页是正文主体,要弱化输入框感)。
  3. 高度固定:height(140) 三行左右的空间,长文可滚动。
  4. 字数实时计数:this.content.length + '/500'——@State content 随 onChange 更新,右侧计数实时变化。0/500 的"分子/上限"格式是移动端输入计数的行业标准。
  5. 添加图片入口:左下角"📷 添加图片"红色文字链,点击 Toast 占位——表白可配图,扩展自拍照/校园照片场景。
  6. 上限未强制:Demo 显示 /500 但未限制输入(content.length 可超 500)——产品化应在 onChange 中 v.slice(0, 500) 截断或输入超限提示。

四、TopicCard:5 话题胶囊单选

TopicCard() {
  Column({ space: 10 }) {
    Text('选择话题').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
    Flex({ wrap: FlexWrap.Wrap }) {
      ForEach(this.topics, (t: string, idx: number) => {
        Text('# ' + t).fontSize(13)
          .fontColor(this.activeTopic === idx ? '#FFFFFF' : C.textSub)
          .padding({ left: 14, right: 14, top: 7, bottom: 7 })
          .backgroundColor(this.activeTopic === idx ? C.primary : C.cardSoft).borderRadius(16)
          .margin({ right: 10, bottom: 10 })
          .onClick(() => { this.activeTopic = idx; })
      }, (t: string) => t)
    }.width('100%')
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

5 个话题(15 行):['表白', '感谢', '寻人', '吐槽', '祝福']——五类情绪全覆盖:正向(表白/感谢/祝福)、求助(寻人)、宣泄(吐槽)。话题胶囊带 # 前缀(微博式话题标签语义)。

技术要点:

  1. 单选实现:@State activeTopic: number 存索引,选中红底白字、未选浅底灰字——与 App 40 上传页的关联活动胶囊同款单选模式。
  2. Flex 自动换行:5 个胶囊自动排布(首行 3 个 + 次行 2 个),margin({ right: 10, bottom: 10 }) 提供间距。
  3. 话题的用途:发布时话题随正文提交,首页帖子可显示话题标签、按话题筛选——为"最新/最热"之外增加"按话题浏览"的扩展维度。

五、OptionCard:匿名开关

OptionCard() {
  Row() {
    Column({ space: 2 }) {
      Text('匿名发布').fontSize(14).fontColor(C.text)
      Text('开启后不会显示你的信息').fontSize(11).fontColor(C.textDim)
    }.alignItems(HorizontalAlign.Start).layoutWeight(1)
    Toggle({ type: ToggleType.Switch, isOn: this.anonymous })
      .selectedColor(C.primary)
      .onChange((v: boolean) => { this.anonymous = v; })
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

技术拆解:

  1. 左文右开关:左侧双行文字(14 号"匿名发布" + 11 号说明"开启后不会显示你的信息"),layoutWeight(1) 撑开,右侧 Toggle 开关。
  2. Toggle 开关组件:Toggle({ type: ToggleType.Switch, isOn: this.anonymous })——ArkUI 原生开关,selectedColor(C.primary) 把开启态染成主题红,onChange 回调写入 @State anonymous。
  3. 默认开启:anonymous: boolean = true——匿名是默认态,符合表白墙"以匿名为核心"的产品定位(用户想实名才手动关闭)。
  4. 说明文案的信任感:"开启后不会显示你的信息"——用一句话打消用户对隐私的顾虑,是匿名产品的关键信任设计。
  5. 开关与按钮联动:按钮文案固定"匿名发布",若用户关闭匿名,产品化后按钮文案应变更为"实名发布"并二次确认——当前 Toggle 状态已就绪,文案联动是小事。

六、发布按钮与提交链路

Button('匿名发布').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
  .width('100%').height(48).borderRadius(D.rMd)
  .onClick(() => { promptAction.showToast({ message: this.content.length > 0 ? '发布成功' : '请输入内容' }); })

本页最值得注意的校验逻辑:按钮的反馈是分支的——content.length > 0 ? '发布成功' : '请输入内容':

  • 内容为空:Toast"请输入内容"——拦截无效提交,这是最简单的表单校验。
  • 内容非空:Toast"发布成功"——放行提交。

一行三元表达式完成校验,比 App 40 上传页(无校验,可"上传 0 张")更完善。产品化后:发布成功应把 { content, topic, anonymous } 组装成 Post 对象插入首页 posts 数组(新帖出现在"最新"顶部)、清空输入框、Toast 后自动切回首页——当前分支反馈已为此链路占位。

41 发布页话题选择

七、跨页数据自洽:发布页是首页的"内容生产端"

发布页与首页、互动页、我的页的联动:

  • 发布 → 首页:输入的内容(content)+ 话题(activeTopic)就是首页帖子的正文与标签;匿名开关(anonymous)决定帖子署名"匿名同学"还是实名昵称——首页 4 条帖子都是 anonymous=true 的产物。
  • 话题 → 帖子分类:5 个话题(表白/感谢/寻人/吐槽/祝福)可作为首页帖子的标签展示,也支撑"按话题浏览"——首页帖子内容恰好覆盖表白(帖1/2)、感谢(帖3/4)两类,与话题系统呼应。
  • 发布 → 我的页:成功发布的帖子计入我的页"8 我的发布"统计——发布即创作的记录。
  • 校验语义:"请输入内容"的拦截保证了首页帖子流永远是有内容的——空帖子不会污染信息流,校验是内容质量的第一道防线。

八、实机截图与交互演示

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

1. 发布页首屏:编辑卡 + 话题 + 匿名开关

第一张截图是发布页默认首屏:顶部"发布心声"标题;下方白色编辑卡——140 高透明 TextArea(占位文案"说出你想说的话,勇敢一点...")+ 底部"📷 添加图片"红色入口 + 右侧"0/500"计数;再往下是"选择话题"卡——# 表白(红色选中态)、# 感谢、# 寻人、# 吐槽、# 祝福五个胶囊;"匿名发布"开关卡(默认开启)与红色"匿名发布"按钮。

2. 点击"# 祝福":话题选中切换

第二张截图是点击"# 祝福"话题后的状态:话题胶囊的红色选中态从"# 表白"切换到"# 祝福"——@State activeTopic 由 0 变 4,单选切换生效。选中态与未选态的"红底白字 vs 浅底灰字"对比清晰,5 个话题在 Flex 换行下排布为"3 + 2"两行。

3. 点击发布:空内容校验 Toast

第三张截图是内容为空时点击红色"匿名发布"按钮的反馈:弹出"请输入内容"Toast——content.length > 0 为 false 走校验分支。这是本页区别于其他发布页的关键设计:无效提交被拦截,而不是无条件"发布成功",用户得到明确引导去补充内容。

4. 发布页完整视图:五区全览

第四张截图是发布页的完整视图:编辑卡、话题卡、匿名开关、发布按钮四区全部可见(Header 之外的四区在单屏内完整呈现),红色 Toggle 开关处于开启态(红色)明确可见——"匿名发布"按钮与匿名开关的状态一致性(都开启)在视觉上一目了然。

九、扩展思考:匿名产品的身份设计

匿名开关是本页的核心差异点,围绕"匿名 vs 实名"可以展开更深的身份设计:

  1. 匿名分级:可提供"完全匿名"(无任何身份信息)与"半匿名"(显示学院/年级,不显示姓名)两档——表白墙可以只匿名到"知道你是哪个学院的",为线下"找到你"留一线可能,这也是表白墙产品常见的设计。
  2. 匿名马甲:为每个匿名用户生成随机马甲(如"图书馆的猫""北区小透明"),比统一"匿名同学"更有记忆点,也让多帖作者可被识别(同一马甲 = 同一人)——本页"匿名同学"是统一马甲的简化版。
  3. 实名线索:用户可在匿名帖子中主动附加"认识我的话,来 3 号教学楼找我"之类的线索——匿名是保护,线索是机会,产品应鼓励用户自己控制"匿名程度"。
  4. 切换确认:关闭匿名开关(转为实名)是高风险操作,应弹确认框"确定公开你的身份吗?发布后将显示你的昵称"——防止误触泄露身份。
  5. 隐私政策:匿名产品的隐私声明必须明确"平台如何保护匿名性"(不记录 IP、不关联设备)——信任是匿名产品的生命线,本页"开启后不会显示你的信息"就是这句话的 UI 雏形。

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

  1. 字数计数验证:输入不同长度文本,确认 0/500 实时更新且与 content.length 一致;再测试中文(length 按字符计)与 emoji(可能计 2)的差异——emoji 计数偏差是移动端常见坑,必要时用 Array.from(str).length 按码点计数。
  2. Toggle 状态同步:切换开关后 @State anonymous 应立即更新——若按钮文案需要随开关变化(匿名发布/实名发布),需确保 onChange 回调先于按钮渲染执行(状态更新是同步的,@State 驱动重绘)。
  3. 键盘遮挡:点击 TextArea 弹键盘会压缩可视区,发布按钮可能被顶出屏幕——用 Scroll 的 expandSafeArea 或监听键盘高度,保证键盘弹出时按钮仍可操作(App 40 上传页同样有此问题)。
  4. 校验分支测试:分别测试"空内容 + 点击"(请输入内容)与"非空 + 点击"(发布成功)两条路径——本页三元表达式覆盖两种分支,测试时确认无第三种意外状态。
  5. 500 字上限:当前未强制截断,超长内容会撑高 TextArea 并可能卡顿——产品化时在 onChange 加 if (v.length > 500) { v = v.slice(0, 500); } 截断或 Toast 提示。

九、系列横向对比:发布类页面的四件套演进

本系列已有多个发布/上传类页面,App 41 发布页是它们的集大成者。横向对比核心组件:

页面输入主体标签选择隐私/附加提交校验
App 41 表白墙发布TextArea 文字5 话题胶囊匿名开关空内容拦截
App 40 照片上传照片九宫格关联活动胶囊TextArea 说明无校验
App 38 技能发布技能名输入分类+熟练度想学标签名称校验
App 35 笔记发布标题+正文分类胶囊无无校验

四件套的完整性:App 41 是系列中唯一"输入 + 标签 + 隐私 + 校验"四件套齐全的发布页——输入(TextArea)、标签(话题胶囊)、隐私(匿名开关)、校验(请输入内容拦截)。App 40 缺校验、App 35 缺校验与隐私,只有 App 41 覆盖了全部四维,这在系列中具有里程碑意义。

校验的价值:App 40 可"上传 0 张照片"、App 41 拦截"请输入内容"——两者对比凸显校验的意义:无效提交被拦截,保证内容流的底线质量。表白墙的帖子流若混入空帖子,整个首页的可信度都会崩塌,所以内容校验在 UGC 产品中是硬需求而非锦上添花。

隐私维度的独特性:匿名开关是 App 41 独有(其他发布页无需匿名)——它是产品定位的直接映射:表白墙卖的是"匿名安全感",其他 App 卖的是"交换/分享"。发布页的附加组件永远服务于产品核心价值,这是四件套选型的第一原则。

41 发布页空内容校验

十、系列横向对比:输入控件的五种形态

App 41 发布页的 TextArea 与系列其他输入控件形成完整谱系:

输入控件代表页适用场景特征
透明 TextAreaApp 41 表白墙情感长文沉浸式、无边框
浅底 TextAreaApp 40 上传页简短说明有边框感、轻量
单行 TextInputApp 38 技能发布短文本(名称)单行、有占位
胶囊点选App 41 话题/App 39 兴趣标签选择零键盘、防错
九宫格点选App 40 上传页多图选择可视化、状态化

选型逻辑:情感长文(表白心声)用透明沉浸式 TextArea——弱化"输入框"感,让用户觉得是在"写心里话"而非"填表单";技术短文本(技能名)用单行 TextInput——信息密度高;标签类全部用胶囊点选——零键盘、零输入错误。App 41 发布页同时使用了"沉浸 TextArea(正文)+ 胶囊(话题)"两种输入,主次分明——正文是自由表达、话题是结构化标签,两种输入哲学在同一表单中互补,这是 UGC 发布页的最佳实践。

字数计数的差异化:App 41 的 0/500 是系列唯一的字数计数(App 38/40 均无)——因为表白是"有感情长度的表达",需要上限引导(500 字足够深情又不冗长);技能名/照片说明则无需字数限制。计数器的存在与否,取决于内容的"表达属性"强弱,这个细节体现了本页在产品思考上的细致。

41 发布页完整视图

十一、开发者视角:发布页的常见问题与避坑清单

  1. TextArea 的透明背景在键盘弹出时的表现:backgroundColor(Color.Transparent) 在部分主题下可能让占位文字颜色过浅(低对比度)——建议自定义 placeholderColor 保证可读性,真机验证占位文案在亮/暗模式下都清晰。
  2. 字数计数的 Unicode 陷阱:content.length 对 emoji 按 UTF-16 单元计数(一个 emoji 计 2),用户输入 250 个 emoji 就会显示"500/500"——如需精确,用 Array.from(content).length 按码点计数,避免用户困惑。
  3. Toggle 与按钮文案的状态同步:匿名开关关闭时按钮仍显示"匿名发布"会造成语义矛盾——产品化时应让按钮文案随 anonymous 变化("匿名发布"/"实名发布"),并加二次确认,当前 Demo 未联动是简化。
  4. 发布成功后的页面状态:Toast"发布成功"后应清空 content、重置话题与开关——否则用户连续发布会重复提交相同内容。Demo 未清空,产品化必做。
  5. 500 字截断:建议在 onChange 中 if (v.length > 500) return; 或 v = v.slice(0, 500) 强制上限,并让计数在 500 时变色警示(红色)——上限 + 警示是字数限制的标准组合。

十二、FAQ 与一句话总结

Q1:为什么匿名默认开启? 表白墙的定位是"说平时不敢说的话",匿名是产品灵魂——默认开启让用户零门槛表达,想实名才手动关闭。若默认实名,很多用户会因顾虑而放弃发布,产品就失去了意义。

Q2:话题为什么只有 5 个? 表白/感谢/寻人/吐槽/祝福覆盖了校园心声的绝大多数场景,5 个恰好两行排布、不拥挤。真实产品可扩展(加"求助""分享""树洞"等),但核心 5 类应保留——它们是表白墙内容的基本盘。

Q3:"添加图片"为什么是 Toast? 需要接入相册选择器(PhotoViewPicker)才能真选图,Demo 用 Toast 占位。挂上选择器后,选中的图片在编辑卡内预览、随正文一起发布——扩展路径清晰。

Q4:发布成功后内容去哪里了? Demo 只弹 Toast。产品化后把 { content, topic, anonymous } 插入首页 posts 数组(新帖置顶"最新"分段),帖子流即时可见——"发布 → 展示"闭环是 UGC 产品的基本信任。

Q5:实名发布会被别人看到真实身份吗? 匿名关闭后,帖子署名会从"匿名同学"变为用户昵称(本 App 即"匿名同学"的实名版),其他信息(头像/学院)按个人资料显示——实名发布的可见范围应在隐私设置(我的页菜单)中由用户自行控制。

一句话总结:发布页以"编辑 + 话题 + 匿名开关 + 校验按钮"四件套完成了表白墙的内容生产闭环。TextArea 沉浸式输入、字数实时计数、话题胶囊单选、Toggle 匿名开关、分支校验——五个能力覆盖了 UGC 发布页的核心要素,而"开启后不会显示你的信息"这句信任文案与"请输入内容"的校验拦截,让本页在同系列发布页中显得格外有产品思考。

发布页的关键价值在于它让"心声"有了一个安全且体面的出口:匿名开关守护表达者,话题胶囊组织内容,字数计数引导表达节奏,校验拦截守护内容质量——四者协同,成就了表白墙最核心的生产链路。 从技术视角看,本页的 TextArea + Flex 胶囊 + Toggle + 分支校验恰好覆盖了 ArkUI 表单的四大基础能力,初学者可把这四件套作为学习模板,一次掌握输入、点选、开关、校验的完整写法。

配图

Logo

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

更多推荐