请添加图片描述

写在前面

地址管理是电商App的基础功能之一。用户下单时需要选择收货地址,平时也需要管理自己的地址列表。这个功能看起来简单,就是一个列表页,但实际做起来发现有不少细节要处理。地址展示、默认地址标识、编辑跳转、添加新地址,每个环节都要考虑周全。

我在做这个页面时,特别注意了信息的层次。地址是最重要的信息,要最醒目;联系人和电话是次要信息,用小字号和灰色;默认地址要有明显的标识,让用户一眼就能看到。

地址管理页的功能规划

在动手之前,我先梳理了一下地址管理页需要做什么。作为用户管理收货地址的入口,这个页面的核心是:让用户方便地查看、编辑、添加收货地址

主要功能:

  • 地址列表展示
  • 默认地址标识
  • 编辑地址入口
  • 添加新地址按钮
  • 地址信息完整展示(姓名、电话、地址)

功能不复杂,但要做到清晰易用,不能让用户觉得混乱。

页面结构搭建

页面类的定义

class AddressListPage extends StatefulWidget {
  const AddressListPage({super.key});

  
  State<AddressListPage> createState() => _AddressListPageState();
}

这是标准的 StatefulWidget,因为页面有地址列表需要管理。

模拟地址数据

class _AddressListPageState extends State<AddressListPage> {
  final List<Map<String, dynamic>> addresses = [
    {
      'name': '西洲',
      'phone': '134****2222',
      'address': '辽宁省沈阳市大东区小东街道天润广场',
      'isDefault': true,
    },
    {
      'name': '西洲',
      'phone': '134****2222',
      'address': '辽宁省沈阳市大东区小东街道天润广场',
      'isDefault': false,
    },
  ];

这里用的是硬编码的地址数据,实际项目中应该从API获取。

每个地址包含:姓名、电话、详细地址、是否默认。电话号码做了脱敏处理(134****2222),保护用户隐私。

页面布局结构


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: [
    // 地址列表
    Expanded(
      child: ListView.separated(
        itemCount: addresses.length,
        separatorBuilder: (context, index) => const Divider(height: 1),
        itemBuilder: (context, index) {
          return _buildAddressItem(addresses[index]);
        },
      ),
    ),
    // 添加新地址按钮
    _buildAddButton(),
  ],
),

布局思路:

  • 上面是地址列表,用 Expanded 占满剩余空间
  • 下面是添加新地址按钮,固定在底部
  • ListView.separated 在地址项之间自动添加分割线

这种布局很常见,列表可以滚动,按钮固定在底部,方便用户随时添加新地址。

地址项的实现

地址项的容器

Widget _buildAddressItem(Map<String, dynamic> address) {
  final bool isDefault = address['isDefault'] as bool;
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [

地址项用 Padding 包裹,上下左右都留出空间。用 Row 横向排列,左边是地址信息,右边是编辑按钮。

crossAxisAlignment: CrossAxisAlignment.start 让内容从顶部对齐,避免编辑按钮跑到中间。

地址信息展示

// 地址信息
Expanded(
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // 地址行
      Row(
        children: [
          if (isDefault)
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
              margin: const EdgeInsets.only(right: 8),
              decoration: BoxDecoration(
                color: AppColors.primary,
                borderRadius: BorderRadius.circular(4),
              ),
              child: const Text(
                '默认',
                style: TextStyle(fontSize: 10, color: AppColors.black),
              ),
            ),

默认地址标签的设计:

  • 只有默认地址才显示标签(if (isDefault)
  • 金色背景,黑色文字,小圆角
  • 字号10,很小,不会太抢眼
  • 右边留8的margin,跟地址文字隔开

这个标签能让用户一眼看出哪个是默认地址。

地址文字

Expanded(
  child: Text(
    address['address'] as String,
    style: const TextStyle(
      fontSize: 15,
      color: AppColors.black,
    ),
  ),
),

地址文字用 Expanded 占满剩余空间,这样即使地址很长,也不会溢出。

字号15,黑色,清晰可读。

联系人信息

const SizedBox(height: 8),
// 联系人信息
Row(
  children: [
    Text(
      address['name'] as String,
      style: const TextStyle(
        fontSize: 13,
        color: AppColors.grey,
      ),
    ),
    const SizedBox(width: 16),
    Text(
      address['phone'] as String,
      style: const TextStyle(
        fontSize: 13,
        color: AppColors.grey,
      ),
    ),
  ],
),

联系人信息的设计:

  • 姓名和电话横向排列,中间用 SizedBox(width: 16) 隔开
  • 字号13,比地址小一点
  • 灰色显示,表示这是次要信息

这样的层次设计能让用户快速找到重点信息。

编辑按钮

// 编辑按钮
GestureDetector(
  onTap: () async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => AddAddressPage(address: address),
      ),
    );
    if (result != null) {
      // TODO: 更新地址
    }
  },
  child: const Padding(
    padding: EdgeInsets.only(left: 16),
    child: Icon(
      Icons.edit_outlined,
      size: 20,
      color: AppColors.grey,
    ),
  ),
),

编辑按钮的设计:

  • 用编辑图标(Icons.edit_outlined),轮廓样式,更精致
  • 灰色显示,不会太抢眼
  • 左边留16的padding,跟地址信息隔开
  • 点击后跳转到编辑页面,传入当前地址数据

await 等待编辑页面返回结果,如果有结果(用户保存了修改),就更新地址列表。

添加新地址按钮

按钮的容器

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

按钮用 Container 包裹,四周留16的padding。用 SafeArea 确保按钮不会被底部的Home指示器遮挡。

按钮的样式

child: GestureDetector(
  onTap: () async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => const AddAddressPage()),
    );
    if (result != null) {
      setState(() {
        addresses.add(result);
      });
    }
  },
  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,
        ),
      ),
    ),
  ),
),

按钮的设计:

  • 金色背景,跟App主题一致
  • 圆角25,做成胶囊形状
  • 黑色加粗文字,醒目
  • 宽度占满,高度50

点击按钮后跳转到添加地址页面,等待用户填写信息。如果用户保存了新地址,就添加到列表中,并调用 setState 刷新UI。

一些细节优化

分割线的使用

ListView.separated(
  itemCount: addresses.length,
  separatorBuilder: (context, index) => const Divider(height: 1),
  itemBuilder: (context, index) {
    return _buildAddressItem(addresses[index]);
  },
),

ListView.separated 而不是 ListView.builder,能自动在地址项之间添加分割线。

separatorBuilder 返回一个 Divider,高度1,很细,只是为了分隔地址项。

电话号码脱敏

'phone': '134****2222',

电话号码中间4位用星号代替,保护用户隐私。这是电商App的常见做法。

实际项目中,后端API应该返回脱敏后的电话号码,前端不需要处理。

默认地址的判断

final bool isDefault = address['isDefault'] as bool;
if (isDefault)
  Container(
    // 默认标签
  ),

用一个布尔变量判断是否是默认地址,然后用 if 条件渲染标签。

这样代码更清晰,不会有嵌套的三元运算符。

编辑结果的处理

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (_) => AddAddressPage(address: address),
  ),
);
if (result != null) {
  // TODO: 更新地址
}

await 等待编辑页面返回结果。如果用户保存了修改(result != null),就更新地址列表。

实际项目中,这里应该调用API更新地址,然后重新获取地址列表。

踩过的坑

分割线位置不对

一开始我用 ListView.builder 手动添加分割线,结果最后一个地址下面也有分割线,看起来很奇怪。

解决方案:

改用 ListView.separated,它会自动在地址项之间添加分割线,最后一个地址下面不会有:

ListView.separated(
  itemCount: addresses.length,
  separatorBuilder: (context, index) => const Divider(height: 1),
  itemBuilder: (context, index) {
    return _buildAddressItem(addresses[index]);
  },
),

编辑按钮点击区域小

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

解决方案:

GestureDetector 包裹图标,并给图标加上 Padding

GestureDetector(
  onTap: () async {
    // 跳转到编辑页面
  },
  child: const Padding(
    padding: EdgeInsets.only(left: 16),
    child: Icon(...),
  ),
),

这样点击区域更大,用户体验更好。

添加地址后列表不更新

一开始我忘了在添加地址后调用 setState,结果新地址添加成功了,但列表没有更新。

解决方案:

在添加地址后调用 setState

if (result != null) {
  setState(() {
    addresses.add(result);
  });
}

调用 setState 后,Flutter会重新构建页面,列表就更新了。

地址文字溢出

用户反馈说有些地址很长,显示不全,后面被截断了。

解决方案:

Expanded 包裹地址文字:

Expanded(
  child: Text(
    address['address'] as String,
    style: const TextStyle(
      fontSize: 15,
      color: AppColors.black,
    ),
  ),
),

Expanded 会让文字自动换行,不会溢出。

写在最后

地址管理页是电商App的基础功能,必须做到清晰、易用。用户需要快速找到想要的地址,方便地编辑和添加新地址。

我的设计原则:

  1. 信息层次要清晰。地址是最重要的信息,用大字号黑色;联系人和电话是次要信息,用小字号灰色。

  2. 默认地址要醒目。用金色标签标识默认地址,让用户一眼就能看到。

  3. 操作要方便。编辑按钮放在每个地址项的右边,添加按钮固定在底部,用户随时可以操作。

  4. 细节要到位。电话号码脱敏,分割线自动添加,地址文字自动换行,这些细节都要考虑到。

做完地址管理页后,我拿给几个朋友试用。有人说"地址信息很清楚,一眼就能看到重点",有人说"编辑和添加都很方便"。这些反馈让我觉得,那些细节的打磨是值得的。

一些经验:

  • ListView.separated 而不是 ListView.builder,能自动处理分割线,省去很多判断逻辑。

  • 默认地址要有明显的标识,不能只是在数据里标记,用户看不到。

  • 编辑和添加要用同一个页面,只是传入的参数不同。这样可以复用代码,减少维护成本。

下一步计划:

地址管理页做完了,接下来要做添加地址页。那个页面会让用户填写地址信息,包括姓名、电话、地区选择、详细地址等。不过有了前面的基础,应该会更容易。

如果你也在做类似的项目,希望这篇文章能给你一些启发。地址管理页看起来简单,但要做到清晰易用,还是需要在信息组织和交互设计上下功夫的。


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

Logo

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

更多推荐