个人中心是App的重要入口,用户可以在这里查看账户概览、进入各项设置、管理数据等。本篇将实现一个功能完善的个人中心页面,详细介绍每个模块的设计思路和实现细节。
请添加图片描述

页面功能规划

个人中心页面包含以下功能模块:

  1. 用户信息展示区 - 头像、昵称、个性签名
  2. 资产统计概览 - 总资产、账户数、记账笔数
  3. 账户管理入口 - 账户、分类、理财目标
  4. 数据管理入口 - 导出、备份
  5. 设置入口 - 通用设置、货币、深色模式
  6. 关于入口 - 关于我们、意见反馈、评分

这种布局符合用户的使用习惯,常用功能放在显眼位置,设置类功能放在下方。

控制器实现

首先创建 profile_controller.dart 来管理页面状态:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../core/services/storage_service.dart';
import '../../core/services/account_service.dart';
import '../../core/services/transaction_service.dart';

class ProfileController extends GetxController {
  final _storage = Get.find<StorageService>();
  final _accountService = Get.find<AccountService>();
  final _transactionService = Get.find<TransactionService>();

这部分代码导入必要的依赖包和服务类。ProfileController 继承自 GetxController,是 GetX 状态管理的核心类。通过 Get.find 获取已注册的服务实例,实现依赖注入。这种方式让控制器与服务解耦,便于测试和维护。三个服务分别负责存储管理、账户管理和交易管理。

  final totalAssets = 0.0.obs;
  final accountCount = 0.obs;
  final transactionCount = 0.obs;
  final isDarkMode = false.obs;

  String get currency => _storage.currency;

  
  void onInit() {
    super.onInit();

定义响应式变量来存储统计数据和深色模式状态。使用 .obs 后缀创建可观察对象,当值改变时会自动通知 UI 更新。totalAssets 存储总资产,accountCount 存储账户数量,transactionCount 存储交易笔数。currency 是一个 getter,直接从存储服务获取货币设置。onInit 是生命周期方法,在控制器初始化时调用。

    _loadStats();
    isDarkMode.value = _storage.isDarkMode;
  }

  void _loadStats() {
    totalAssets.value = _accountService.totalBalance;
    accountCount.value = _accountService.accounts.length;
    transactionCount.value = _transactionService.allTransactions.length;
  }

在初始化时加载统计数据和深色模式设置。_loadStats 方法从各个服务获取最新数据并更新响应式变量。totalBalance 从账户服务获取所有账户的总余额,accounts.length 获取账户数量,allTransactions.length 获取交易记录总数。这些数据会在个人中心页面的统计卡片中展示。

  void toggleDarkMode() {
    isDarkMode.value = !isDarkMode.value;
    _storage.setDarkMode(isDarkMode.value);
    Get.changeThemeMode(isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
  }

  void refresh() {
    _loadStats();
  }
}

toggleDarkMode 方法切换深色模式状态。首先反转 isDarkMode 的值,然后保存到本地存储确保下次启动时保持设置。最后调用 Get.changeThemeMode 立即切换应用主题,用户可以实时看到效果。refresh 方法用于手动刷新统计数据,当用户下拉刷新时会调用此方法重新加载最新数据。

页面主体结构

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../routes/app_pages.dart';
import 'profile_controller.dart';

const _primaryColor = Color(0xFF2E7D32);
const _textSecondary = Color(0xFF757575);
const _cardRadius = 12.0;

导入页面所需的依赖包。flutter_screenutil 用于屏幕适配,GetX 用于状态管理和路由导航。定义三个常量:_primaryColor 是主题绿色,_textSecondary 是次要文本的灰色,_cardRadius 是卡片圆角半径。这些常量在整个页面中复用,确保视觉风格统一。如果需要调整颜色或圆角,只需修改这里的常量值即可。

class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  
  Widget build(BuildContext context) {
    final controller = Get.put(ProfileController());
    return Scaffold(
      appBar: AppBar(
        title: const Text('我的'),

ProfilePage 是无状态组件,状态由 ProfileController 管理。使用 Get.put 创建并注册控制器实例,GetX 会自动管理其生命周期。Scaffold 提供基本的页面结构,AppBar 显示标题"我的"。使用 const 构造函数可以提升性能,因为这些组件在运行时不会改变。

        centerTitle: true,
        elevation: 0,
      ),
      body: RefreshIndicator(
        onRefresh: () async => controller.refresh(),
        child: SingleChildScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          padding: EdgeInsets.all(16.w),

AppBar 标题居中显示,elevation 设为 0 去除阴影效果。RefreshIndicator 包裹内容区域,支持下拉刷新功能,触发时调用控制器的 refresh 方法。SingleChildScrollView 让内容可以滚动,AlwaysScrollableScrollPhysics 确保即使内容不足一屏也能触发下拉刷新。padding 为内容区域添加 16 像素的内边距。

          child: Column(
            children: [
              _buildUserCard(controller),
              SizedBox(height: 16.h),
              _buildStatsCard(controller),
              SizedBox(height: 16.h),
              _buildMenuSection('账户管理', [
                _MenuItem(Icons.account_balance, '账户管理', 
                  () => Get.toNamed(Routes.accountList)),

Column 垂直排列所有内容组件。首先是用户信息卡片,然后是统计数据卡片,各组件之间用 SizedBox 添加 16 像素间距。_buildMenuSection 创建菜单分组,第一组是"账户管理",包含账户管理、分类管理和理财目标三个入口。_MenuItem 封装菜单项的图标、标题和点击事件,点击时使用 Get.toNamed 进行路由跳转。

                _MenuItem(Icons.category, '分类管理', 
                  () => Get.toNamed(Routes.categoryList)),
                _MenuItem(Icons.flag, '理财目标', 
                  () => Get.toNamed(Routes.goals)),
              ]),
              SizedBox(height: 16.h),
              _buildMenuSection('数据管理', [
                _MenuItem(Icons.file_download, '导出数据', 
                  () => Get.toNamed(Routes.exportData)),

完成账户管理分组的菜单项定义。接下来是"数据管理"分组,包含导出数据、数据备份和数据恢复三个功能。每个菜单项都有对应的图标和点击处理函数。导出数据跳转到专门的导出页面,备份和恢复功能则弹出对话框进行操作。分组之间用 SizedBox 添加间距,保持视觉层次清晰。

                _MenuItem(Icons.backup, '数据备份', 
                  () => _showBackupDialog()),
                _MenuItem(Icons.restore, '数据恢复', 
                  () => _showRestoreDialog()),
              ]),
              SizedBox(height: 16.h),
              _buildMenuSection('设置', [
                _MenuItem(Icons.settings, '通用设置', 
                  () => Get.toNamed(Routes.settings)),

数据管理分组的备份和恢复功能通过对话框实现,避免误操作。"设置"分组包含通用设置、货币设置和深色模式三个选项。通用设置和货币设置跳转到对应页面,深色模式则直接在当前页面切换。这种设计让常用的深色模式切换更便捷,无需进入二级页面。

                _MenuItem(Icons.attach_money, '货币设置', 
                  () => Get.toNamed(Routes.currency)),
                _MenuItem(Icons.dark_mode, '深色模式', 
                  () => controller.toggleDarkMode(), 
                  trailing: Obx(() => Switch(
                    value: controller.isDarkMode.value, 
                    onChanged: (_) => controller.toggleDarkMode(),
                    activeColor: _primaryColor,

货币设置跳转到货币选择页面。深色模式菜单项比较特殊,trailing 参数提供了自定义的尾部组件 Switch 开关。使用 Obx 包裹 Switch 实现响应式更新,当 isDarkMode 值改变时开关状态自动同步。Switch 的 onChanged 回调调用 toggleDarkMode 方法切换主题。activeColor 设置为主题色,保持视觉一致性。

                  ))
                ),
              ]),
              SizedBox(height: 16.h),
              _buildMenuSection('关于', [
                _MenuItem(Icons.info, '关于我们', 
                  () => Get.toNamed(Routes.about)),
                _MenuItem(Icons.feedback, '意见反馈', 
                  () => Get.toNamed(Routes.feedback)),

完成设置分组。"关于"分组包含关于我们、意见反馈、给个好评和分享应用四个功能。关于我们和意见反馈跳转到对应页面,让用户了解应用信息和提交反馈。给个好评弹出对话框引导用户评分,分享应用调用系统分享功能。这些功能有助于提升用户参与度和应用口碑。

                _MenuItem(Icons.star, '给个好评', 
                  () => _showRateDialog()),
                _MenuItem(Icons.share, '分享应用', 
                  () => _shareApp()),
              ]),
              SizedBox(height: 32.h),
              _buildVersionInfo(),
            ],
          ),
        ),
      ),
    );
  }
}

完成所有菜单分组的定义。最后添加版本信息组件,用 32 像素的较大间距与上方内容分隔。_buildVersionInfo 显示应用名称、版本号和版权信息。整个页面结构清晰,功能分组合理,用户可以快速找到需要的入口。RefreshIndicator 和 SingleChildScrollView 的组合确保了良好的交互体验。

用户信息卡片

Widget _buildUserCard(ProfileController controller) {
  return Card(
    elevation: 2,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(_cardRadius.r),
    ),
    child: InkWell(
      onTap: () => _showEditProfileDialog(),
      borderRadius: BorderRadius.circular(_cardRadius.r),

_buildUserCard 方法构建用户信息卡片。Card 组件提供卡片样式,elevation 设置阴影深度为 2。shape 参数定义圆角矩形边框,使用之前定义的 _cardRadius 常量保持一致性。InkWell 包裹卡片内容,提供点击效果和水波纹动画。onTap 回调弹出编辑资料对话框,borderRadius 确保水波纹效果不超出卡片圆角范围。

      child: Padding(
        padding: EdgeInsets.all(20.w),
        child: Row(
          children: [
            Hero(
              tag: 'avatar',
              child: CircleAvatar(
                radius: 36.r,
                backgroundColor: _primaryColor,

Padding 为卡片内容添加 20 像素内边距。Row 水平排列头像和用户信息。Hero 组件包裹头像,实现页面跳转时的共享元素动画效果,tag 为 ‘avatar’ 用于标识动画元素。CircleAvatar 创建圆形头像,radius 设置半径为 36 像素,backgroundColor 使用主题绿色作为背景。

                child: Icon(Icons.person, size: 36.sp, color: Colors.white),
              ),
            ),
            SizedBox(width: 16.w),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(

头像中显示白色的人物图标,大小为 36 像素。SizedBox 在头像和文字之间添加 16 像素间距。Expanded 让文字区域占据剩余空间,避免内容过长时溢出。Column 垂直排列昵称、个性签名和记账天数,crossAxisAlignment 设置为 start 让文字左对齐。

                    '理财达人', 
                    style: TextStyle(
                      fontSize: 20.sp, 
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  SizedBox(height: 6.h),
                  Text(
                    '坚持记账,财富自由', 

显示用户昵称"理财达人",字体大小 20 像素,加粗显示突出重点。SizedBox 添加 6 像素垂直间距。第二个 Text 显示个性签名"坚持记账,财富自由",这是用户的座右铭或个性化描述。实际应用中这些文字应该从用户数据中读取,这里使用固定文本作为示例。

                    style: TextStyle(
                      fontSize: 14.sp, 
                      color: _textSecondary,
                    ),
                  ),
                  SizedBox(height: 4.h),
                  Text(
                    '已记账 ${controller.transactionCount.value} 天',
                    style: TextStyle(
                      fontSize: 12.sp,

个性签名使用 14 像素字体,颜色为次要文本灰色,视觉上弱化。SizedBox 添加 4 像素间距。第三个 Text 显示记账天数,使用字符串插值从控制器获取交易笔数。这里将交易笔数作为记账天数展示,实际应用中可以计算用户连续记账的天数。字体大小 12 像素,较小的字体适合辅助信息。

                      color: _primaryColor,
                    ),
                  ),
                ],
              ),
            ),
            Icon(
              Icons.chevron_right, 
              color: _textSecondary,
              size: 24.sp,
            ),

记账天数使用主题绿色显示,突出这个激励性的数据。Column 结束,完成用户信息的垂直布局。Row 的最后一个元素是右箭头图标,提示用户这是可点击的区域。图标颜色为次要文本灰色,大小 24 像素。这个视觉提示符合用户对可交互元素的认知习惯。

          ],
        ),
      ),
    ),
  );
}

void _showEditProfileDialog() {
  Get.dialog(
    AlertDialog(
      title: const Text('编辑资料'),
      content: Column(

完成用户卡片的构建。_showEditProfileDialog 方法弹出编辑资料对话框。使用 Get.dialog 显示对话框,这是 GetX 提供的便捷方法。AlertDialog 是 Material Design 风格的对话框,title 显示"编辑资料"。content 使用 Column 垂直排列输入框,让用户可以修改昵称和个性签名。

        mainAxisSize: MainAxisSize.min,
        children: [
          TextField(
            decoration: const InputDecoration(
              labelText: '昵称',
              hintText: '请输入昵称',
            ),
          ),
          SizedBox(height: 16.h),

Column 的 mainAxisSize 设置为 min,让对话框高度自适应内容。第一个 TextField 用于输入昵称,decoration 定义输入框样式。labelText 是浮动标签,显示"昵称"。hintText 是占位符文本,提示用户"请输入昵称"。SizedBox 在两个输入框之间添加 16 像素间距,保持视觉舒适度。

          TextField(
            decoration: const InputDecoration(
              labelText: '个性签名',
              hintText: '请输入个性签名',
            ),
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Get.back(), 

第二个 TextField 用于输入个性签名,同样定义了 labelText 和 hintText。实际应用中应该添加 controller 来获取输入内容,并设置初始值为当前的昵称和签名。actions 定义对话框底部的按钮。第一个 TextButton 是取消按钮,点击时调用 Get.back 关闭对话框,不保存任何修改。

          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            Get.back();
            Get.snackbar('提示', '资料已更新');
          }, 
          child: const Text('保存'),
        ),
      ],
    ),
  );
}

第二个 TextButton 是保存按钮,点击时先关闭对话框,然后显示 Snackbar 提示用户资料已更新。实际应用中这里应该获取输入框的内容,验证后保存到本地存储或服务器,然后更新控制器中的用户信息。Get.snackbar 是 GetX 提供的轻量级提示方法,显示在屏幕顶部或底部。

资产统计卡片

Widget _buildStatsCard(ProfileController controller) {
  return Card(
    elevation: 2,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(_cardRadius.r),
    ),
    child: Obx(() => Padding(
      padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 16.w),
      child: Row(

_buildStatsCard 方法构建资产统计卡片。Card 组件提供卡片样式,elevation 和 shape 与用户卡片保持一致。Obx 包裹内容区域,实现响应式更新,当控制器中的统计数据变化时自动刷新显示。Padding 添加垂直 20 像素、水平 16 像素的内边距。Row 水平排列三个统计项。

        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          _buildStatItem(
            '总资产', 
            '${controller.currency}${_formatNumber(controller.totalAssets.value)}',
            Icons.account_balance_wallet,
          ),
          _buildDivider(),

mainAxisAlignment 设置为 spaceAround,让三个统计项均匀分布并在两端留有空间。第一个统计项显示总资产,标题为"总资产",值从控制器获取并格式化。使用货币符号前缀,_formatNumber 方法将大数字转换为易读格式。图标使用钱包图标 account_balance_wallet。_buildDivider 在统计项之间添加分隔线。

          _buildStatItem(
            '账户数', 
            '${controller.accountCount.value}',
            Icons.credit_card,
          ),
          _buildDivider(),
          _buildStatItem(
            '记账笔数', 
            '${controller.transactionCount.value}',
            Icons.receipt_long,

第二个统计项显示账户数量,值直接从控制器获取,图标使用信用卡图标。再次添加分隔线。第三个统计项显示记账笔数,表示用户总共记录了多少笔交易,图标使用收据图标 receipt_long。三个统计项展示了用户理财管理的核心数据,让用户快速了解自己的财务状况。

          ),
        ],
      ),
    )),
  );
}

Widget _buildDivider() {
  return Container(
    width: 1, 
    height: 50.h, 
    color: Colors.grey[300],
  );
}

完成统计卡片的构建。_buildDivider 方法创建垂直分隔线。使用 Container 组件,宽度 1 像素,高度 50 像素,颜色为浅灰色。这个分隔线在视觉上将三个统计项分开,让数据展示更清晰。分隔线的高度与统计项的高度相匹配,保持视觉平衡。

Widget _buildStatItem(String label, String value, IconData icon) {
  return Column(
    children: [
      Icon(icon, color: _primaryColor, size: 24.sp),
      SizedBox(height: 8.h),
      Text(
        value, 
        style: TextStyle(
          fontSize: 18.sp, 
          fontWeight: FontWeight.bold, 

_buildStatItem 方法构建单个统计项。接收标题、数值和图标三个参数。Column 垂直排列图标、数值和标题。图标使用主题绿色,大小 24 像素。SizedBox 在图标和数值之间添加 8 像素间距。数值文本字体大小 18 像素,加粗显示突出重点。

          color: _primaryColor,
        ),
      ),
      SizedBox(height: 4.h),
      Text(
        label, 
        style: TextStyle(
          fontSize: 12.sp, 
          color: _textSecondary,
        ),
      ),
    ],
  );
}

数值颜色也使用主题绿色,与图标颜色一致。SizedBox 在数值和标题之间添加 4 像素间距。标题文本字体大小 12 像素,颜色为次要文本灰色,视觉上弱化。这种布局将重点放在数值上,标题作为辅助说明。整个统计项的设计简洁明了,用户一眼就能看到关键数据。

String _formatNumber(double number) {
  if (number >= 10000) {
    return '${(number / 10000).toStringAsFixed(1)}万';
  }
  return number.toStringAsFixed(0);
}

_formatNumber 方法格式化数字显示。如果数字大于等于 10000,则除以 10000 并保留一位小数,后面加"万"字。例如 15000 显示为"1.5万",50000 显示为"5.0万"。如果数字小于 10000,则直接显示整数部分,不显示小数。这种格式化让大数字更易读,符合中文数字表达习惯。

菜单分组组件

Widget _buildMenuSection(String title, List<_MenuItem> items) {
  return Card(
    elevation: 1,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(_cardRadius.r),
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [

_buildMenuSection 方法构建菜单分组。接收分组标题和菜单项列表两个参数。Card 组件提供卡片样式,elevation 设置为 1,比其他卡片的阴影稍浅。shape 定义圆角矩形边框。Column 垂直排列分组标题和所有菜单项,crossAxisAlignment 设置为 start 让内容左对齐。

        Padding(
          padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 8.h),
          child: Row(
            children: [
              Container(
                width: 4.w,
                height: 16.h,
                decoration: BoxDecoration(
                  color: _primaryColor,

Padding 为分组标题添加内边距,左右 16 像素,上 16 像素,下 8 像素。Row 水平排列装饰条和标题文字。Container 创建一个小竖条作为装饰,宽度 4 像素,高度 16 像素。BoxDecoration 定义装饰样式,背景色使用主题绿色,增加视觉层次感。

                  borderRadius: BorderRadius.circular(2.r),
                ),
              ),
              SizedBox(width: 8.w),
              Text(
                title, 
                style: TextStyle(
                  fontSize: 14.sp, 
                  color: _textSecondary,
                  fontWeight: FontWeight.w500,

装饰条的圆角半径为 2 像素,让边缘更柔和。SizedBox 在装饰条和标题之间添加 8 像素间距。Text 显示分组标题,字体大小 14 像素,颜色为次要文本灰色。fontWeight 设置为 w500(中等粗细),比普通文本稍粗,但不如加粗那么重。这种设计让分组标题既突出又不过于抢眼。

                ),
              ),
            ],
          ),
        ),
        ...items.asMap().entries.map((entry) {
          final index = entry.key;
          final item = entry.value;
          return Column(
            children: [

完成分组标题的构建。使用扩展运算符 … 将菜单项列表展开到 Column 的 children 中。asMap().entries 将列表转换为带索引的条目,这样可以获取每个菜单项的索引。map 方法遍历每个条目,提取索引和菜单项对象。返回一个 Column 包裹 ListTile 和分隔线。

              ListTile(
                leading: Container(
                  padding: EdgeInsets.all(8.w),
                  decoration: BoxDecoration(
                    color: _primaryColor.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(8.r),
                  ),
                  child: Icon(item.icon, color: _primaryColor, size: 20.sp),

ListTile 是 Material Design 的列表项组件,自动处理布局和交互。leading 是左侧组件,这里用 Container 包裹图标。padding 为图标添加 8 像素内边距。BoxDecoration 定义背景样式,颜色为主题绿色的 10% 透明度,圆角半径 8 像素。Icon 显示菜单项图标,颜色为主题绿色,大小 20 像素。

                ),
                title: Text(item.title),
                trailing: item.trailing ?? Icon(
                  Icons.chevron_right, 
                  color: _textSecondary,
                  size: 20.sp,
                ),
                onTap: item.onTap,
              ),

title 显示菜单项标题文字。trailing 是右侧组件,如果菜单项提供了自定义 trailing(如深色模式的开关),则使用自定义组件,否则显示默认的右箭头图标。右箭头颜色为次要文本灰色,大小 20 像素。onTap 设置点击回调,调用菜单项的 onTap 方法。ListTile 会自动处理点击效果和水波纹动画。

              if (index < items.length - 1)
                Divider(height: 1, indent: 56.w, endIndent: 16.w),
            ],
          );
        }),
        SizedBox(height: 8.h),
      ],
    ),
  );
}

使用 if 条件判断,只在非最后一项后面添加分隔线。Divider 是水平分隔线组件,height 设置为 1 像素。indent 设置左侧缩进 56 像素(图标宽度 + 内边距),endIndent 设置右侧缩进 16 像素。这样分隔线不会从最左边开始,视觉效果更精致。最后添加 8 像素底部间距,完成菜单分组的构建。

class _MenuItem {
  final IconData icon;
  final String title;
  final VoidCallback onTap;
  final Widget? trailing;
  
  _MenuItem(this.icon, this.title, this.onTap, {this.trailing});
}

_MenuItem 类封装菜单项数据。包含四个字段:icon 是图标,title 是标题,onTap 是点击回调,trailing 是可选的尾部组件。使用私有类(下划线前缀)表示这是文件内部使用的类。构造函数接收前三个必需参数和一个可选的命名参数 trailing。这种封装让菜单项的创建更简洁,代码更易维护。

对话框实现

void _showBackupDialog() {
  Get.dialog(
    AlertDialog(
      title: Row(
        children: [
          Icon(Icons.backup, color: _primaryColor),
          SizedBox(width: 8.w),
          const Text('数据备份'),

_showBackupDialog 方法显示数据备份对话框。使用 Get.dialog 弹出 AlertDialog。title 使用 Row 水平排列图标和文字,让标题更生动。备份图标使用主题绿色,SizedBox 添加 8 像素间距,然后显示"数据备份"文字。这种带图标的标题设计让对话框的功能一目了然。

        ],
      ),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('备份将保存以下数据:'),
          SizedBox(height: 12.h),
          _buildBackupItem('交易记录'),

content 使用 Column 垂直排列内容。mainAxisSize 设置为 min 让对话框高度自适应。crossAxisAlignment 设置为 start 让内容左对齐。首先显示说明文字"备份将保存以下数据:“,SizedBox 添加 12 像素间距。然后列出会备份的数据类型,第一项是"交易记录”。_buildBackupItem 方法创建带勾选图标的列表项。

          _buildBackupItem('账户信息'),
          _buildBackupItem('分类设置'),
          _buildBackupItem('预算配置'),
          _buildBackupItem('理财目标'),
          SizedBox(height: 12.h),
          Text(
            '备份文件将保存到本地存储',
            style: TextStyle(fontSize: 12.sp, color: _textSecondary),

继续列出其他备份项:账户信息、分类设置、预算配置、理财目标。这样用户清楚知道备份包含哪些内容。SizedBox 添加 12 像素间距。最后显示提示文字"备份文件将保存到本地存储",字体大小 12 像素,颜色为次要文本灰色。这个提示告诉用户备份文件的存储位置。

          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Get.back(), 
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            Get.back();

完成对话框内容区域。actions 定义底部按钮。第一个 TextButton 是取消按钮,点击时调用 Get.back 关闭对话框。第二个 ElevatedButton 是主要操作按钮,样式更突出。点击时先关闭对话框,然后执行备份操作。按钮的顺序和样式符合 Material Design 规范,次要操作在左,主要操作在右。

            _performBackup();
          }, 
          child: const Text('开始备份'),
        ),
      ],
    ),
  );
}

Widget _buildBackupItem(String text) {
  return Padding(
    padding: EdgeInsets.symmetric(vertical: 4.h),

开始备份按钮调用 _performBackup 方法执行实际的备份操作。_buildBackupItem 方法构建备份项列表。接收文本参数,返回一个带勾选图标的行。Padding 添加垂直 4 像素内边距,让列表项之间有适当间距。

    child: Row(
      children: [
        Icon(Icons.check_circle, color: _primaryColor, size: 16.sp),
        SizedBox(width: 8.w),
        Text(text, style: TextStyle(fontSize: 14.sp)),
      ],
    ),
  );
}

void _performBackup() {

Row 水平排列勾选图标和文字。check_circle 图标表示这些项会被备份,颜色为主题绿色,大小 16 像素。SizedBox 添加 8 像素间距。Text 显示备份项名称,字体大小 14 像素。这种设计让用户清楚看到备份范围,增加操作的透明度和信任感。

  Get.dialog(
    const Center(child: CircularProgressIndicator()),
    barrierDismissible: false,
  );
  
  Future.delayed(const Duration(seconds: 2), () {
    Get.back();
    Get.snackbar(
      '备份成功', 
      '数据已备份到本地存储',

_performBackup 方法执行备份操作。首先显示一个加载对话框,中心显示圆形进度指示器。barrierDismissible 设置为 false,防止用户点击外部关闭对话框。使用 Future.delayed 模拟异步备份操作,延迟 2 秒后关闭加载对话框。然后显示 Snackbar 提示备份成功,告知用户数据已保存到本地存储。

      snackPosition: SnackPosition.BOTTOM,
      backgroundColor: _primaryColor,
      colorText: Colors.white,
    );
  });
}

void _showRestoreDialog() {
  Get.dialog(
    AlertDialog(
      title: Row(

Snackbar 显示在屏幕底部,背景色为主题绿色,文字颜色为白色。实际应用中这里应该调用存储服务的备份方法,将数据序列化后保存到文件。_showRestoreDialog 方法显示数据恢复对话框。同样使用带图标的标题设计,但图标和颜色不同,表示这是不同的操作。

        children: [
          Icon(Icons.restore, color: Colors.orange),
          SizedBox(width: 8.w),
          const Text('数据恢复'),
        ],
      ),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.warning_amber, color: Colors.orange, size: 48.sp),

恢复图标使用橙色,表示这是需要谨慎操作的功能。SizedBox 添加间距,然后显示"数据恢复"文字。content 区域使用 Column 垂直排列内容。首先显示一个大的警告图标,颜色为橙色,大小 48 像素。这个醒目的警告图标提醒用户注意操作风险。

          SizedBox(height: 16.h),
          const Text('恢复数据将覆盖当前所有数据,此操作不可撤销。'),
          SizedBox(height: 8.h),
          Text(
            '请确保已备份当前数据',
            style: TextStyle(fontSize: 12.sp, color: _textSecondary),
          ),
        ],
      ),

SizedBox 添加 16 像素间距。显示警告文字"恢复数据将覆盖当前所有数据,此操作不可撤销。“,明确告知用户操作后果。再添加 8 像素间距,显示提示文字"请确保已备份当前数据”,字体较小,颜色为次要文本灰色。这种多层次的警告设计有效防止用户误操作导致数据丢失。

      actions: [
        TextButton(
          onPressed: () => Get.back(), 
          child: const Text('取消'),
        ),
        ElevatedButton(
          style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
          onPressed: () {
            Get.back();
            Get.snackbar('提示', '请选择备份文件');

actions 定义底部按钮。取消按钮关闭对话框。确认按钮使用橙色背景,与警告主题一致,视觉上强调这是危险操作。点击时关闭对话框,然后显示提示让用户选择备份文件。实际应用中这里应该打开文件选择器,让用户选择之前备份的文件,然后读取并恢复数据。

          }, 
          child: const Text('选择文件'),
        ),
      ],
    ),
  );
}

void _showRateDialog() {
  Get.dialog(
    AlertDialog(
      title: const Text('感谢支持'),
      content: Column(

完成恢复对话框。_showRateDialog 方法显示评分对话框。title 显示"感谢支持",表达对用户的感谢。content 使用 Column 垂直排列内容,包括爱心图标、感谢文字和五星图标。这个对话框的设计更轻松友好,鼓励用户给应用好评。

        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.favorite, color: Colors.red, size: 48.sp),
          SizedBox(height: 16.h),
          const Text('感谢您的使用!'),
          SizedBox(height: 8.h),
          const Text('如果觉得好用,请给我们一个好评吧~'),
          SizedBox(height: 16.h),

mainAxisSize 设置为 min 让对话框高度自适应。首先显示红色爱心图标,大小 48 像素,表达感谢之情。SizedBox 添加 16 像素间距。显示"感谢您的使用!“文字,再添加 8 像素间距。显示引导文字"如果觉得好用,请给我们一个好评吧~”,语气轻松友好。再添加 16 像素间距。

          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(5, (index) => 
              Icon(Icons.star, color: Colors.amber, size: 32.sp)
            ),
          ),
        ],
      ),
      actions: [
        TextButton(

Row 水平居中排列五个星星图标。使用 List.generate 生成 5 个星星图标,颜色为琥珀色(金黄色),大小 32 像素。这个五星图标直观展示评分系统,增加趣味性和吸引力。actions 定义底部按钮,包括"以后再说"和"去评分"两个选项。

          onPressed: () => Get.back(), 
          child: const Text('以后再说'),
        ),
        ElevatedButton(
          onPressed: () {
            Get.back();
            Get.snackbar('感谢', '感谢您的支持!');
          }, 
          child: const Text('去评分'),
        ),
      ],
    ),
  );
}

"以后再说"按钮关闭对话框,不强制用户评分。"去评分"按钮关闭对话框后显示感谢提示。实际应用中这里应该打开应用商店的评分页面,让用户可以直接评分。这种友好的引导方式比强制评分更容易获得用户好评。

void _shareApp() {
  Get.snackbar(
    '分享', 
    '分享功能开发中',
    snackPosition: SnackPosition.BOTTOM,
  );
}

_shareApp 方法处理分享应用功能。目前显示"分享功能开发中"提示。实际应用中这里应该调用系统分享功能,生成应用下载链接或二维码,让用户可以分享给朋友。Snackbar 显示在屏幕底部,简洁地告知用户功能状态。

版本信息

Widget _buildVersionInfo() {
  return Column(
    children: [
      Text(
        '个人理财管家',
        style: TextStyle(
          fontSize: 14.sp,
          color: _textSecondary,
        ),
      ),

_buildVersionInfo 方法构建版本信息组件。Column 垂直排列应用名称、版本号和版权信息。第一个 Text 显示应用名称"个人理财管家",字体大小 14 像素,颜色为次要文本灰色。这个名称让用户知道当前使用的应用,增强品牌认知。

      SizedBox(height: 4.h),
      Text(
        'Version 1.0.0',
        style: TextStyle(
          fontSize: 12.sp,
          color: _textSecondary.withOpacity(0.7),
        ),
      ),
      SizedBox(height: 8.h),
      GestureDetector(

SizedBox 添加 4 像素间距。第二个 Text 显示版本号"Version 1.0.0",字体大小 12 像素,颜色为次要文本灰色的 70% 透明度,进一步弱化。版本号帮助用户和开发者识别应用版本,便于问题反馈和更新管理。SizedBox 添加 8 像素间距。GestureDetector 包裹版权信息,添加点击交互。

        onTap: () => _showDebugInfo(),
        child: Text(
          '© 2024 OpenHarmony Community',
          style: TextStyle(
            fontSize: 12.sp,
            color: _textSecondary.withOpacity(0.5),
          ),
        ),
      ),
    ],
  );
}

onTap 回调调用 _showDebugInfo 方法显示调试信息。Text 显示版权信息"© 2024 OpenHarmony Community",字体大小 12 像素,颜色为次要文本灰色的 50% 透明度,最弱化的显示。这个隐藏的点击功能是开发者常用的技巧,普通用户不会注意到,但开发者可以快速查看调试信息。

void _showDebugInfo() {
  Get.bottomSheet(
    Container(
      padding: EdgeInsets.all(16.w),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
      ),
      child: Column(

_showDebugInfo 方法显示调试信息底部弹窗。使用 Get.bottomSheet 从屏幕底部弹出。Container 包裹内容,padding 添加 16 像素内边距。BoxDecoration 定义样式,背景色为白色,只设置顶部圆角半径 16 像素。Column 垂直排列弹窗内容。

        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Center(
            child: Container(
              width: 40.w,
              height: 4.h,
              decoration: BoxDecoration(
                color: Colors.grey[300],

mainAxisSize 设置为 min 让弹窗高度自适应。crossAxisAlignment 设置为 start 让内容左对齐。Center 居中显示一个小横条,这是底部弹窗的常见设计元素。Container 宽度 40 像素,高度 4 像素。BoxDecoration 设置背景色为浅灰色,圆角半径 2 像素。这个小横条提示用户可以下滑关闭弹窗。

                borderRadius: BorderRadius.circular(2.r),
              ),
            ),
          ),
          SizedBox(height: 16.h),
          Text('调试信息', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
          SizedBox(height: 16.h),
          _buildDebugItem('应用版本', '1.0.0'),

完成小横条的构建。SizedBox 添加 16 像素间距。Text 显示标题"调试信息",字体大小 16 像素,加粗显示。再添加 16 像素间距。_buildDebugItem 方法创建调试信息项,第一项显示应用版本 1.0.0。这些信息对开发和调试很有帮助。

          _buildDebugItem('Flutter版本', '3.16.0'),
          _buildDebugItem('Dart版本', '3.2.0'),
          _buildDebugItem('设备信息', 'OpenHarmony'),
          SizedBox(height: 16.h),
        ],
      ),
    ),
  );
}

Widget _buildDebugItem(String label, String value) {

继续添加调试信息项:Flutter 版本 3.16.0、Dart 版本 3.2.0、设备信息 OpenHarmony。这些技术信息帮助开发者快速了解运行环境。SizedBox 添加 16 像素底部间距。_buildDebugItem 方法构建单个调试信息项,接收标签和值两个参数。

  return Padding(
    padding: EdgeInsets.symmetric(vertical: 8.h),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: TextStyle(color: _textSecondary)),
        Text(value),
      ],
    ),
  );
}

Padding 添加垂直 8 像素内边距。Row 水平排列标签和值,mainAxisAlignment 设置为 spaceBetween 让两者分别靠左右两端。标签文字颜色为次要文本灰色,值文字使用默认颜色。这种左右对齐的布局清晰展示键值对信息,易于阅读。调试信息面板为开发者提供了便捷的信息查看入口。

深色模式适配

深色模式需要在主题中配置对应的颜色:

// app_theme.dart 中的深色主题配置
static ThemeData darkTheme = ThemeData(
  brightness: Brightness.dark,
  primaryColor: _primaryColor,
  scaffoldBackgroundColor: const Color(0xFF121212),
  cardColor: const Color(0xFF1E1E1E),
  appBarTheme: const AppBarTheme(
    backgroundColor: Color(0xFF1E1E1E),
    elevation: 0,
  ),
  // ... 其他配置
);

深色模式下,背景色使用深灰色而不是纯黑色,减少视觉疲劳。卡片颜色比背景稍浅,保持层次感。

性能优化

个人中心页面的性能优化要点:

  1. 使用 const 构造函数创建不变的 Widget
  2. Obx 只包裹需要响应式更新的最小范围
  3. 图标使用 Material Icons,不需要额外加载资源
  4. 对话框使用 Get.dialog 而不是 showDialog,更轻量
// 好的做法:Obx 只包裹需要更新的部分
Obx(() => Text('${controller.totalAssets.value}'))

// 不好的做法:Obx 包裹整个卡片
Obx(() => Card(child: ...))

设计要点总结

个人中心页面的设计需要注意:

  1. 功能分组清晰,用户能快速找到需要的入口
  2. 统计数据一目了然,让用户了解自己的记账情况
  3. 常用功能(如深色模式)可以直接操作,无需进入二级页面
  4. 保持视觉一致性,图标、颜色、间距统一
  5. 对话框信息完整,操作有确认提示
  6. 支持下拉刷新,数据可以手动更新

完整代码结构

lib/app/modules/profile/
├── profile_page.dart      # 页面UI
├── profile_controller.dart # 控制器
├── profile_binding.dart   # 依赖绑定
├── settings_page.dart     # 通用设置页面
├── currency_page.dart     # 货币设置页面
├── about_page.dart        # 关于我们页面
├── feedback_page.dart     # 意见反馈页面
└── export_page.dart       # 数据导出页面

下一篇我们将实现通用设置页面,提供更多个性化配置选项。


欢迎加入 OpenHarmony 跨平台开发社区,获取更多技术资源和交流机会:

https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐