flutter for openharmony盲盒抽奖App应用实战+支付成功实现
支付成功页是整个支付流程的终点,也是用户情绪的高点。用户完成支付后,需要一个明确的反馈,告诉他们"支付成功了"。这个页面不能只是简单地显示"支付成功"四个字,而是要通过动画、颜色、文字,让用户感受到成功的喜悦。我在做这个页面时,特别注意了动画的使用。一个弹性的成功图标,能让用户感受到"完成"的满足感。同时,订单信息要清晰展示,让用户知道自己买了什么、花了多少钱。@override这是标准的 Sta

写在前面
支付成功页是整个支付流程的终点,也是用户情绪的高点。用户完成支付后,需要一个明确的反馈,告诉他们"支付成功了"。这个页面不能只是简单地显示"支付成功"四个字,而是要通过动画、颜色、文字,让用户感受到成功的喜悦。
我在做这个页面时,特别注意了动画的使用。一个弹性的成功图标,能让用户感受到"完成"的满足感。同时,订单信息要清晰展示,让用户知道自己买了什么、花了多少钱。
支付成功页的功能规划
在动手之前,我先想清楚了支付成功页要做什么。作为支付流程的结束页,这个页面的核心是:给用户明确的成功反馈,同时提供后续操作入口。
主要功能:
- 成功图标动画(弹性缩放)
- 成功提示文字
- 订单信息展示(订单号、金额、支付方式、时间)
- 返回首页按钮
- 查看订单按钮
功能不复杂,但要做得有仪式感,让用户觉得这次支付很值得。
页面结构搭建
页面类的定义
class PaymentSuccessPage extends StatefulWidget {
const PaymentSuccessPage({super.key});
State<PaymentSuccessPage> createState() => _PaymentSuccessPageState();
}
这是标准的 StatefulWidget,因为页面有动画需要管理。
动画控制器的初始化
class _PaymentSuccessPageState extends State<PaymentSuccessPage> with TickerProviderStateMixin {
late AnimationController _scaleController;
late Animation<double> _scaleAnimation;
void initState() {
super.initState();
_scaleController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
);
_scaleController.forward();
}
动画的设计思路:
- 动画时长600毫秒,不会太快也不会太慢
- 从0缩放到1,图标从无到有
- 用
Curves.elasticOut曲线,产生弹性效果- 页面加载时立即播放动画(
forward())为什么用弹性曲线?因为它能产生"弹跳"的效果,让成功图标看起来更有活力,更有"完成"的感觉。
释放动画资源
void dispose() {
_scaleController.dispose();
super.dispose();
}
动画控制器用完后必须释放,避免内存泄漏。这是Flutter开发的基本功。
页面布局结构
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 40),
_buildSuccessIcon(),
const SizedBox(height: 24),
_buildSuccessText(),
const SizedBox(height: 40),
_buildOrderInfo(),
const SizedBox(height: 24),
_buildActionButtons(context),
const SizedBox(height: 40),
],
),
),
),
);
}
布局思路:
- 用
SafeArea避免内容被刘海屏遮挡- 用
SingleChildScrollView让内容可以滚动- 从上到下依次是:成功图标、成功文字、订单信息、操作按钮
- 用
SizedBox控制间距,让页面有呼吸感顶部和底部都留了40的空间,让页面看起来更舒展。
成功图标动画
图标的实现
Widget _buildSuccessIcon() {
return ScaleTransition(
scale: _scaleAnimation,
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFFFD700), Color(0xFFFFA500)],
),
shape: BoxShape.circle,
),
child: const Icon(Icons.check, color: Colors.white, size: 60),
),
);
}
图标的设计:
- 用
ScaleTransition应用缩放动画- 圆形容器,直径100
- 金色渐变背景,跟App主题一致
- 白色对勾图标,size: 60,很大很醒目
为什么用圆形?因为圆形代表"完整"、“圆满”,在成功场景下很合适。
动画的效果
_scaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
);
弹性动画的原理:
Curves.elasticOut会让动画在接近终点时产生"过冲"和"回弹"的效果。就像一个弹簧,拉到最大后会弹回来一点,然后稳定下来。这种效果能让成功图标看起来更有活力,给用户一种"完成任务"的满足感。
成功提示文字
文字的实现
Widget _buildSuccessText() {
return Column(
children: [
const Text(
'支付成功',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'您的订单已成功提交',
style: TextStyle(fontSize: 14, color: Color(0xFF999999)),
),
],
);
}
文字的层次:
- 主标题"支付成功"用大字号(28)和粗体,最醒目
- 副标题"您的订单已成功提交"用小字号(14)和灰色,作为补充说明
- 两行文字之间用
SizedBox(height: 8)隔开为什么要有副标题?因为只说"支付成功"还不够,要告诉用户接下来会发生什么(订单已提交)。
订单信息展示
信息卡片的容器
Widget _buildOrderInfo() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
),
],
),
订单信息用白色卡片展示,配上很浅的阴影,让它从页面背景中凸显出来。
信息列表
child: Column(
children: [
_buildInfoRow('订单号', '2024011512345678'),
const Divider(height: 16),
_buildInfoRow('支付金额', '¥11257.00', isAmount: true),
const Divider(height: 16),
_buildInfoRow('支付方式', '微信支付'),
const Divider(height: 16),
_buildInfoRow('支付时间', '2024-01-15 12:34:56'),
],
),
信息的组织:
- 订单号:让用户可以查询订单
- 支付金额:最重要的信息,用特殊样式突出
- 支付方式:告诉用户用的什么方式付款
- 支付时间:记录支付的时间点
每项信息之间用
Divider分割,清晰明了。
信息行的封装
Widget _buildInfoRow(String label, String value, {bool isAmount = false}) {
return Row(
children: [
Text(label, style: const TextStyle(color: Color(0xFF999999))),
const Spacer(),
Text(
value,
style: TextStyle(
fontWeight: isAmount ? FontWeight.bold : FontWeight.normal,
color: isAmount ? const Color(0xFFFF6B6B) : const Color(0xFF333333),
fontSize: isAmount ? 16 : 14,
),
),
],
);
}
信息行的设计:
- 左边是标签,用灰色显示
- 右边是值,用深色显示
- 用
Spacer()让标签和值分别靠左右两边- 如果是金额(
isAmount: true),用粗体、红色、大字号突出显示这种左右对齐的布局在很多App里都能看到,因为它确实好用。
操作按钮
按钮区域的容器
Widget _buildActionButtons(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
// 返回首页按钮
// 查看订单按钮
],
),
);
}
按钮区域用
Padding包裹,左右各留16的间距。
返回首页按钮
GestureDetector(
onTap: () {
Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false);
},
child: Container(
width: double.infinity,
height: 50,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFFD700), Color(0xFFFFA500)],
),
borderRadius: BorderRadius.circular(25),
),
child: const Center(
child: Text(
'返回首页',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
),
返回首页按钮的设计:
- 金色渐变背景,跟App主题一致
- 圆角25,做成胶囊形状
- 白色加粗文字,醒目
- 宽度占满,高度50,方便点击
为什么用
pushNamedAndRemoveUntil?因为要清空导航栈,让用户不能通过返回键回到支付页。第二个参数(route) => false表示清空所有路由。
查看订单按钮
const SizedBox(height: 12),
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/my_orders');
},
child: Container(
width: double.infinity,
height: 50,
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFFFD700)),
borderRadius: BorderRadius.circular(25),
),
child: const Center(
child: Text(
'查看订单',
style: TextStyle(
color: Color(0xFFFFD700),
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
),
查看订单按钮的设计:
- 白色背景,金色边框
- 金色文字,跟边框颜色一致
- 样式跟返回首页按钮类似,但用边框而不是填充
为什么用边框样式?因为"查看订单"是次要操作,不能比"返回首页"更醒目。用边框样式能形成视觉层次。
一些细节优化
动画时长的选择
duration: const Duration(milliseconds: 600),
600毫秒是我试了很多次后定下来的。太快(比如300毫秒)会显得急躁,太慢(比如1000毫秒)又会让用户等得不耐烦。
600毫秒刚刚好,既能看清动画效果,又不会让用户觉得慢。
间距的统一
const SizedBox(height: 40), // 顶部和底部
const SizedBox(height: 24), // 模块之间
const SizedBox(height: 12), // 按钮之间
const SizedBox(height: 8), // 文字之间
所有的间距都是8的倍数(8、12、24、40),这样看起来更整齐,也更容易维护。
大的间距用40和24,小的间距用12和8,形成清晰的层次。
颜色的选择
Color(0xFFFFD700) // 金色
Color(0xFFFFA500) // 橙色
Color(0xFFFF6B6B) // 红色
Color(0xFF999999) // 灰色
Color(0xFF333333) // 深灰色
这些颜色都是精心挑选的:
- 金色和橙色用于渐变,跟App主题一致
- 红色用于金额,吸引注意
- 灰色用于标签,不抢眼
- 深灰色用于普通文字,清晰可读
字号的层次
fontSize: 28, // 主标题
fontSize: 16, // 按钮文字和金额
fontSize: 14, // 副标题和普通文字
不同的信息用不同的字号,形成清晰的层次。主标题最大,按钮文字和金额其次,普通文字最小。
踩过的坑
动画不流畅
一开始我用的是 Curves.linear,结果动画看起来很僵硬,没有生气。
解决方案:
改用
Curves.elasticOut,产生弹性效果:CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut)弹性曲线让动画看起来更有活力,更符合"成功"的情绪。
返回键导致问题
一开始我用的是 Navigator.pushNamed,结果用户可以通过返回键回到支付页,再次点击支付。
解决方案:
改用
pushNamedAndRemoveUntil,清空导航栈:Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false);这样用户只能通过"返回首页"或"查看订单"按钮离开,不能回到支付页。
动画资源没释放
测试时没发现问题,但用户反馈说多次进入支付成功页后App会卡。
解决方案:
在
dispose方法里释放动画控制器:void dispose() { _scaleController.dispose(); super.dispose(); }这是Flutter开发的基本功,创建了动画控制器就必须释放。
订单信息写死
一开始我把订单号、金额、时间都写死了,结果每次支付成功显示的都是同样的信息。
解决方案:
应该从路由参数或全局状态中获取真实的订单信息:
// 实际项目中应该这样 final order = ModalRoute.of(context)!.settings.arguments as Order; _buildInfoRow('订单号', order.id), _buildInfoRow('支付金额', '¥${order.amount}', isAmount: true),这里为了演示简化了,实际项目中必须用真实数据。
写在最后
支付成功页是整个支付流程的终点,也是用户情绪的高点。这个页面不能只是简单地告诉用户"支付成功了",而是要通过动画、颜色、文字,让用户感受到成功的喜悦。
我的设计原则:
-
动画要有仪式感。弹性的成功图标能让用户感受到"完成"的满足感,这种情绪上的反馈很重要。
-
信息要清晰完整。订单号、金额、支付方式、时间,这些信息都要展示,让用户心里有数。
-
操作要明确。提供两个按钮:返回首页和查看订单,让用户知道接下来可以做什么。
-
细节要到位。动画时长、间距、颜色、字号,这些细节都要精心调整,才能达到最好的效果。
做完支付成功页后,我拿给几个朋友试用。有人说"动画很有意思,有种完成任务的感觉",有人说"信息很清楚,一眼就能看到订单号和金额"。这些反馈让我觉得,那些细节的打磨是值得的。
一些经验:
-
动画不是越多越好,一个恰到好处的动画就够了。成功图标的弹性动画已经足够表达"成功"的情绪。
-
颜色要克制。虽然是成功页,但不能用太多鲜艳的颜色。金色渐变配白色文字,简洁又醒目。
-
按钮的层次要清晰。主要操作用填充样式,次要操作用边框样式,这样用户不会搞混。
下一步计划:
支付成功页做完了,接下来要做订单管理页。那个页面会展示用户的所有订单,支持筛选和查看详情。不过有了前面的基础,应该会更容易。
如果你也在做类似的项目,希望这篇文章能给你一些启发。支付成功页看起来简单,但要做出仪式感,让用户感受到成功的喜悦,还是需要在动画和细节上下功夫的。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)