Flutter for Openharmony盲盒抽奖App应用实战+地址管理实现
地址管理是电商App的基础功能之一。用户下单时需要选择收货地址,平时也需要管理自己的地址列表。这个功能看起来简单,就是一个列表页,但实际做起来发现有不少细节要处理。地址展示、默认地址标识、编辑跳转、添加新地址,每个环节都要考虑周全。我在做这个页面时,特别注意了信息的层次。地址是最重要的信息,要最醒目;联系人和电话是次要信息,用小字号和灰色;默认地址要有明显的标识,让用户一眼就能看到。@overri

写在前面
地址管理是电商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的基础功能,必须做到清晰、易用。用户需要快速找到想要的地址,方便地编辑和添加新地址。
我的设计原则:
-
信息层次要清晰。地址是最重要的信息,用大字号黑色;联系人和电话是次要信息,用小字号灰色。
-
默认地址要醒目。用金色标签标识默认地址,让用户一眼就能看到。
-
操作要方便。编辑按钮放在每个地址项的右边,添加按钮固定在底部,用户随时可以操作。
-
细节要到位。电话号码脱敏,分割线自动添加,地址文字自动换行,这些细节都要考虑到。
做完地址管理页后,我拿给几个朋友试用。有人说"地址信息很清楚,一眼就能看到重点",有人说"编辑和添加都很方便"。这些反馈让我觉得,那些细节的打磨是值得的。
一些经验:
-
用
ListView.separated而不是ListView.builder,能自动处理分割线,省去很多判断逻辑。 -
默认地址要有明显的标识,不能只是在数据里标记,用户看不到。
-
编辑和添加要用同一个页面,只是传入的参数不同。这样可以复用代码,减少维护成本。
下一步计划:
地址管理页做完了,接下来要做添加地址页。那个页面会让用户填写地址信息,包括姓名、电话、地区选择、详细地址等。不过有了前面的基础,应该会更容易。
如果你也在做类似的项目,希望这篇文章能给你一些启发。地址管理页看起来简单,但要做到清晰易用,还是需要在信息组织和交互设计上下功夫的。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)