基于 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 锁定高度 110,ListView 横滑 3 张,每张是渐变红色背景 + 大面额 + 满减条件 + “立即领取” 按钮的四段式。这种券模板可以无限复制,是国内电商和本地生活类必备组件。Padding(EdgeInsets.only(bottom: 4))是把"券"字往下推一点,让它和大字号的金额底端对齐,是处理大小字号混排的关键技巧。
SizedBox(height:110,child:ListView.separated(scrollDirection:Axis.horizontal,itemCount:coupons.length,separatorBuilder:(_,__)=>constSizedBox(width:10),itemBuilder:(_,i){finalc=coupons[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){finalactive=i==0;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(Map<String,String>h){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 骨架的前提下完成。下一篇继续第三组的第二块——宠物寄养预约系统。
