Flutter for OpenHarmony二手物品置换App实战 - 我的收藏实现
收藏页面实现方案 本文介绍了"闲置换"应用中收藏页面的设计与实现方案。该页面包含空状态展示和收藏商品列表两种视图状态: 空状态视图使用空心爱心图标和灰色文字提示,引导用户前往首页浏览商品 收藏列表采用两列网格布局,支持下拉刷新和长按操作 状态管理包括加载中、空数据和正常三种状态 实现细节: 使用StatefulWidget管理页面状态 网格布局设置0.65宽高比优化显示效果 空

收藏功能让用户保存感兴趣的商品,方便以后查看和购买。今天我们来实现"闲置换"的我的收藏页面,包括空状态展示和收藏列表。
收藏页面的设计思路
收藏页面比较简单,主要是展示用户收藏的商品列表。如果没有收藏任何商品,显示空状态引导用户去首页浏览。有收藏的话用网格布局展示,每个卡片右上角有取消收藏的按钮,长按可以弹出更多操作。
完整代码实现
import 'package:flutter/material.dart';
class FavoritesPage extends StatelessWidget {
const FavoritesPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('我的收藏')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.favorite_border, size: 80, color: Colors.grey[300]),
const SizedBox(height: 16),
Text('暂无收藏', style: TextStyle(color: Colors.grey[500])),
const SizedBox(height: 8),
Text('去首页逛逛吧', style: TextStyle(color: Colors.grey[400], fontSize: 14)),
],
),
),
);
}
}
这是收藏页面的基础版本,展示空状态。用空心爱心图标Icons.favorite_border表示收藏,比实心爱心更能传达"空"的感觉。图标设置80像素大小,灰色调不会太突兀。主提示"暂无收藏"用深一点的灰色,副提示"去首页逛逛吧"用浅灰色,形成主次分明的视觉层次。Center组件让整个内容块在页面正中央显示。
收藏列表的完整实现
实际项目中收藏页面应该这样实现:
class FavoritesPage extends StatefulWidget {
const FavoritesPage({super.key});
State<FavoritesPage> createState() => _FavoritesPageState();
}
class _FavoritesPageState extends State<FavoritesPage> {
List<Map<String, dynamic>> _favorites = [];
bool _isLoading = true;
void initState() {
super.initState();
_loadFavorites();
}
Future<void> _loadFavorites() async {
setState(() => _isLoading = true);
try {
final data = await api.getFavorites();
setState(() {
_favorites = data;
_isLoading = false;
});
} catch (e) {
setState(() => _isLoading = false);
Get.snackbar('错误', '加载失败,请重试');
}
}
把页面改成StatefulWidget来管理状态。_favorites存储收藏列表数据,_isLoading控制加载状态的显示。initState里调用_loadFavorites加载数据,加载前先把_isLoading设为true显示loading,加载完成后设为false。用try-catch捕获异常,加载失败时弹出错误提示,避免页面卡在loading状态。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('我的收藏'),
actions: [
if (_favorites.isNotEmpty)
TextButton(
onPressed: _toggleEditMode,
child: Text(_isEditMode ? '完成' : '编辑'),
),
],
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_favorites.isEmpty) {
return _buildEmptyState();
}
return RefreshIndicator(
onRefresh: _loadFavorites,
child: GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.65,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: _favorites.length,
itemBuilder: (context, index) => _buildFavoriteCard(_favorites[index]),
),
);
}
build方法根据状态显示不同内容。AppBar右边在有收藏时显示编辑按钮,点击可以进入批量选择模式。_buildBody方法处理三种情况:加载中显示圆形进度条,空数据显示空状态页,有数据显示网格列表。GridView.builder用两列网格布局,childAspectRatio: 0.65控制卡片宽高比,让卡片稍微高一些以容纳图片和文字。RefreshIndicator支持下拉刷新。
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.favorite_border, size: 80, color: Colors.grey[300]),
const SizedBox(height: 16),
Text('暂无收藏', style: TextStyle(color: Colors.grey[500])),
const SizedBox(height: 8),
Text('去首页逛逛吧', style: TextStyle(color: Colors.grey[400], fontSize: 14)),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => Get.offAll(() => const MainPage()),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF07C160),
),
child: const Text('去首页', style: TextStyle(color: Colors.white)),
),
],
),
);
}
空状态页面比基础版多了一个跳转按钮。ElevatedButton用主题绿色作为背景,白色文字,点击后跳转到首页。Get.offAll会清空整个路由栈然后跳转,相当于重新打开App直接进入首页,用户按返回键会退出App而不是回到收藏页。这种处理方式在空状态引导场景很常见。
Widget _buildFavoriteCard(Map<String, dynamic> product) {
return GestureDetector(
onTap: () => Get.to(() => ProductDetailPage(productId: product['id'])),
onLongPress: () => _showActionSheet(product),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
color: Colors.grey[200],
),
child: Center(child: Icon(Icons.image, size: 60, color: Colors.grey[400])),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product['title'], style: const TextStyle(fontSize: 14), maxLines: 2, overflow: TextOverflow.ellipsis),
const Spacer(),
Text('¥${product['price']}', style: const TextStyle(color: Color(0xFFFF4D4F), fontSize: 16, fontWeight: FontWeight.bold)),
],
),
),
),
],
),
),
),
);
}
收藏卡片用Stack实现图片上叠加爱心按钮的效果。卡片分为上下两部分,图片区域占3份高度,信息区域占2份。GestureDetector处理点击和长按事件,点击进入商品详情,长按弹出操作菜单。右上角的红色爱心按钮用Positioned定位,半透明黑色圆形背景让按钮在任何颜色的图片上都清晰可见。标题最多显示两行,超出部分用省略号。
void _showActionSheet(Map<String, dynamic> product) {
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.delete_outline),
title: const Text('取消收藏'),
onTap: () {
Get.back();
_removeFavorite(product);
},
),
ListTile(
leading: const Icon(Icons.share),
title: const Text('分享'),
onTap: () {
Get.back();
_shareProduct(product);
},
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () => Get.back(),
child: const Text('取消'),
),
),
],
),
),
);
}
Future<void> _removeFavorite(Map<String, dynamic> product) async {
try {
await api.removeFavorite(product['id']);
setState(() {
_favorites.removeWhere((item) => item['id'] == product['id']);
});
Get.snackbar('成功', '已取消收藏');
} catch (e) {
Get.snackbar('错误', '操作失败,请重试');
}
}
长按弹出底部操作菜单,用Get.bottomSheet实现。菜单顶部有圆角,包含取消收藏和分享两个选项,底部是取消按钮。mainAxisSize: MainAxisSize.min让菜单高度自适应内容。取消收藏时先调用接口,成功后用removeWhere从本地列表中删除对应商品,这样不需要重新请求接口就能更新UI,体验更流畅。
收藏状态的同步
收藏状态需要在多个页面同步,可以用GetX的状态管理实现:
class FavoriteController extends GetxController {
var favorites = <Map<String, dynamic>>[].obs;
Future<void> addFavorite(Map<String, dynamic> product) async {
await api.addFavorite(product['id']);
favorites.add(product);
}
Future<void> removeFavorite(int productId) async {
await api.removeFavorite(productId);
favorites.removeWhere((item) => item['id'] == productId);
}
bool isFavorite(int productId) {
return favorites.any((item) => item['id'] == productId);
}
}
用GetX的Controller统一管理收藏状态。favorites用.obs声明为响应式变量,数据变化时会自动通知所有监听者更新UI。在商品详情页和收藏页面都使用这个Controller,用户在详情页点击收藏后,收藏页面会自动显示新收藏的商品,无需手动刷新。isFavorite方法用于判断某个商品是否已收藏,详情页可以据此显示不同的收藏按钮状态。
小结
这篇实现了"闲置换"App的我的收藏页面,包括空状态展示和收藏列表。收藏卡片右上角有取消收藏按钮,长按弹出操作菜单。用GetX的Controller实现收藏状态的跨页面同步,让用户体验更加流畅。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)