HarmonyOS运动健康——社区动态页面
社区页面是运动健康App的社交入口。用户从首页底部导航社区跳转到这里浏览其他用户的运动动态点赞、评论、关注。这个页面的任务是把运动从个人行为变成社交行为——你不是一个人在跑步有一群人和你一起流汗。页面结构很简单顶部导航栏返回按钮标题和下方的帖子信息流。信息流用Scroll包裹每条帖子是一个白色卡片包含用户头像、昵称、发布时间、正文内容、互动按钮。整个页面只有一个滚动方向——纵向没有横向滚动的元素。完整效果数据模型interface Post { user: string avatar: string content: string likes: number time: string }五个字段描述一条帖子用户名、头像emoji、正文、点赞数、发布时间。这个模型极度精简——没有帖子ID、没有图片、没有评论列表、没有是否已点赞的状态。原型阶段够用但离真实社区产品差很远。State posts: Post[] [ { user: 健身达人小王, avatar: , content: 今天完成了5组波比跳燃脂效果太棒了坚持就是胜利 , likes: 128, time: 2小时前 }, { user: 跑步爱好者, avatar: , content: 晨跑10公里打卡清晨的空气真的太好了一整天都充满活力, likes: 89, time: 5小时前 }, ... ]五条帖子用State声明——意味着posts数组是响应式的。但当前没有任何地方修改这个数组点赞不会加数、关注不会改变状态State其实是多余的。如果以后要加点赞交互State才能发挥作用点击❤️时修改p.likesUI会自动重新渲染。导航栏的返回按钮Row() { Row() { SymbolGlyph($r(sys.symbol.chevron_left)).fontSize(20).fontColor([T1]) }.width(34).height(34).borderRadius(17).backgroundColor(rgba(0,0,0,0.03)) .justifyContent(FlexAlign.Center) .onClick(() { router.back() }) Text(社区).fontSize(20).fontWeight(FontWeight.Bold).fontColor(T1) .margin({ left: 10 }).layoutWeight(1) }.width(100%).padding({ left: 16, right: 16, top: 12, bottom: 8 })返回按钮用SymbolGlyph渲染系统图标sys.symbol.chevron_left左箭头。外层Row提供34×34的点击区域和浅灰圆形背景内层SymbolGlyph只负责显示图标。和首页的返回按钮对比首页的导航栏没有返回按钮因为首页是根页面进度页和训练页的返回按钮用的是同样的SymbolGlyph模式。整个App的返回按钮风格统一——34×34圆形区域浅灰背景点击调用router.back()。帖子卡片的结构每条帖子是一个Column内部从上到下用户信息行、正文、互动按钮行。用户信息行Row() { Text(p.avatar).fontSize(24).width(44).height(44).borderRadius(22) .backgroundColor(#F5F5F7).textAlign(TextAlign.Center) Column() { Text(p.user).fontSize(14).fontWeight(FontWeight.Bold).fontColor(T1) Text(p.time).fontSize(11).fontColor(T3) }.alignItems(HorizontalAlign.Start).margin({ left: 10 }).layoutWeight(1) Text( 关注).fontSize(11).fontColor(A).fontWeight(FontWeight.Medium) .padding({ left: 10, right: 10, top: 4, bottom: 4 }) .backgroundColor(#FFF0F0).borderRadius(10) }.width(100%).margin({ bottom: 10 })头像是emoji文字、、不是图片。44×44的浅灰圆形区域居中显示emoji——这种文字头像在原型阶段非常实用不需要处理图片加载、缓存、裁剪的问题。用户名和发布时间在同一Column里alignItems(HorizontalAlign.Start)让文字左对齐。用户名14px加粗时间11px灰色——视觉层级清晰用户名更醒目。关注按钮在最右边layoutWeight(1)让中间的用户信息区域撑满剩余空间关注按钮被推到右侧。 关注用红色文字浅红背景#FFF0F0和首页的开始运动按钮同色系——红色在整个App中代表主要操作。正文内容Text(p.content).fontSize(14).fontColor(T1).lineHeight(22).width(100%).margin({ bottom: 10 })正文14px行高22px。lineHeight(22)比fontSize(14)大8px——行间距是字号的57%比浏览器默认的1.2倍16.8px略大。更大的行间距让长文本更易读尤其在手机小屏幕上。互动按钮行Row() { Text(❤️ p.likes.toString()).fontSize(12).fontColor(T3).margin({ right: 20 }) Text( 评论).fontSize(12).fontColor(T3) Blank() Text(分享).fontSize(12).fontColor(T3) }.width(100%)三个操作点赞❤️ 数字、评论 文字、分享纯文字。点赞在左分享在右评论在中间——Blank()把分享推到最右边。所有按钮都是纯展示没有绑定onClick。点赞数是静态的点击不会增加。评论和分享按钮点击也没有反应。这是原型和真实产品的最大区别——原型验证的是看起来对不对不是用起来对不对。Scroll和ForEach的配合Scroll() { Column({ space: 12 }) { ForEach(this.posts, (p: Post) { // 帖子卡片 }) Blank().height(20) }.width(100%).padding({ left: 16, right: 16 }) }.width(100%).layoutWeight(1)Scroll包裹ColumnColumn用space: 12控制卡片间距。ForEach渲染所有帖子最后加一个20px的Blank作为底部留白——避免最后一条帖子紧贴屏幕底部。layoutWeight(1)让Scroll占据导航栏以外的所有空间。导航栏在Scroll外面不会被滚动——这是固定头部可滚动内容区的标准模式。和ProgressPage的Scroll对比ProgressPage的Scroll里有多个Section柱状图、统计卡片每个Section之间用margin控制间距。CommunityPage的Scroll里只有一个ForEach用Column的space统一控制间距。前者更灵活不同Section可以有不同间距后者更简洁统一间距不需要逐个设置。为什么用Column而不是ListArkUI有专门的List组件用于长列表渲染支持懒加载只渲染可见区域的item。CommunityPage没有用List而是用ScrollColumnForEach——所有帖子一次性渲染。对于5条帖子的原型这两种方式没有区别。但如果帖子数量增加到50条、100条ScrollColumn会一次性创建所有DOM节点内存占用线性增长。List的懒加载只会渲染屏幕可见的几条性能好得多。原型阶段用ScrollColumn是对的——代码简单、逻辑清晰。但真实项目必须换成List否则长列表会卡顿。这是一个典型的原型决策——先跑通再优化。页面间导航的设计社区页面从首页底部导航的社区按钮跳转过来。底部导航有5个按钮首页、训练、进度、社区、我的。社区是第4个。但社区页面本身没有底部导航——它只有一个返回按钮。用户看完社区动态后只能点返回回到首页不能直接跳到训练或进度页。这意味着社区是一个死胡同页面进得去出不来除非返回首页。更好的设计是社区页面也保留底部导航让用户在社区、训练、进度之间自由切换。但这样做的代价是每个页面都要维护底部导航的状态——哪个tab是激活的、点击后跳转到哪里。原型阶段先简化社区是独立页面返回即回到入口。颜色体系的一致性社区页面用的颜色和首页完全一致颜色用途出现位置A (#FF4757)主色/强调色关注按钮文字、点赞图标T1 (#1E1B2E)主文字用户名、正文、标题T2 (#888888)次要文字无帖子中未使用T3 (#BBBBBB)辅助文字时间、互动按钮、副标题BG (#F8F7FC)页面背景社区页面背景CW (#FFFFFF)卡片背景帖子卡片背景这套颜色在首页、训练页、进度页、社区页都一样。用户不需要重新学习红色是什么意思——红色在整个App中始终代表主要操作或活力。一致性是UI设计的基本功但很多App做不到——每个页面自己定义一套颜色用户看得眼花缭乱。踩坑记录SymbolGlyph的fontColor参数格式。SymbolGlyph($r(sys.symbol.chevron_left)).fontColor([T1])——fontColor接受的是数组[T1]不是单个值T1。这是SymbolGlyph的特殊要求和Text的fontColor不同。如果写成.fontColor(T1)不带数组编译不会报错但运行时图标不显示颜色。这个坑在训练页的返回按钮也踩过一次。帖子间距的实现方式。Column的space: 12控制所有帖子之间的间距。但最后一张帖子和Scroll底部之间没有间距——用户滚动到底时最后一张卡片紧贴屏幕底部。最后加了Blank().height(20)解决这个问题。但更好的做法是给Scroll加padding({ bottom: 20 })这样不需要在内容里塞一个空的Blank。ForEach的key问题。ForEach(this.posts, (p: Post) { ... })没有提供key生成函数。ArkUI的ForEach如果不传key框架用数组index作为key。当数组顺序变化时比如删除一条帖子框架无法正确识别哪些item变了会导致动画异常。当前数组是静态的不会出问题。但如果以后加了下拉刷新替换整个数组不传key会导致列表闪烁。头像emoji的平台差异。不同设备对emoji的渲染不同——同一个在华为设备上可能比在三星设备上大一圈。44×44的固定尺寸能约束容器大小但emoji本身的大小不可控。如果要精确控制头像外观应该用图片代替emoji。但图片需要网络加载、缓存、失败回退——原型阶段emoji是更务实的选择。互动按钮的语义缺失。❤️、、分享——三个按钮用emoji文字表示。但在无障碍模式下屏幕阅读器emoji可能被读成红心符号而不是点赞按钮。真实产品应该用Icon组件或带aria-label的按钮让屏幕阅读器能正确识别。原型阶段可以忽略无障碍但上线前必须补上。社区页面的核心价值是社交氛围——让用户看到其他人也在运动形成群体归属感。五条帖子、五种运动、五种态度波比跳的燃脂、晨跑的活力、瑜伽的放松、营养搭配的科学、游泳的夏日感。每条帖子都传递运动是有趣的这个信息。从技术角度看这个页面是整个App中最简单的——没有复杂布局、状态管理和网络请求。但它的存在让App从工具变成了社区从我一个人练变成了和一群人一起练。