基于 Harmony 6.0 应用的附近优惠信息聚合应用实现
基于 Harmony 6.0 应用的附近优惠信息聚合应用实现前言我们每天经过的街道里藏着大量今天吃饭打八折新店开业第二份半价的本地优惠信息但这些信息分散在不同 App 的 Banner、不同店铺的橱窗、不同生活号的推送里用户很难一站式获取。所以附近优惠聚合是一种典型的、被互联网卷过又重新被需要的服务形态。Harmony 6.0 时代这类聚合型应用有几个新的成长点——一是分布式定位让附近的精度更高鸿蒙的位置服务可以融合 GPS / 蓝牙 / WiFi 指纹三源数据比传统单一 GPS 精准 5 倍以上二是 AI 助手集成让找折扣这类动作可以语音直达三是钱包凭证让领券即落袋成为系统级体验。本文用 Flutter 在 Harmony 6.0 上实现一个聚合优惠首页把定位 / 分类 Tab / 限时秒杀券 / 8 宫格分类 / 附近爆款五件事在一屏内讲清楚作为本三页一组的开篇。背景优惠类 App 的视觉锚点和外卖很像但又不同——外卖的核心是店优惠的核心是券。所以首页必须把券做成最具视觉冲击力的元素大字号面额、明显的过期提醒、一键领取按钮。橙红色作为主色与外卖差异化为更红一点的 #E11D48强调折扣、紧迫的心理暗示。本项目首页 5 个模块定位 Header、横滑分类 Tab、限时秒杀券3 张、8 宫格分类、附近爆款列表折扣 原价划线 销量。从产品角度优惠类应用的复购关键是省了多少钱的可视化——用户每次领券、用券都会带来一次省钱的正反馈所以应用要把今日已省 ¥XX“本月累计省 ¥XX这些指标做成显著的视觉元素强化用户继续使用的动机。鸿蒙 6.0 上做这类指标可视化非常方便——把今日已省做成一个原子化服务卡片放到桌面用户一眼就能看到自己的省钱进度”这种端外曝光对促活极其有效。Flutter × Harmony 6.0 跨端开发介绍Flutter 在 Harmony 6.0 上的部署方式与之前完全一致——保留 ohos 目录的鸿蒙工程壳UIAbility 内部加载 Flutter Engine 渲染 Dart UI。本系列继续坚守零依赖、纯 UI的原则所有页面都是 StatelessWidget所有交互预留 onTap。鸿蒙 6.0 在红色系#E11D48 / #F97316 / #FBBF24的渲染上有非常通透的 OLED 表现配合圆角卡片和大字号面额能营造出今天不薅就亏了的紧迫感。Skia 引擎对带有渐变和透明叠加的券卡片渲染极其稳定无需任何额外性能优化。从能力栈视角优惠聚合类应用最值得借助的鸿蒙能力是位置服务、钱包凭证、推送通道三件套——位置服务通过 LocationKit 提供高精度定位钱包凭证通过 WalletKit 让券落袋后能在系统钱包里随时调用推送通道通过 PushKit 在用户接近某个领过券的店铺时主动唤醒。这些能力的接入都需要在 ArkTS 端做适配层Flutter 这边通过 MethodChannel 接到结果做 UI 呈现。开发核心代码代码一限时秒杀券横滑 3 张券是优惠类首页的灵魂。我用 SizedBox 锁定高度 110ListView 横滑 3 张每张是渐变红色背景 大面额 满减条件 “立即领取” 按钮的四段式。这种券模板可以无限复制是国内电商和本地生活类必备组件。Padding(EdgeInsets.only(bottom: 4))是把券字往下推一点让它和大字号的金额底端对齐是处理大小字号混排的关键技巧。SizedBox(height:110,child:ListView.separated(scrollDirection:Axis.horizontal,itemCount:coupons.length,separatorBuilder:(_,__)constSizedBox(width:10),itemBuilder:(_,i){finalccoupons[i];returnContainer(width:180,padding:constEdgeInsets.all(14),decoration:BoxDecoration(gradient:LinearGradient(colors:[c[color]asColor,(c[color]asColor).withValues(alpha:0.7),]),borderRadius:BorderRadius.circular(14),),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Row(crossAxisAlignment:CrossAxisAlignment.end,children:[constText(¥,style:TextStyle(color:Colors.white,fontSize:14,fontWeight:FontWeight.w700)),constSizedBox(width:2),Text(c[amount]!,style:constTextStyle(color:Colors.white,fontSize:30,fontWeight:FontWeight.w900)),constSizedBox(width:4),constPadding(padding:EdgeInsets.only(bottom:4),child:Text(券,style:TextStyle(color:Colors.white70,fontSize:12)),),]),constSizedBox(height:4),Text(c[cond]!,style:constTextStyle(color:Colors.white70,fontSize:11)),constSpacer(),Container(padding:constEdgeInsets.symmetric(horizontal:10,vertical:4),decoration:BoxDecoration(color:Colors.white,borderRadius:BorderRadius.circular(20)),child:Text(立即领取,style:TextStyle(color:c[color]asColor,fontSize:12,fontWeight:FontWeight.w700)),),],),);},),)券的立即领取按钮在生产业务里点击后应该自动触发两个动作一是后端记录领券、二是把券送进鸿蒙钱包。第二个动作通过 ArkTS 端 WalletKit 的 addPass 接口完成券会以电子凭证形式落袋到系统钱包用户走到对应店铺时鸿蒙会自动弹出凭证。这种领券即落袋的体验是鸿蒙生态相对于安卓的最大体验差异之一。从横滑列表的视觉节奏与色彩配方角度再补一段。三张券分别用不同的主色如红、橙、紫做渐变背景每张券走满色 → 70% 透明度的同色对角渐变——这种「同色双段渐变」配方比双色拼接更克制更高级既能把券的品牌主色彩「立起来」又能避免色彩太杂导致视觉疲劳。Container(width: 180)锁定每张券宽度配合外层SizedBox(height: 110)锁定高度整条横滑区域形成「180×110」的固定矩阵不会因为内容长度变化而抖动。立即领取按钮做成「白底 主题色文字 圆角 20」的胶囊形态比起常见的「主色填充 白字」更轻量也更贴合券的语义——胶囊形小按钮更容易让用户产生「点一下就到手」的轻盈感。鸿蒙 6.0 上做这套渐变券Skia 的色彩管线对透明度过渡的精度高于 Android 早期版本不会出现「渐变带状色阶」的现象OLED 屏下看尤其通透。代码二横滑分类 Tab优惠类应用的二级分类多得吓人——美食、娱乐、休闲、购物、丽人、健身。做成横滑 Tab 是最聪明的方案避免一次性铺满首页。每个 Tab 一个圆角小 chip被选中的那个用主色填充。横滑 Tab 在鸿蒙 6.0 上滚动手感非常顺滑无需任何手动加 BouncingScrollPhysics()。Widget_tabs(){finaltabs[全部,美食,丽人,娱乐,购物,健身,休闲];returnSizedBox(height:32,child:ListView.separated(scrollDirection:Axis.horizontal,itemCount:tabs.length,separatorBuilder:(_,__)constSizedBox(width:8),itemBuilder:(_,i){finalactivei0;returnContainer(padding:constEdgeInsets.symmetric(horizontal:14),alignment:Alignment.center,decoration:BoxDecoration(color:active?_red:_card,borderRadius:BorderRadius.circular(16),),child:Text(tabs[i],style:TextStyle(color:active?Colors.white:_ink,fontSize:12,fontWeight:FontWeight.w600)),);},),);}Tab 选中态的切换在真实业务里需要把 active 从常量改成 state本文为了保持页面纯展示性故意做成静态。如果要让 Tab 切换带动整页内容刷新可以用 StatefulWidget setState或者引入轻量级的 ValueNotifier鸿蒙端 Flutter Engine 对响应式重建的开销控制得很好。从交互体验和切换动效角度再补一段。这套横滑 Tab 用「圆角胶囊 主色填充」做选中态「灰底 黑字」做未选中态是国内 App 里最通用的视觉编码——用户能在第一眼识别出当前 Tab。如果要进一步提升交互质感可以在切换时用AnimatedContainer让背景色 0.2 秒过渡再配合AnimatedDefaultTextStyle让文字颜色同步过渡整个 Tab 切换会有「丝滑感」而不是「硬切」。鸿蒙 6.0 端 Flutter Engine 的隐式动画驱动是对齐 vsync 的60Hz 屏下 12 帧、120Hz 屏下 24 帧的过渡都不会丢帧与 ArkUI 原生的animateTo表现完全一致。如果未来要做「指示条横滑跟随」的进阶效果被选中 Tab 下面有一根小色条可以在外层 Stack 上叠一个AnimatedPositioned控制指示条的 left 偏移依旧保持单文件 200 行内可控。代码三附近爆款列表爆款卡片需要把折扣、原价划线、销量、距离全部塞进去。我用 Row 把图片和信息分成左右两栏价格用 Row 加crossAxisAlignment: CrossAxisAlignment.end让现价和原价底端对齐再给原价加lineThrough。红色 划线灰是优惠类必备搭配。Widget_hotItem(MapString,Stringh){returnContainer(margin:constEdgeInsets.only(bottom:10),padding:constEdgeInsets.all(12),decoration:BoxDecoration(color:_card,borderRadius:BorderRadius.circular(14)),child:Row(children:[Container(width:80,height:80,decoration:BoxDecoration(color:_red.withValues(alpha:0.12),borderRadius:BorderRadius.circular(12)),child:constIcon(Icons.local_offer,color:_red,size:36),),constSizedBox(width:12),Expanded(child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Text(h[name]!,style:constTextStyle(color:_ink,fontSize:14,fontWeight:FontWeight.w700)),constSizedBox(height:4),Text(${h[shop]} · ${h[dist]},style:constTextStyle(color:_sub,fontSize:12)),constSizedBox(height:8),Row(crossAxisAlignment:CrossAxisAlignment.end,children:[Text(¥${h[now]},style:constTextStyle(color:_red,fontSize:18,fontWeight:FontWeight.w800)),constSizedBox(width:6),Text(¥${h[origin]},style:constTextStyle(color:_sub,fontSize:11,decoration:TextDecoration.lineThrough)),constSpacer(),Text(已抢 ${h[sold]},style:constTextStyle(color:_sub,fontSize:11)),]),],)),]),);}距离信息在真实业务里来自鸿蒙位置服务实时计算可通过 ArkTS 端 LocationKit 拿到当前坐标然后与店铺坐标做球面距离计算。这种数据更新频率高的场景建议用 StreamBuilder 包裹整个列表鸿蒙端 Flutter Engine 对流式数据更新有专门的局部重绘优化不会全列表刷新。从价格混排和削减线渲染细节角度再补一段。这段「现价 18 号红粗 原价 11 号灰删除线」的混排关键在于crossAxisAlignment: CrossAxisAlignment.end让两个不同字号的数字底端对齐——如果不显式指定默认顶端对齐会让小字「飘」在大字上方看起来很别扭。TextDecoration.lineThrough这条删除线在 Harmony 6.0 端的 Skia 渲染表现非常规范不会出现 Android 老版本里删除线偏上半个字符的小 bug。删除线的颜色会自动跟随文字颜色所以原价用_sub灰色后删除线也是灰色与红色现价形成明显视觉对比。Spacer()把销量信息推到右侧底部与价格组形成「左价格、右销量」的对称排版——这种排版在国内电商类 UI 里极其常见因为人眼会习惯性地左看价格右扫销量符合阅读动线。如果商品有多种规格价格可以把Text(¥${h[now]}, ...)改成Wrap容器内放多个价格胶囊骨架不变。心得优惠类应用最重要的事情是营造紧迫感但又不能让用户感到焦虑。这条边界靠的是配色平衡——红色用在金额、按钮、图标灰色用在划线原价和说明文字白色卡片背景做缓冲。三色严格分层整页就既紧又稳。鸿蒙 6.0 OLED 屏对红色的呈现非常通透比 LCD 屏少 20% 左右的色彩漂移配合 Flutter 自绘的圆角阴影整页氛围非常符合我得赶紧领的产品意图。从工程化能力角度优惠类应用最有价值的鸿蒙端能力组合是位置服务 钱包凭证 桌面卡片 AI 助手把这四件事串起来就形成一个独特的体验闭环——用户走过店铺时手机自动唤醒位置、领的券进系统钱包凭证、累计省了多少钱在桌面卡片可见卡片、想找新券问 AI 助手即可直达语义。每一个环节都是 Android 上很难做、iOS 上做不了的能力是鸿蒙生态对这类应用最大的价值锚点。总结本篇实现了 Harmony 6.0 端的附近优惠信息聚合首页5 个模块、纯 UI、零依赖。读者可把骨架直接迁移到团购、闪购、社区福利等场景。从工程化扩展角度建议生产业务里把定位接入 LocationKit把领券落袋接入 WalletKit把今日已省做成 FormExtensionAbility 桌面卡片把找美食优惠接入 AI 助手语义路由把列表改成 ListView.builder 以支持大数据集。这一切扩展都可以在不动当前 UI 骨架的前提下完成。下一篇继续第三组的第二块——宠物寄养预约系统。