请添加图片描述

写在前面

支付功能是整个App最核心的环节之一。用户选好商品、确认订单后,就到了支付这一步。这个页面看起来简单,就是显示金额、选择支付方式、点击确认,但实际上要考虑的细节很多。支付金额的展示、支付方式的选择、支付按钮的状态,每个环节都要做到位。

我在做这个页面时,特别注意了视觉的聚焦。用户进来后,第一眼应该看到的是支付金额,其次是支付方式,最后是确认按钮。整个页面的信息流要顺畅,不能让用户思考。

支付页的功能规划

在动手之前,我先想清楚了支付页要做什么。作为支付流程的最后一步,这个页面的核心是:让用户清楚地知道要付多少钱,用什么方式付

主要功能:

  • 显示实付金额(大字号,醒目)
  • 支付方式选择(微信、支付宝)
  • 确认支付按钮
  • 支付成功后跳转到结果页

功能不多,但每个都要做到极致。特别是支付金额的展示,必须让用户一眼就能看到。

页面参数设计

接收支付金额

class PaymentPage extends StatefulWidget {
  final double amount;

  const PaymentPage({super.key, required this.amount});

  
  State<PaymentPage> createState() => _PaymentPageState();
}

为什么金额是必传参数?

因为支付页必须知道要付多少钱。这个金额从订单确认页传过来,不能在支付页里计算,避免数据不一致。

required 标记,确保调用时必须传入金额。

状态变量定义

class _PaymentPageState extends State<PaymentPage> {
  int _selectedPaymentMethod = 0; // 0: 微信支付, 1: 支付宝

用一个整数表示选中的支付方式,0是微信,1是支付宝。这样比用字符串更简洁,也更容易扩展。

如果以后要加更多支付方式,只需要增加数字就行了。

页面结构搭建

顶部导航栏


Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.white,
    appBar: AppBar(
      backgroundColor: Colors.white,
      elevation: 0,
      leading: IconButton(
        icon: const Icon(Icons.arrow_back_ios, color: AppColors.black, size: 20),
        onPressed: () => Navigator.pop(context),
      ),
      title: const Text(
        '收银台',
        style: TextStyle(color: AppColors.black, fontSize: 18),
      ),
      centerTitle: true,
    ),

导航栏的设计:

  • 标题用"收银台"而不是"支付",更有仪式感
  • 白色背景,elevation: 0 去掉阴影,简洁
  • 返回按钮用iOS风格的箭头,更现代

整个导航栏很简洁,不会分散用户注意力。

页面布局结构

body: Column(
  children: [
    // 支付金额
    _buildAmountSection(),
    const SizedBox(height: 12),
    // 支付方式选择
    _buildPaymentMethods(),
    const Spacer(),
    // 确认支付按钮
    _buildConfirmButton(),
  ],
),

布局思路:

  • 顶部显示支付金额
  • 中间显示支付方式选择
  • Spacer() 把按钮推到底部
  • 底部是确认支付按钮

这种布局让页面看起来很舒展,不会挤在一起。

支付金额展示

金额区域的容器

Widget _buildAmountSection() {
  return Container(
    padding: const EdgeInsets.symmetric(vertical: 30),
    child: Column(
      children: [
        const Text(
          '实付金额',
          style: TextStyle(
            fontSize: 14,
            color: AppColors.grey,
          ),
        ),

金额区域用 padding: EdgeInsets.symmetric(vertical: 30) 留出足够的空间,让金额有呼吸感。

"实付金额"用小字号和灰色,作为标签,不抢眼。

金额数字的展示

const SizedBox(height: 12),
Text(
  ${widget.amount.toStringAsFixed(2)}',
  style: const TextStyle(
    fontSize: 36,
    color: AppColors.black,
    fontWeight: FontWeight.bold,
  ),
),

金额展示的设计:

  • 字号36,非常大,用户一眼就能看到
  • 黑色加粗,醒目
  • toStringAsFixed(2) 保留两位小数,显示完整的金额

为什么不用红色?因为红色在支付场景下可能让用户紧张。黑色更中性,更专业。

支付方式选择

支付方式区域的容器

Widget _buildPaymentMethods() {
  return Container(
    color: const Color(0xFFF5F5F5),
    child: Container(
      margin: const EdgeInsets.only(top: 12),
      color: Colors.white,

用一个浅灰色背景(0xFFF5F5F5)包裹,里面是白色的支付方式列表。这样能形成视觉层次,让支付方式区域更突出。

支付方式的标题

child: Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    const Padding(
      padding: EdgeInsets.all(16),
      child: Text(
        '选择支付方式',
        style: TextStyle(
          fontSize: 14,
          color: AppColors.grey,
        ),
      ),
    ),

标题用小字号和灰色,作为提示文字,不会太抢眼。

微信支付选项

// 微信支付
_buildPaymentOption(
  index: 0,
  icon: _buildWechatIcon(),
  label: '微信支付',
),
const Divider(height: 1, indent: 56),

微信支付是第一个选项,索引为0。两个选项之间用 Divider 分割,indent: 56 让分割线从图标后面开始,更精致。

支付宝选项

// 支付宝
_buildPaymentOption(
  index: 1,
  icon: _buildAlipayIcon(),
  label: '支付宝',
),

支付宝是第二个选项,索引为1。

支付选项的实现

Widget _buildPaymentOption({
  required int index,
  required Widget icon,
  required String label,
}) {
  final bool isSelected = _selectedPaymentMethod == index;
  return GestureDetector(
    onTap: () => setState(() => _selectedPaymentMethod = index),
    behavior: HitTestBehavior.opaque,

选项的交互:

点击选项时,调用 setState 更新 _selectedPaymentMethod,触发重新构建。

behavior: HitTestBehavior.opaque 让整个选项区域都可以点击,不只是文字和图标。这样用户点击空白处也能选中,体验更好。

选项的布局

child: Padding(
  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
  child: Row(
    children: [
      icon,
      const SizedBox(width: 12),
      Expanded(
        child: Text(
          label,
          style: const TextStyle(
            fontSize: 15,
            color: AppColors.black,
          ),
        ),
      ),

选项的布局:

  • 左边是支付方式的图标
  • 中间是支付方式的名称,用 Expanded 占满剩余空间
  • 右边是单选按钮

上下padding是14,左右padding是16,让选项有足够的点击区域。

单选按钮的实现

Container(
  width: 22,
  height: 22,
  decoration: BoxDecoration(
    shape: BoxShape.circle,
    color: isSelected ? const Color(0xFF07C160) : Colors.transparent,
    border: Border.all(
      color: isSelected ? const Color(0xFF07C160) : AppColors.grey,
      width: 1.5,
    ),
  ),
  child: isSelected
      ? const Icon(Icons.check, color: Colors.white, size: 14)
      : null,
),

单选按钮的状态:

  • 选中时:绿色背景(0xFF07C160,微信的绿色),白色对勾
  • 未选中时:透明背景,灰色边框

为什么用绿色?因为绿色代表"确认"、“通过”,在支付场景下能给用户信心。

支付方式图标

微信图标

Widget _buildWechatIcon() {
  return Container(
    width: 28,
    height: 28,
    decoration: BoxDecoration(
      color: const Color(0xFF07C160),
      borderRadius: BorderRadius.circular(4),
    ),
    child: const Icon(
      Icons.chat_bubble,
      color: Colors.white,
      size: 18,
    ),
  );
}

微信图标的设计:

  • 用微信的品牌色(0xFF07C160)作为背景
  • 用聊天气泡图标代表微信
  • 圆角4,不是完全的圆形,更有设计感

虽然不是微信的官方图标,但用户一看到绿色就能联想到微信。

支付宝图标

Widget _buildAlipayIcon() {
  return Container(
    width: 28,
    height: 28,
    decoration: BoxDecoration(
      color: const Color(0xFF1677FF),
      borderRadius: BorderRadius.circular(4),
    ),
    child: const Center(
      child: Text(
        '支',
        style: TextStyle(
          color: Colors.white,
          fontSize: 16,
          fontWeight: FontWeight.bold,
        ),
      ),
    ),
  );
}

支付宝图标的设计:

  • 用支付宝的品牌色(0xFF1677FF)作为背景
  • 用"支"字代表支付宝
  • 白色加粗文字,醒目

这种简化的图标设计在很多App里都能看到,简洁又有辨识度。

确认支付按钮

按钮的容器

Widget _buildConfirmButton() {
  return Container(
    padding: const EdgeInsets.all(16),
    child: SafeArea(

SafeArea 包裹,确保按钮不会被底部的Home指示器遮挡。

padding是16,给按钮留出足够的空间。

按钮的样式

child: GestureDetector(
  onTap: _confirmPayment,
  child: Container(
    width: double.infinity,
    height: 50,
    decoration: BoxDecoration(
      color: AppColors.primary,
      borderRadius: BorderRadius.circular(25),
    ),
    child: const Center(
      child: Text(
        '确认支付',
        style: TextStyle(
          fontSize: 16,
          color: AppColors.black,
          fontWeight: FontWeight.bold,
        ),
      ),
    ),
  ),
),

按钮的设计:

  • 宽度占满(width: double.infinity
  • 高度50,足够大,方便点击
  • 金色背景,跟App主题一致
  • 圆角25,做成胶囊形状
  • 文字黑色加粗,醒目

为什么文字用黑色而不是白色?因为金色背景配黑色文字对比度更高,更容易看清。

支付确认逻辑

void _confirmPayment() {
  // 跳转到开箱结果页面
  Navigator.pushReplacement(
    context,
    MaterialPageRoute(
      builder: (_) => BoxResultPage(price: widget.amount),
    ),
  );
}

支付确认的流程:

点击按钮后,直接跳转到开箱结果页。用 pushReplacement 而不是 push,这样用户不能通过返回键回到支付页。

实际项目中,这里应该先调用支付接口,等支付成功后再跳转。如果支付失败,应该显示错误提示。

一些细节优化

颜色的选择

const Color(0xFF07C160)  // 微信绿
const Color(0xFF1677FF)  // 支付宝蓝
const Color(0xFFF5F5F5)  // 浅灰色背景

这些颜色都是精心挑选的:

  • 微信绿和支付宝蓝是它们的品牌色,用户一看就知道
  • 浅灰色背景能形成层次,但不会太抢眼

颜色的选择很重要,用对了能提升用户体验。

间距的统一

padding: const EdgeInsets.symmetric(vertical: 30),  // 金额区域
padding: const EdgeInsets.all(16),  // 标题和按钮
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),  // 选项

所有的间距都是偶数,而且是4的倍数(12、14、16、30)。这样看起来更整齐,也更容易维护。

字号的层次

fontSize: 36,  // 支付金额
fontSize: 18,  // 页面标题
fontSize: 16,  // 按钮文字
fontSize: 15,  // 支付方式名称
fontSize: 14,  // 提示文字

不同的信息用不同的字号,形成清晰的层次:

  • 支付金额最大(36),最重要
  • 页面标题其次(18)
  • 按钮文字和支付方式名称适中(15-16)
  • 提示文字最小(14),不抢眼

踩过的坑

支付方式选择不生效

一开始我忘了在点击时调用 setState,结果点击支付方式后界面没有更新。

解决方案:

onTap 里调用 setState

onTap: () => setState(() => _selectedPaymentMethod = index),

这是Flutter的基本功,状态变化后必须调用 setState 才会重新构建。

点击区域太小

一开始我只给图标和文字加了点击事件,结果用户点击空白处没反应。

解决方案:

给整个选项加上 GestureDetector,并设置 behavior: HitTestBehavior.opaque

GestureDetector(
  onTap: () => setState(() => _selectedPaymentMethod = index),
  behavior: HitTestBehavior.opaque,
  child: Padding(...),
)

这样整个选项区域都可以点击,用户体验更好。

按钮被底部遮挡

在有Home指示器的手机上(比如iPhone X及以后的机型),按钮会被底部的指示器遮挡。

解决方案:

SafeArea 包裹按钮:

Container(
  padding: const EdgeInsets.all(16),
  child: SafeArea(
    child: GestureDetector(...),
  ),
)

SafeArea 会自动避开系统UI,确保按钮完全可见。

金额显示不完整

一开始我直接用 widget.amount.toString(),结果有时候显示 59.9,有时候显示 59.0,不统一。

解决方案:

toStringAsFixed(2) 固定显示两位小数:

Text(${widget.amount.toStringAsFixed(2)}')

这样无论金额是多少,都会显示两位小数,看起来更专业。

写在最后

支付页是整个支付流程的最后一步,必须做到简洁、清晰、可靠。用户在这个页面停留的时间不会太长,但如果体验不好,很容易导致用户放弃支付。

我的设计原则:

  1. 信息聚焦。支付金额用超大字号,用户一眼就能看到。其他信息都是辅助,不能抢眼。

  2. 操作简单。只需要选择支付方式,点击确认,两步完成。不要加太多复杂的功能。

  3. 视觉舒适。用品牌色表示支付方式,用绿色表示确认,这些都是用户熟悉的视觉语言。

  4. 细节到位。点击区域要大,按钮要避开系统UI,金额要显示完整,这些细节都要考虑到。

做完支付页后,我拿给几个朋友试用。有人说"很简洁,一眼就知道怎么操作",有人说"支付金额很醒目,不会看错"。这些反馈让我觉得,简单就是最好的设计。

下一步计划:

支付页做完了,接下来要做支付成功页。那个页面会有一些动画效果,让用户感受到支付成功的喜悦。不过有了前面的基础,应该会更容易。

如果你也在做类似的项目,希望这篇文章能给你一些启发。支付功能看起来简单,但每个细节都要考虑周全,才能给用户最好的体验。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐