Flutter for OpenHarmony 个人理财管理App实战 - 个人中心页面
个人中心是App的重要入口,用户可以在这里查看账户概览、进入各项设置、管理数据等。本篇将实现一个功能完善的个人中心页面,详细介绍每个模块的设计思路和实现细节。
页面功能规划
个人中心页面包含以下功能模块:
- 用户信息展示区 - 头像、昵称、个性签名
- 资产统计概览 - 总资产、账户数、记账笔数
- 账户管理入口 - 账户、分类、理财目标
- 数据管理入口 - 导出、备份
- 设置入口 - 通用设置、货币、深色模式
- 关于入口 - 关于我们、意见反馈、评分
这种布局符合用户的使用习惯,常用功能放在显眼位置,设置类功能放在下方。
控制器实现
首先创建 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,
),
// ... 其他配置
);
深色模式下,背景色使用深灰色而不是纯黑色,减少视觉疲劳。卡片颜色比背景稍浅,保持层次感。
性能优化
个人中心页面的性能优化要点:
- 使用
const构造函数创建不变的 Widget Obx只包裹需要响应式更新的最小范围- 图标使用 Material Icons,不需要额外加载资源
- 对话框使用
Get.dialog而不是showDialog,更轻量
// 好的做法:Obx 只包裹需要更新的部分
Obx(() => Text('${controller.totalAssets.value}'))
// 不好的做法:Obx 包裹整个卡片
Obx(() => Card(child: ...))
设计要点总结
个人中心页面的设计需要注意:
- 功能分组清晰,用户能快速找到需要的入口
- 统计数据一目了然,让用户了解自己的记账情况
- 常用功能(如深色模式)可以直接操作,无需进入二级页面
- 保持视觉一致性,图标、颜色、间距统一
- 对话框信息完整,操作有确认提示
- 支持下拉刷新,数据可以手动更新
完整代码结构
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
更多推荐



所有评论(0)