在这里插入图片描述

应用的三个主要页面,转盘页和统计页都做完了,还剩个人中心。这一篇我们来实现个人中心页面,展示用户信息和获奖记录列表。

页面结构

个人中心分两部分:顶部的用户信息卡片,下面是获奖记录列表。

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';

这里有个小问题:PrizeManagermodelsutils目录下都有。实际上应该只保留一个,这里用的是models目录下的。如果你的项目结构不同,调整一下导入路径就行。

列表性能优化

如果获奖记录很多,可以考虑一些优化:

ListView.builder而不是ListView,这样只会构建可见的列表项,内存占用更低。

CardListTile都是比较轻量的组件,性能不会有太大问题。如果列表项更复杂,可以考虑用const构造函数减少重建。

记录数量特别多的话,可以考虑分页加载,但对于这个应用来说,用户不太可能有几千条记录,暂时不需要。

下拉刷新

如果后续接入了后端,可以加上下拉刷新功能:

RefreshIndicator(
  onRefresh: () async {
    // 从服务器拉取最新数据
    await _prizeManager.refresh();
  },
  child: ListView.builder(...),
)

现在数据都在本地,不需要刷新,所以没加这个功能。


个人中心页面就完成了。用户可以在这里看到自己的抽奖次数和所有获奖记录,点击记录可以查看详情。下一篇是这个系列的最后一篇,我们来做个项目总结。


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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐