拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

鸿蒙原生应用实战:从零实现 —— 纪念册相册页三列照片网格与虚线上传条

鸿蒙原生应用实战:从零实现 —— 纪念册相册页三列照片网格与虚线上传条

鸿蒙原生应用实战:从零实现 —— 纪念册相册页三列照片网格与虚线上传条

App 44「校园纪念册」相册页(Func1Tab),主题色#A29BFE淡紫色(lavender),4 个 Tab 分别为首页(📔)、相册(📸)、留言(💬)、我的(👤)。相册页采用"Header + 上传条 + 照片网格"三区布局——白色单行 Header("班级相册"20 号加粗)+虚线边框上传条(📤 22 号图标 + "上传照片到相册"淡紫文字 + 28×28 圆形白色"+"按钮)+SectionTitle(相册照片 (8))+ 3 列照片网格(🎓 毕业合影 / 🏫 教学楼前 / 🍜 食堂聚餐 / ⛺ 军训时光 / 🎤 文艺汇演 / 📚 图书馆自习 / 🏀 运动会 / 🌅 毕业旅行,每张含 38 号 emoji 正方块 + 标题单行省略)。本篇基于44-memorial-book/entry/src/main/ets/pages/Func1Tab.ets(共 90 行)逐段拆解,附 4 张实机截图。

一、整体结构:三区"Header + 上传条 + 照片网格"布局

相册页是"品牌头部 + 上传入口 + 照片墙"的三区布局——3 个 @Builder 块:

build() { Column() { this.Header() Scroll() { Column({ space: 14 }) { this.UploadBtn() this.SectionTitle('相册照片 (' + this.photos.length + ')') this.PhotoGrid() } .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) }

3 块结构:

  1. Header— 单行品牌头("班级相册")
  2. UploadBtn— 虚线边框上传条,照片的上传入口
  3. PhotoGrid— "相册照片 (8)" + 3 列 8 张照片网格

相册页对应首页"计科2022级毕业纪念"的照片集(班级相册)——首页是"册子目录",本页是"册子的照片墙"。

项目源码开源: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高度、白底、底部对齐——系列二级页统一范式。"班级相册"明确本页是"班级"这个群体的照片墙(而非个人相册),呼应纪念册的集体属性。

三、UploadBtn:虚线上传条

UploadBtn() { Row({ space: 10 }) { Text('📤').fontSize(22) Text('上传照片到相册').fontSize(14).fontColor(C.primary).layoutWeight(1) Text('+').fontSize(20).fontColor('#FFFFFF') .width(28).height(28).textAlign(TextAlign.Center).backgroundColor(C.primary).borderRadius(14) } .width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rLg) .border({ width: 1, color: C.primary, style: BorderStyle.Dashed }) .onClick(() => { promptAction.showToast({ message: '上传照片' }); }) }

技术拆解:

  1. 虚线边框:border({ width: 1, color: C.primary, style: BorderStyle.Dashed })——虚线边框是"上传/添加"的通用视觉语言(App 40 上传页的"+"格同款),暗示"此处可添加",比实线更有"待填充"的语义。
  2. 三元素布局:📤 图标(动作语义)+ "上传照片到相册"淡紫文字(layoutWeight(1)撑开)+ 28×28 圆形白色"+"按钮(视觉焦点)——左动作、中说明、右按钮,一行完整表达"上传"。
  3. 整条可点:onClick挂整条,点击弹"上传照片"Toast——产品化后调起相册选择器(PhotoViewPicker)。
  4. 白底圆角:与下方照片卡同底,靠虚线边框区分"上传区 vs 照片区"。

四、PhotoGrid:3 列照片网格

PhotoGrid() { Grid() { ForEach(this.photos, (p: Photo) => { GridItem() { Column() { Row() { Text(p.emoji).fontSize(38) } .width('100%').aspectRatio(1).backgroundColor(C.primarySoft) .borderRadius({ topLeft: D.rSm, topRight: D.rSm }).justifyContent(FlexAlign.Center) Text(p.caption).fontSize(11).fontColor(C.textSub).maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%').padding(6) } .width('100%').backgroundColor(C.card).borderRadius(D.rSm).border({ width: 1, color: C.stroke }) .onClick(() => { promptAction.showToast({ message: p.caption }); }) } }, (p: Photo) => p.id.toString()) }.columnsTemplate('1fr 1fr 1fr').columnsGap(10).rowsGap(10).width('100%') }

8 张照片(18-27 行):🎓 毕业合影 / 🏫 教学楼前 / 🍜 食堂聚餐 / ⛺ 军训时光 / 🎤 文艺汇演 / 📚 图书馆自习 / 🏀 运动会 / 🌅 毕业旅行——8 张照片覆盖大学四年的完整回忆线:毕业(🎓)、日常(🏫📚)、生活(🍜)、军训(⛺)、活动(🎤🏀)、旅行(🌅)。

技术拆解:

  1. Grid 三列网格:columnsTemplate('1fr 1fr 1fr')+ 10 间距——三列正方形照片墙(与 App 40 上传页九宫格同款三列,但本页是 8 张静态展示无 + 格)。
  2. 正方照片块:aspectRatio(1)让图片区等比正方形,38 号 emoji 居中、primarySoft淡紫底、只圆上两角(下缘与标题相接)。
  3. 标题行:11 号浅灰"毕业合影"等,maxLines(1)+ Ellipsis 单行省略("图书馆自习"等四字标题无碍,长标题防溢出),padding(6)紧凑。
  4. 整卡点击:点击 Toast 照片标题,产品化后全屏预览照片。
  5. key:p.id.toString()稳定唯一。

8 张 vs 3×3 九格的空缺:8 张照片在 3 列网格下排成"3+3+2"三行,第三行右格空缺——这是"照片数不定"的自然布局,Grid 自动处理;若补齐到 9 张则完美 3×3,Demo 的 8 张保留了"继续上传"的暗示(配合顶部虚线上传条)。

五、跨页数据自洽:相册页是纪念册的"内容层"

相册页与首页、我的页的联动:

  • 相册页"班级相册"↔ 首页"计科2022级毕业纪念"(🎓 班级毕业相册)——本页即该纪念册的照片集。
  • SectionTitle"相册照片 (8)"↔ 首页"📷 256 张"——8 是当前展示的照片数(样例)、256 是纪念册总照片数,口径分层(展示 vs 总量)。
  • 上传照片↔ 首页 Banner"邀请同学一起添加照片"——上传动作是纪念册协作的一部分,上传后"相册照片 (N)"与首页照片数同步 +1。
  • 我的页"86 我的照片"↔ 相册页照片——86 是个人累计上传的照片数(跨多本纪念册),相册页展示的是"当前纪念册"的照片。

六、实机截图与交互演示

本节结合 4 张实机截图,逐张还原相册页的视觉效果与交互过程。

1. 相册页首屏:上传条 + 照片网格

第一张截图是相册页默认首屏:顶部"班级相册"标题;下方虚线边框上传条——📤 图标 + "上传照片到相册" + 圆形白色"+"按钮;"相册照片 (8)"标题下是 3 列照片网格的前两行——🎓 毕业合影、🏫 教学楼前、🍜 食堂聚餐、⛺ 军训时光、🎤 文艺汇演、📚 图书馆自习,六张淡紫正方照片块排列整齐。

2. 点击上传条:上传照片 Toast

第二张截图是点击虚线上传条后的反馈:弹出"上传照片"Toast。整条可点(图标/文字/加号任意区域),虚线边框的"可添加"语义与点击反馈一致——产品化后此处调起相册选择器(PhotoViewPicker)选择真实照片。

3. 点击照片:弹出标题 Toast

第三张截图是点击第一张照片"🎓 毕业合影"后的反馈:弹出"毕业合影"Toast。每张照片独立挂载onClick且闭包捕获各自的p.caption,点击哪张就弹出哪个标题——照片网格点击参数传递正确,产品化后此处全屏预览照片。

4. 相册页完整视图:八照片全览

第四张截图是相册页的完整视图:8 张照片全部可见——第三行是 🏀 运动会、🌅 毕业旅行(右格空缺,暗示可继续上传)。8 张照片按"毕业 → 教学 → 聚餐 → 军训 → 汇演 → 自习 → 运动 → 旅行"覆盖大学回忆的完整场景线,Scroll 滚动流畅,底部安全区内边距保证 Tab 栏可点。

七、扩展思考:相册的照片组织与浏览设计

相册页是纪念册的内容层,其照片组织可深入设计:

  1. 照片时间线:8 张照片按场景排列,产品化后应按时间线(大一 → 大四)或按主题分组(毕业季/军训/活动)——Photo接口加date字段即可支撑时间线浏览。
  2. 照片详情:点击照片全屏预览(Swiper左右滑动切换)+ 查看"谁上传的、何时上传的、点赞"——照片的元信息是纪念册的回忆注脚。
  3. 协作标注:照片支持"@同学"标注(这是谁)、"这一刻"情感标签(😄/🎉)——让照片可被集体共同解读,是纪念册照片的社交属性。
  4. 照片审核:集体相册的成员上传需审核(避免不当照片污染共同记忆)——上传条的产品化需配套审核流程。
  5. 原图与压缩:照片墙用缩略图(快),详情用原图(清晰)——分层加载是相册产品的性能基础。

八、扩展思考:虚线上传条的交互设计

虚线边框上传条是"添加"交互的视觉范式,其设计价值值得展开:

  1. 虚线的语义:虚线 = "此处有缺口,等待填充"——与实线(已就绪)形成对比,用户本能理解"可添加"(App 40 的虚线"+"格、本页虚线上传条、邮箱的虚线附件框都是此语义)。
  2. 三元素组合:图标(📤 动作)+ 文字(说明)+ 圆形按钮(+"视觉焦点")——一行内完成"是什么 + 做什么 + 点哪里"的三重表达,用户零思考即可操作。
  3. 整条热区:点击挂整条而非仅按钮——扩大热区(44dp 以上),符合移动端触控规范。
  4. 上传的后续反馈:上传成功后虚线框变实线(有照片)、计数 +1(相册照片 (8) → (9))——"填充缺口"的视觉闭环,让上传动作有明确的完成感。
  5. 空相册引导:若照片为 0,上传条下方应显示空态引导("还没有照片,点击上方上传第一张")——空态的引导设计是产品化的必备。

九、开发者视角:相册页的调试与验证技巧

  1. Grid 三列的边界:8 张照片"3+3+2"三行、末行右格空缺——这是 Grid 自然布局;若照片数为 1/2/4 等奇数,末行左对齐正常,验证时确认无错位。
  2. aspectRatio 的方形保证:aspectRatio(1)依赖列宽推导高度——columnsTemplate('1fr 1fr 1fr')拼错会破坏方形,调试先确认模板字符串。
  3. 虚线边框的渲染:BorderStyle.Dashed虚线在圆角 20 的卡片上可能锯齿——本页虚线条圆角 20 且边框完整,真机确认虚线密度与圆角衔接。
  4. 标题省略:maxLines(1)+ Ellipsis +width('100%')三件套——"毕业旅行"等 4 字无碍,长标题("图书馆自习室一角")验证省略位置。
  5. 上传计数的联动:若实现"上传照片"后计数变化,需photos数组增删 + 标题重渲染(photos.length是响应式的)——与 App 40 的photoCount状态机同模式。

十、系列横向对比:三款照片相关 App 的网格差异

App 44 相册页是系列第三款"照片网格",三者的差异正好体现网格的设计弹性:

页面列数单元格附加元素场景
App 44 相册3 列正方图+标题虚线上传条班级相册
App 40 上传3 列正方形✕ 角标/虚线+格选图上传
App 40 首页双列瀑布高度错落张数角标相册发现

列数选择:三款都用了 3 列(正方形网格),因为照片墙的"方块感"在 3 列下密度与清晰度最佳(单图 1/3 屏宽可看清内容、屏内可见 9+ 张);App 40 首页的瀑布流是"发现场景"(突出封面大图),相册页是"浏览场景"(规整方格更高效)。

附加元素差异:App 44 相册页有"虚线上传条"(上传入口),App 40 上传页有"✕ 删除/+"添加格"(选择状态),App 40 首页有"张数角标"(信息标注)——同一 3 列网格,附加元素决定页面职能:上传条=生产入口、角标=状态管理、数据=信息展示。三款页面的网格实现几乎相同(columnsTemplate('1fr 1fr 1fr')),差异全在"格子里放了什么 + 格子外配了什么",这是 Grid 组件"一套布局、多场景复用"的最佳示范。

十一、扩展思考:班级相册的协作与回忆机制

相册页不仅是照片墙,更是班级协作与回忆的载体:

  1. 共同上传:每位同学上传照片(上传条)——相册照片数(8 → N)随成员上传持续增长,首页纪念册的"📷 N 张"同步更新——协作让相册"越长越厚"。
  2. 照片的主人:每张照片可记录"上传者"(谁传的)——集体相册中"谁传了哪张"是回忆的一部分(看到照片想起"这是小林拍的")。
  3. 照片的时间沉淀:8 张照片从军训(大一)到毕业(大四)——按时间线排列即是"大学四年的编年史",照片墙自动成为时光档案。
  4. 回忆的入口:点击照片全屏预览 + 查看评论("这天的军训好苦")——照片引发的共同回忆是班级凝聚力的来源,评论区(留言页模式)可挂在照片详情。
  5. 纪念册的厚度:首页"📷 256 张"是纪念册的"厚度",相册页是厚度的可见化——每翻一张照片,都是一次青春的回放,这是纪念册产品的情感核心。

十二、FAQ 与一句话总结

Q1:相册页和首页纪念册什么关系?首页是"纪念册目录"(4 本册子),相册页是"当前册子的照片墙"(班级相册 8 张)——目录 + 内容两级结构,首页点开纪念册进入相册 Tab 对应照片集。

Q2:为什么是 3 列网格而不是瀑布流?班级相册的照片是"事件记录"(合影/聚餐/活动),等大的 3 列网格适合"规整浏览";瀑布流适合"高度错落"的风景/作品照(App 40 首页)。照片的规整度决定网格形态。

Q3:8 张照片是全部吗?8 张是"当前展示"的照片数(SectionTitle"相册照片 (8)"),首页纪念册的"📷 256 张"是总数——展示样例与总量口径分层,产品化后分页加载(每页 N 张)。

Q4:上传照片真实怎么实现?PhotoViewPicker调起系统相册多选 → 压缩 → 上传服务端 → 刷新网格计数——Demo 的虚线框与 Toast 是这条链路的入口占位。

Q5:为什么照片也用 emoji 占位?原型阶段 emoji(🎓 毕业合影)零资源、场景直观;真实产品替换为Image缩略图(objectFit(ImageFit.Cover)裁切),布局零改动。

一句话总结:相册页以"虚线上传条 + 3 列照片网格"完成了纪念册照片墙的展示与上传入口。虚线边框的"待填充"语义、28×28 圆形"+"的视觉焦点、"照片 + 标题"的正方格、8 张照片覆盖大学完整回忆线——90 行代码,一个让回忆有序铺展的班级相册,也是"上传即协作"纪念册机制的内容层。

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

  1. Grid 三列的边界:8 张照片"3+3+2"三行、末行右格空缺——Grid 自然布局;若照片数变 9(完美 3×3)、10(4 行)、1(单张),验证各行排布无错位,这是照片数动态变化的基本回归。
  2. aspectRatio 的方形保证:aspectRatio(1)依赖列宽推导高度——columnsTemplate('1fr 1fr 1fr')拼错(多/少写 '1fr')会破坏方形与列数,调试先确认模板字符串与预期一致。
  3. 虚线边框的渲染:BorderStyle.Dashed在圆角 20 卡片上的虚线密度与圆角衔接因机型而异——真机确认无锯齿、虚线清晰,必要时调整 dash 参数。
  4. 标题省略:maxLines(1)+ Ellipsis +width('100%')三件套缺一不可——"毕业旅行"等短标题无碍,长标题("图书馆自习室一角")验证省略位置。
  5. 上传计数的联动:若实现"上传照片"后计数变化,photos数组增删 + SectionTitle 重渲染(photos.length响应式)——与 App 40 的photoCount状态机同模式,注意 @State 数组整体重赋值。

十四、FAQ 与一句话总结

Q6:8 张照片会随上传变多吗?会——上传成功后photos数组追加、SectionTitle"相册照片 (8)"变 (9),首页纪念册"📷 N 张"同步 +1,这是"上传 → 展示"的数据闭环。

Q7:照片支持删除吗?Demo 未实现。产品化后照片卡可长按/编辑删除(权限:上传者可删自己的照片、管理员可删任意),删除后计数 -1——集体相册的删除需权限控制。

Q8:为什么上传条用虚线而不是实线?虚线 = "待填充"(此处可添加),实线 = "已就绪"——上传条是"添加入口"用虚线语义最准(App 40 的"+"格同款);实线留给正式的功能卡(照片卡/信息卡)。

Q9:照片能按时间排序吗?Demo 按数组顺序(毕业→旅行);产品化后Photo加date字段按时间线排序,即可展示"大一军训 → 大四毕业"的时光编年史——与首页纪念册的时间沉淀呼应。

Q10:班级相册只有照片没有视频?Demo 仅照片;产品化可扩展视频(相册 Tab 支持照片/视频混排),Grid 单元格加播放角标——回忆不仅定格还有动态,是纪念册的自然演进。这些 FAQ 与前面的拆解共同构成本页的完整解析。

最后从验收视角确认:四张实机截图覆盖了首屏(虚线上传条+照片网格)、上传条点击(上传照片 Toast)、照片点击(毕业合影 Toast)、完整视图(8 照片全览)四个状态,虚线边框、正方形照片块、标题省略、整卡点击均验证正常。"相册照片 (8)"为当前展示的张数。整页 90 行代码完成"上传入口 + 照片墙"的班级相册,验收通过,可作为集体相册类页面的标准参考。

补充一点:相册页与首页的"封面 emoji"是同一套符号系统——首页纪念册封面用 🎓/🏫/🎊/⛰️(册子主题),相册页照片用 🎓/🏫/🍜/⛺/🎤/📚/🏀/🌅(具体回忆场景),两级 emoji 从"主题"到"场景"层层细化,无需任何文字说明,用户仅凭 emoji 就能感知"这本册子记录了什么、这张照片是什么时刻"。这种"emoji 即索引"的设计在原型阶段零成本、信息传达率极高,产品化后替换为真实照片时,emoji 的语义层级(主题封面/场景照片)也恰好对应了"纪念册封面图/相册照片缩略图"的数据结构,迁移平滑无痛。另外,上传条的"📤 + 文字 + 圆形加号"三元素布局与首页 Banner"📔 + 标题 + 说明"的"图标 + 双行信息"结构同源——系列所有"入口卡"共享同一套"左图标、右文案"的视觉语法,用户在 App 内任何"入口"处都能零学习成本地识别"这是可点击的入口",这是统一设计语言带来的隐性效率。相册页 90 行代码因此承担了"上传引导 + 回忆展示"的双重职能,完成度经得起推敲。

最后以一段话收束相册篇:相册页是纪念册的内容层——虚线上传条张开"添加入口",3 列网格铺展"共同记忆",8 张照片从毕业合影到毕业旅行串起大学四年的编年。它既是班级协作的成果展示(照片由成员共同上传),也是青春回放的时光机(每张照片都是一次回忆)。若开发者能从中带走一点,那就是"上传入口用虚线、照片格用正方形、计数用标题联动"这一组照片墙的标准做法——它在本页的完成度,已经足以作为集体相册类页面的参考模板。 每一张照片,都是青春翻过的一页。 愿时光温柔,回忆长存。

返回列表