鸿蒙原生应用 ArkTS 表单工程:作品发布页的分类多选与实时预览
鸿蒙原生应用 ArkTS 表单工程作品发布页的分类多选与实时预览App 19「校园影像」发布 TabFunc1Tab是系列交互最丰富的作品发布表单——作品信息标题/描述8 分类多选已选计数 拍摄场景单选教学楼/操场/图书馆/食堂/宿舍/湖边滤镜选择原图/复古/黑白/暖色/冷色/胶片 隐私三态公开/仅好友/私密实时预览卡滤镜/标题/场景/隐私/描述/已选分类全联动 4 个快捷模板 发布按钮。本篇基于19-campus-photo/entry/src/main/ets/pages/Func1Tab.ets约 279 行逐段拆解附 4 张实机截图。一、整体结构7 个 Builder 2 个辅助方法的发布漏斗Func1Tab 与 App 18 发现页同构多选 预览 模板但多了场景选择和滤镜选择两个模块build() { Column() { this.Header() Scroll() { Column({ space: 14 }) { this.FormSection() this.CategorySection() this.SceneSection() this.FilterSection() this.PreviewCard() this.TemplateSection() this.SubmitBtn() } .width(100%) .padding({ left: D.pad, right: D.pad, top: 14, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) } .width(100%).height(100%).backgroundColor(C.bg) }7 个块按填信息 → 选分类 → 选场景 → 选滤镜 → 看预览 → 用模板 → 发布漏斗展开FormSection— 标题 描述输入CategorySection— 8 分类多选本页核心交互SceneSection— 拍摄场景单选6 个FilterSection— 滤镜选择6 个 隐私三态PreviewCard— 实时预览本页最大亮点TemplateSection— 4 个快捷模板SubmitBtn— 发布作品对比 App 18App 18 是3 组单选学院/年龄/范围 匿名 ToggleApp 19 是场景单选 滤镜单选 隐私三态——滤镜和隐私三态是本页新增。**发布作品 填信息 选分类 选场景 选滤镜 定隐私**是摄影 App 的完整发布流程。项目源码开源https://gitee.com/codenestFlow/HarmonyOSHub二、Header FormSection作品信息Header 是发布作品/分享你的校园摄影作品两行品牌区。FormSection 是标题/描述输入与 App 18 同款上标签 描边输入框Builder FormSection() { Column({ space: 14 }) { Text(作品信息).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width(100%) Column({ space: 8 }) { Text(标题).fontSize(12).fontColor(C.textDim).width(100%) TextInput({ placeholder: 给作品起个名字..., text: this.title }) .onChange((val: string) { this.title val; }) .height(44).borderRadius(D.rMd).backgroundColor(C.bg) .border({ width: 1, color: C.stroke }).placeholderColor(C.textDim).placeholderFont({ size: 13 }) }.width(100%) Column({ space: 8 }) { Text(描述).fontSize(12).fontColor(C.textDim).width(100%) TextArea({ placeholder: 分享拍摄故事或心得..., text: this.desc }) .onChange((val: string) { this.desc val; }) .height(80).borderRadius(D.rMd).backgroundColor(C.bg) .border({ width: 1, color: C.stroke }).placeholderColor(C.textDim).placeholderFont({ size: 13 }) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }占位文案的引导性标题给作品起个名字...给...起个名字是口语化引导比请输入标题亲切描述分享拍摄故事或心得...分享故事是摄影 App 的文案调性——不只是填描述而是讲故事State titleState desc双向绑定——输入实时存入状态供 PreviewCard 读取。三、CategorySection8 分类多选本页核心CategorySection 是 8 个分类标签的多选胶囊已选计数Builder CategorySection() { Column({ space: 12 }) { Row() { Text(作品分类).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Blank() Text(已选 this.selectedCount() 个).fontSize(12).fontColor(C.primary) }.width(100%) Flex({ wrap: FlexWrap.Wrap }) { ForEach(this.categories, (item: CategoryTag) { Text(item.label) .fontSize(13) .fontColor(item.selected ? #FFFFFF : C.textSub) .backgroundColor(item.selected ? C.primary : C.cardSoft) .borderRadius(16) .padding({ left: 14, right: 14, top: 8, bottom: 8 }) .margin({ right: 8, bottom: 8 }) .border({ width: 1, color: item.selected ? C.primary : C.stroke }) .onClick(() { this.toggleCategory(item.id); }) }, (item: CategoryTag) item.id.toString()) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }8 个分类风景(选)/人像/建筑(选)/美食/运动/夜景(选)/花卉/动物——初始 3 个选中风景/建筑/夜景——预选降低门槛App 18 同款设计。Flex({ wrap: FlexWrap.Wrap })自动换行margin({ right: 8, bottom: 8 })标签间距——8 个长短不一标签的最佳布局同 App 18 发现页。toggleCategory(id)selectedCount()两个辅助方法与 App 18 完全同构toggleCategory(id: number): void { for (let i 0; i this.categories.length; i) { if (this.categories[i].id id) { this.categories[i] { id: this.categories[i].id, label: this.categories[i].label, selected: !this.categories[i].selected }; } } } selectedCount(): number { let count: number 0; for (let i 0; i this.categories.length; i) { if (this.categories[i].selected) { count; } } return count; }toggleCategory创建新对象赋值this.categories[i] {...}而非改字段——ArkTS 响应式数组的整体替换写法改字段不触发重渲染。selectedCount()线性遍历统计——右上角已选 3 个。四、SceneSection拍摄场景单选SceneSection 是 6 个拍摄场景的 emoji 胶囊单选Builder SceneSection() { Column({ space: 12 }) { Text(拍摄场景).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width(100%) Row({ space: 8 }) { ForEach(this.scenes, (s: SceneItem) { Row({ space: 6 }) { Text(s.icon).fontSize(14) Text(s.name).fontSize(12) } .padding({ left: 10, right: 10, top: 8, bottom: 8 }) .borderRadius(D.rMd) .backgroundColor(this.selectedScene s.id ? C.primarySoft : C.cardSoft) .border({ width: 1, color: this.selectedScene s.id ? C.primary : C.stroke }) .onClick(() { this.selectedScene s.id; promptAction.showToast({ message: s.name }); }) }, (s: SceneItem) s.id.toString()) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }6 个场景教学楼/操场⚽/图书馆/食堂/宿舍/湖边——校园拍摄地全覆盖。默认选中教学楼selectedScene 1。单选交互selectedScene s.id ? C.primarySoft : C.cardSoft选中浅青绿 vs 未选浅灰 青绿描边。点击还弹 Toasts.name——选择反馈。与 App 18 学院选择的对比App 18 是学院App 19 是拍摄场景——**场景 照片拍摄地**是摄影 App 特有的元数据图库按地点分类。五、FilterSection滤镜选择 隐私三态FilterSection 是 6 个滤镜选择 3 档隐私公开/仅好友/私密Builder FilterSection() { Column({ space: 12 }) { Text(滤镜选择).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width(100%) Row({ space: 8 }) { ForEach(this.filters, (f: FilterItem) { Column({ space: 6 }) { Row() { Text(f.preview).fontSize(22) } .width(44).height(44).borderRadius(D.rMd) .backgroundColor(this.selectedFilter f.id ? C.primarySoft : C.cardSoft) .justifyContent(FlexAlign.Center) Text(f.name).fontSize(10) .fontColor(this.selectedFilter f.id ? C.primary : C.textDim) }.layoutWeight(1) .onClick(() { this.selectedFilter f.id; promptAction.showToast({ message: 滤镜: f.name }); }) }, (f: FilterItem) f.id.toString()) }.width(100%) Row() { ForEach(this.privacyOptions, (o: string, idx: number) { Text(o) .fontSize(12).fontColor(this.privacy idx ? #FFFFFF : C.textSub) .backgroundColor(this.privacy idx ? C.primary : C.cardSoft) .borderRadius(D.rMd) .padding({ left: 12, right: 12, top: 8, bottom: 8 }) .border({ width: 1, color: this.privacy idx ? C.primary : C.stroke }) .onClick(() { this.privacy idx; }) }, (o: string, idx: number) o idx) }.width(100%).margin({ top: 8 }) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }5.1 6 个滤镜预览 emojiprivate filters: FilterItem[] [ { id: 1, name: 原图, preview: }, { id: 2, name: 复古, preview: }, { id: 3, name: 黑白, preview: }, { id: 4, name: 暖色, preview: }, { id: 5, name: 冷色, preview: ❄️ }, { id: 6, name: 胶片, preview: } ];滤镜名 预览 emoji——原图/复古/黑白/暖色/冷色❄️/胶片——emoji 表达滤镜效果暖色用夕阳、冷色用❄️冰雪、黑白用——emoji 即滤镜预览demo 替代真实滤镜渲染。滤镜选中44×44 图标块C.primarySoft浅青绿 vs 灰 文字色。默认选中原图selectedFilter 1。FilterItem的preview: string字段是滤镜预览的 demo 化——真实项目这里应该是滤镜滤镜预览图或滤镜的 CSS/Shader 效果。5.2 隐私三态公开/仅好友/私密Row() { ForEach(this.privacyOptions, (o: string, idx: number) { Text(o) .fontSize(12).fontColor(this.privacy idx ? #FFFFFF : C.textSub) .backgroundColor(this.privacy idx ? C.primary : C.cardSoft) .borderRadius(D.rMd) .padding({ left: 12, right: 12, top: 8, bottom: 8 }) .border({ width: 1, color: this.privacy idx ? C.primary : C.stroke }) .onClick(() { this.privacy idx; }) }, (o: string, idx: number) o idx) }privacyOptions: string[] [公开, 仅好友, 私密]State privacy: number 0默认公开——三态隐私是社交内容发布的标准微信朋友圈公开/仅好友/私密同款——隐私层级设计。选中态白字青绿底 vs 灰字灰底。公开/仅好友/私密三档可见范围递增收窄——内容可见性控制。六、PreviewCard实时预览本页最大亮点PreviewCard 是实时预览卡——滤镜/标题/场景/隐私/描述/已选分类全部联动Builder PreviewCard() { Column({ space: 12 }) { Row() { Text(预览效果).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Blank() Text(他人将看到).fontSize(11).fontColor(C.textDim) }.width(100%) Row({ space: 14 }) { Row() { Text(this.filters[this.selectedFilter - 1].preview).fontSize(40) } .width(64).height(64).backgroundColor(C.primarySoft).borderRadius(D.rMd).justifyContent(FlexAlign.Center) Column({ space: 4 }) { Text(this.title.length 0 ? this.title : 作品标题).fontSize(16).fontWeight(FontWeight.Bold).fontColor(C.text) Text(this.scenes[this.selectedScene - 1].name · this.filters[this.selectedFilter - 1].name) .fontSize(12).fontColor(C.textDim) Text( this.privacyOptions[this.privacy]).fontSize(11).fontColor(C.textSub) }.alignItems(HorizontalAlign.Start).layoutWeight(1) }.width(100%) if (this.desc.length 0) { Text(this.desc).fontSize(12).fontColor(C.textSub).width(100%).maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }) } Row({ space: 6 }) { ForEach(this.categories, (item: CategoryTag) { if (item.selected) { Text(item.label).fontSize(11).fontColor(C.primary) .backgroundColor(C.primarySoft).borderRadius(4) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) } }, (item: CategoryTag) item.id.toString()) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke }) }6.1 滤镜联动最精彩的联动Row() { Text(this.filters[this.selectedFilter - 1].preview).fontSize(40) }filters[selectedFilter - 1].preview——预览卡的封面图 当前选中滤镜的预览 emoji——选滤镜 → 预览封面换 emoji选暖色预览卡封面变、选黑白变——滤镜实时预览是本页最直观的联动。注意selectedFilter - 1的下标偏移——id 从 1 开始、数组下标从 0 开始selectedFilter 1原图 →filters[0]——id-1 取下标的经典写法本系列多次出现。6.2 标题/场景/隐私联动Text(this.title.length 0 ? this.title : 作品标题).fontSize(16).fontWeight(FontWeight.Bold).fontColor(C.text) Text(this.scenes[this.selectedScene - 1].name · this.filters[this.selectedFilter - 1].name) .fontSize(12).fontColor(C.textDim) Text( this.privacyOptions[this.privacy]).fontSize(11).fontColor(C.textSub)标题title.length 0 ? title : 作品标题空时显示占位场景滤镜scenes[selectedScene-1].name · filters[selectedFilter-1].name教学楼 · 原图——场景和滤镜的拼接显示隐私 privacyOptions[privacy] 公开——锁图标 隐私状态6.3 描述 已选分类联动if (this.desc.length 0) { Text(this.desc).fontSize(12).fontColor(C.textSub).width(100%).maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }) } Row({ space: 6 }) { ForEach(this.categories, (item: CategoryTag) { if (item.selected) { Text(item.label).fontSize(11).fontColor(C.primary) .backgroundColor(C.primarySoft).borderRadius(4) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) } }, (item: CategoryTag) item.id.toString()) }描述if (desc.length 0)有才显示 maxLines(2)截断分类if (item.selected)只显示已选标签——点选/取消分类预览卡标签实时增删一次操作、五处联动的完整闭环选滤镜 → 预览封面换 emoji 场景·滤镜文字变 输标题 → 预览标题实时变 选场景 → 场景·滤镜文字变 选隐私 → 文字变 点分类 → 预览标签增删 计数变5 个交互点全部实时反馈到预览卡——发布即所见是摄影 App 的信任设计发布前完全掌控别人看到的。七、TemplateSection SubmitBtnTemplateSection 是 4 个快捷模板校园风景/活动记录/人物特写/创意构图每行emoji 标题 描述Builder TemplateSection() { Column({ space: 12 }) { Text(快捷模板).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width(100%) Row({ space: 12 }) { ForEach(this.templates, (t: TemplateItem) { Column({ space: 6 }) { Row() { Text(t.icon).fontSize(24) } .width(44).height(44).backgroundColor(C.primarySoft).borderRadius(22).justifyContent(FlexAlign.Center) Text(t.title).fontSize(12).fontWeight(FontWeight.Bold).fontColor(C.text) Text(t.desc).fontSize(10).fontColor(C.textDim).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }) } .layoutWeight(1).padding({ top: 10, bottom: 10 }) .backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) .alignItems(HorizontalAlign.Center) .onClick(() { promptAction.showToast({ message: 应用模板: t.title }); }) }, (t: TemplateItem) t.id.toString()) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }4 个模板 4 种摄影题材校园风景/活动记录/人物特写/创意构图——模板即题材预设点击应自动填充标题/分类/场景/滤镜。SubmitBtn发布作品 50vp 高青绿按钮——点击弹 Toast 作品发布中...发布流程占位。八、State 的 6 状态设计Func1Tab 有 6 个 StateState类型用途联动titlestring标题输入预览卡标题descstring描述输入预览卡描述categoriesCategoryTag[]8 分类选中胶囊高亮/计数/预览标签selectedScenenumber场景1-6场景胶囊 预览场景名selectedFilternumber滤镜1-6滤镜胶囊 预览封面/滤镜名privacynumber隐私0-2隐私胶囊 预览文字6 个 State 覆盖 6 个交互点——State 数量 交互点数规律延续。categories是对象数组 StatetoggleCategory整体替换元素——ArkTS 响应式数组的关键知识点与 App 18 相同。九、与 App 18 发现页的对比维度App 18 发现匹配App 19 作品发布核心兴趣多选 匹配范围分类多选 场景/滤镜多选10 兴趣8 分类单选学院/年龄/范围场景/滤镜隐私匿名 Toggle公开/仅好友/私密三态预览昵称/兴趣/匿名滤镜封面/标题/场景/隐私/分类特色匿名模式滤镜实时预览App 19 的进步① 场景选择拍摄地元数据②滤镜选择 预览封面联动filters[selectedFilter-1].preview换 emoji③ 隐私三态比 App 18 的二元 Toggle 更细。发布表单 实时预览模板已复用 2 次App 18/19未来 App 20 会继续。十、滤镜选择的emoji 预览与真实滤镜的实现App 19 的滤镜选择用preview: ///...的 emoji 做滤镜预览这是 demo 的高明简化——真实项目的滤镜实现有 4 个层级1. 静态预览emoji/色块——demo 当前实现选滤镜 → 预览卡封面换 emoji。适合原型/教学。2. 颜色矩阵滤镜CSS Filter/Canvas——真实滤镜用颜色矩阵变换像素黑白去饱和度、暖色加红色调、冷色加蓝色调——每个滤镜 一组颜色矩阵参数// 伪代码颜色矩阵滤镜 const FILTERS { 黑白: { saturation: 0, contrast: 1.1 }, 暖色: { temperature: 1.3, saturation: 1.1 }, 冷色: { temperature: 0.8, saturation: 1.05 } };3. 叠加纹理胶片颗粒/光晕——胶片滤镜不只是调色还叠加颗粒感暗角纹理——滤镜 颜色变换 纹理叠加。4. 一键增强AI 滤镜——AI 自动调色智能白平衡/对比度/人像美颜——AI 滤镜是现代相机 App 的标配。滤镜的 UI 交互设计App 19 的参考价值滤镜列表横滑可滚动6 个以上选中的滤镜实时应用到预览图滤镜强度滑杆原图 ↔ 滤镜效果可调emoji 预览 → 颜色矩阵 → AI 滤镜是滤镜实现的进化路径——App 19 的 emoji 版是第 1 层读者做真实产品时按需求选层。十一、隐私三态与内容可见性设计App 19 的隐私三态公开/仅好友/私密是内容型 App 的可见性模型三态可见性递进隐私谁能看到场景公开所有人推荐/广场想被更多人看到仅好友好友列表私密分享私密仅自己草稿/私人记录公开 → 仅好友 → 私密是可见范围的递增收窄——微信朋友圈公开/仅好友/私密、小红书公开/私密同款——三档可见性是社交内容的标准模型。公开的推荐逻辑公开的作品 → 进首页推荐PhotoList、参与比赛、进分类排行——公开 参与内容生态仅好友/私密→ 不出现在公共列表——可见性 内容分发。真实项目的隐私进阶分组可见仅某分组好友可见指定不可见拉黑名单时间限定24 小时后转私密作品级权限不同作品不同可见性隐私三态 预览卡联动让用户在发布前就确认谁将看到——可见性前置确认是内容发布的信任设计用户不怕发错可见范围。十二、总结App 19 作品发布页解析完毕。8 分类多选toggleCategory 翻转 6 场景单选 6 滤镜选择预览联动 隐私三态 实时预览卡5 处联动是五大亮点。滤镜实时预览是摄影 App 发布页的核心体验——发布前完全掌控作品效果。