Flutter for OpenHarmony:从零搭建幸运大转盘app(十一)个人中心
应用的三个主要页面,转盘页和统计页都做完了,还剩个人中心。这一篇我们来实现个人中心页面,展示用户信息和获奖记录列表。

应用的三个主要页面,转盘页和统计页都做完了,还剩个人中心。这一篇我们来实现个人中心页面,展示用户信息和获奖记录列表。
页面结构
个人中心分两部分:顶部的用户信息卡片,下面是获奖记录列表。
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
final PrizeManager _prizeManager = PrizeManager();
void initState() {
super.initState();
_prizeManager.addListener(_onRecordsChanged);
}
void dispose() {
_prizeManager.removeListener(_onRecordsChanged);
super.dispose();
}
void _onRecordsChanged() {
if (mounted) setState(() {});
}
}
监听PrizeManager的变化,有新的获奖记录时自动刷新。
用户信息卡片
顶部用一个渐变背景的卡片展示用户信息:
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [Colors.purple.shade400, Colors.deepPurple]),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
const CircleAvatar(
radius: 35,
backgroundColor: Colors.white,
child: Icon(Icons.person, size: 40, color: Colors.deepPurple),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'幸运用户',
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
'已抽奖 ${_prizeManager.records.length} 次',
style: const TextStyle(color: Colors.white70),
),
],
),
],
),
)
左边是头像,用CircleAvatar配合图标实现。右边显示用户名和抽奖次数。渐变从浅紫到深紫,跟应用主题色一致。
实际项目中,这里应该显示真实的用户信息,可能需要接入登录系统。这里简化处理,用固定的"幸运用户"。
获奖记录标题
记录列表上面加个标题:
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
const Icon(Icons.emoji_events, color: Colors.amber),
const SizedBox(width: 8),
const Text('获奖记录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
)
奖杯图标配合文字,简洁明了。
获奖记录列表
用ListView.builder展示记录列表:
Expanded(
child: _prizeManager.records.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.inbox, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text('暂无获奖记录', style: TextStyle(color: Colors.grey.shade500)),
const SizedBox(height: 8),
Text('快去抽奖试试手气吧!', style: TextStyle(color: Colors.grey.shade400, fontSize: 12)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _prizeManager.records.length,
itemBuilder: (context, index) {
final record = _prizeManager.records[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: record.prize.color,
child: Icon(record.prize.icon, color: Colors.white),
),
title: Text(record.prize.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(_formatTime(record.time)),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PrizeDetailPage(record: record)),
),
),
);
},
),
)
空状态显示友好的提示,引导用户去抽奖。有记录时,每条记录用Card包裹的ListTile展示,点击跳转到详情页。
leading用圆形头像显示奖品图标,颜色跟奖品一致。trailing的箭头图标暗示可以点击查看详情。
时间格式化
记录的时间格式化成易读的形式:
String _formatTime(DateTime time) {
return '${time.year}-${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} '
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
}
格式是"2026-01-09 14:30"这样,清晰易读。
完整的build方法
把各部分组合起来:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('个人中心'),
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Column(
children: [
// 用户信息卡片
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [Colors.purple.shade400, Colors.deepPurple]),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
const CircleAvatar(radius: 35, backgroundColor: Colors.white, child: Icon(Icons.person, size: 40, color: Colors.deepPurple)),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('幸运用户', style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text('已抽奖 ${_prizeManager.records.length} 次', style: const TextStyle(color: Colors.white70)),
],
),
],
),
),
// 获奖记录标题
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
const Icon(Icons.emoji_events, color: Colors.amber),
const SizedBox(width: 8),
const Text('获奖记录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
),
// 获奖记录列表
Expanded(
child: _prizeManager.records.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.inbox, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text('暂无获奖记录', style: TextStyle(color: Colors.grey.shade500)),
const SizedBox(height: 8),
Text('快去抽奖试试手气吧!', style: TextStyle(color: Colors.grey.shade400, fontSize: 12)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _prizeManager.records.length,
itemBuilder: (context, index) {
final record = _prizeManager.records[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(backgroundColor: record.prize.color, child: Icon(record.prize.icon, color: Colors.white)),
title: Text(record.prize.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(_formatTime(record.time)),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PrizeDetailPage(record: record)),
),
),
);
},
),
),
],
),
);
}
导入依赖
别忘了在文件顶部导入需要的文件:
import 'package:flutter/material.dart';
import '../models/prize_manager.dart';
import 'prize_detail_page.dart';
这里有个小问题:PrizeManager在models和utils目录下都有。实际上应该只保留一个,这里用的是models目录下的。如果你的项目结构不同,调整一下导入路径就行。
列表性能优化
如果获奖记录很多,可以考虑一些优化:
用ListView.builder而不是ListView,这样只会构建可见的列表项,内存占用更低。
Card和ListTile都是比较轻量的组件,性能不会有太大问题。如果列表项更复杂,可以考虑用const构造函数减少重建。
记录数量特别多的话,可以考虑分页加载,但对于这个应用来说,用户不太可能有几千条记录,暂时不需要。
下拉刷新
如果后续接入了后端,可以加上下拉刷新功能:
RefreshIndicator(
onRefresh: () async {
// 从服务器拉取最新数据
await _prizeManager.refresh();
},
child: ListView.builder(...),
)
现在数据都在本地,不需要刷新,所以没加这个功能。
个人中心页面就完成了。用户可以在这里看到自己的抽奖次数和所有获奖记录,点击记录可以查看详情。下一篇是这个系列的最后一篇,我们来做个项目总结。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)