个人信息的案例虽然通过对话框可以更新主页面和子页面的值,但是退出了这个软件,再回到这两个页面,会发现还是显示默认值,这是因为值没有保存起来,退出软件,设置的值就丢失了。需要使用数据持久化技术。
持久化数据流说明
Dart
用户修改数据
→ 触发回调函数
→ HomePage 更新状态
→ 调用 PreferenceService.saveUserData()
→ 保存到 SharedPreferences
↓
应用重启
→ HomePage.initState()
→ 调用 PreferenceService.loadUserData()
→ 从 SharedPreferences 读取数据
→ 更新界面显示
数据的生命周期
Dart
用户输入 → UI更新 → 状态管理 → 持久化保存 → 应用重启 → 数据加载 → UI显示
数据流向分析
Dart
// 修改数据时的完整流程
onPressed: () {
// 1. 获取用户输入
String newName = _nameController.text.trim();
// 2. 更新本地状态
setState(() {
_name = newName;
});
// 3. 回调父组件更新
widget.onNameChanged(newName);
// 4. 持久化保存(在回调中)
_saveName(newName);
}
分层架构设计
Dart
┌─────────────────┐
│ UI 层 │ - Widgets、页面布局
├─────────────────┤
│ 业务逻辑层 │ - 状态管理、数据处理
├─────────────────┤
│ 数据持久层 │ - PreferenceService
├─────────────────┤
│ 原生存储层 │ - SharedPreferences
└─────────────────┘
实现步骤
1.添加依赖:在 pubspec.yaml中添加
Dart
dependencies:
shared_preferences: ^2.5.2
2.创建一个新的文件 lib/services/preference_service.dart
Dart
import 'package:shared_preferences/shared_preferences.dart'; //导入本地存储包
class PreferenceService {
//常量定义--存储键名
static const String _keyName = 'user_name';
static const String _keyGender = 'user_gender';
static const String _keyBirthday = 'user_birthday';
static const String _keyHeight = 'user_height';
// 保存用户数据
static Future<void> saveUserData({
String? name,
String? gender,
String? birthday,
String? height,
}) async {
//获取SharedPreferences实例
//await:等待异步操作完成
//getInstance():单例模式,确保整个应用只有一个实例
final prefs = await SharedPreferences.getInstance();
if (name != null) await prefs.setString(_keyName, name);
if (gender != null) await prefs.setString(_keyGender, gender);
if (birthday != null) await prefs.setString(_keyBirthday, birthday);
if (height != null) await prefs.setString(_keyHeight, height);
}
// 加载用户数据
static Future<Map<String, String>> loadUserData() async {
final prefs = await SharedPreferences.getInstance();
return {
'name': prefs.getString(_keyName) ?? '西西没烦恼',
'gender': prefs.getString(_keyGender) ?? '男',
'birthday': prefs.getString(_keyBirthday) ?? '2000-01-01',
'height': prefs.getString(_keyHeight) ?? '183',
};
}
}
3.修改home_page:导入持久化服务
Dart
import 'package:my_flutter/preference_service.dart';
4.页面初始化时加载数据
Dart
@override
void initState() {
super.initState();
_loadUserData(); // 4.页面初始化时加载数据
}
5.从本地存储加载用户数据
Dart
Future<void> _loadUserData() async {
try {
final userData = await PreferenceService.loadUserData();
setState(() {
_homeName = userData['name']!;
_homeGender = userData['gender']!;
_homeBirthday = userData['birthday']!;
_homeHeight = userData['height']!;
});
} catch (e) {
print('加载用户数据失败: $e');
}
}
6.将用户新设置的值保存在本地存储
Dart
// 保存姓名到本地存储
Future<void> _saveName(String newName) async {
await PreferenceService.saveUserData(name: newName);
}
// 保存性别到本地存储
Future<void> _saveGender(String newGender) async {
await PreferenceService.saveUserData(gender: newGender);
}
// 保存生日到本地存储
Future<void> _saveBirthday(String newBirthday) async {
await PreferenceService.saveUserData(birthday: newBirthday);
}
// 保存身高到本地存储
Future<void> _saveHeight(String newHeight) async {
await PreferenceService.saveUserData(height: newHeight);
}
7.添加保存到存储的代码
Dart
Navigator.push(context, MaterialPageRoute(builder: (context)=> PersonInformationPage(
onNameChanged: (newName) {
setState(() {
_homeName = newName;
});
_saveName(newName); // 保存到本地存储
},
onGenderChanged: (newGender){
setState(() {
_homeGender = newGender;
});
_saveGender(newGender); // 保存到本地存储
},
onBirthdayChanged: (newBirthday){
setState(() {
_homeBirthday = newBirthday;
});
_saveBirthday(newBirthday); // 保存到本地存储
},
onHeightChanged: (newHeight){
setState(() {
_homeHeight = newHeight;
});
_saveHeight(newHeight); // 保存到本地存储
},
)));
8.来到子页面personal_information_page:导入持久化服务
Dart
import 'package:my_flutter/preference_service.dart';
9.设置加载初始数据
Dart
@override
void initState(){
super.initState();
_nameController.text = _name;
_loadInitialData(); // 9.加载初始数据
}
10.定义加载初始数据
Dart
//10. 加载初始数据
Future<void> _loadInitialData() async {
try {
final userData = await PreferenceService.loadUserData();
setState(() {
_name = userData['name']!;
_selectedGender = userData['gender']!;
_birthday = userData['birthday']!;
_height = userData['height']!;
_nameController.text = _name;
});
} catch (e) {
print('加载初始数据失败: $e');
}
}
学习方向
1.为什么选择 SharedPreferences 而不是 SQLite 或文件存储
2.数据类型转换、空值处理策略
3.async/await 使用、Future 处理、错误处理
4.组件生命周期、资源管理、内存泄漏预防
5.父子组件通信、回调模式、数据传递
6.服务类设计、静态方法使用、接口封装
代码实例
home_page
Dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_flutter/person_information_page.dart';
import 'package:my_flutter/preference_service.dart'; // 3.导入持久化服务
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var _homeGender = "男"; //性别
var _homeName = "西西没烦恼"; //昵称
var _homeBirthday = "2000-01-01"; // 出生年月
var _homeHeight = "183"; // 身高
@override
void initState() {
super.initState();
_loadUserData(); // 4.页面初始化时加载数据
}
// 5.从本地存储加载用户数据
Future<void> _loadUserData() async {
try {
final userData = await PreferenceService.loadUserData();
setState(() {
_homeName = userData['name']!;
_homeGender = userData['gender']!;
_homeBirthday = userData['birthday']!;
_homeHeight = userData['height']!;
});
} catch (e) {
print('加载用户数据失败: $e');
}
}
// 保存姓名到本地存储
Future<void> _saveName(String newName) async {
await PreferenceService.saveUserData(name: newName);
}
// 保存性别到本地存储
Future<void> _saveGender(String newGender) async {
await PreferenceService.saveUserData(gender: newGender);
}
// 保存生日到本地存储
Future<void> _saveBirthday(String newBirthday) async {
await PreferenceService.saveUserData(birthday: newBirthday);
}
// 保存身高到本地存储
Future<void> _saveHeight(String newHeight) async {
await PreferenceService.saveUserData(height: newHeight);
}
// =======================计算具体年龄的方法=================================
int get _age {
try {
//将字符串格式的生日(如 "2000-01-01")转换为 DateTime对象
final birthDate = DateTime.parse(_homeBirthday);
final now = DateTime.now(); //获取当前日期时间
int age = now.year - birthDate.year; //计算基础年龄(虚岁)
if (now.month < birthDate.month || (now.month == birthDate.month && now.day < birthDate.day)) { //判断是否已过生日(实岁)
age--;
}
return age;
} catch (e) {
return 23; // 默认年龄
}
}
//构建UI
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: double.infinity,
padding: const EdgeInsets.all(16),
// 主页背景颜色
decoration: const BoxDecoration(
gradient: LinearGradient(//渐变
begin: Alignment.topCenter,
end: Alignment.centerRight,
colors: [
Color(0xFF62F4F4),
Color(0xFFF6F7F9),
],
),
),
child: SingleChildScrollView( //可滚动的页面
physics: const ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 个人信息
buildUserInfo(),
const SizedBox(height: 25),
],
),
),
),
);
}
//=======================个人信息的UI=====================================
Widget buildUserInfo() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 96),
// 头像:讲图片裁剪成圆形
ClipOval(
child: Image.asset(
"assets/images/apple.png",
width: 90,
height: 90,
fit: BoxFit.cover,
),
),
const SizedBox(height: 8),
// 昵称
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () { //点击跳转个人信息详情页面
//7.添加保存到存储的代码
Navigator.push(context, MaterialPageRoute(builder: (context)=> PersonInformationPage(
onNameChanged: (newName) {
setState(() {
_homeName = newName;
});
_saveName(newName); // 保存到本地存储
},
onGenderChanged: (newGender){
setState(() {
_homeGender = newGender;
});
_saveGender(newGender); // 保存到本地存储
},
onBirthdayChanged: (newBirthday){
setState(() {
_homeBirthday = newBirthday;
});
_saveBirthday(newBirthday); // 保存到本地存储
},
onHeightChanged: (newHeight){
setState(() {
_homeHeight = newHeight;
});
_saveHeight(newHeight); // 保存到本地存储
},
)));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_homeName, // 名字
style: TextStyle(
color: const Color(0xFF3D3D3D),
fontWeight: FontWeight.bold,
fontSize: 24,
),
),
const SizedBox(width: 10),
Image.asset(
"assets/images/cherry.png",
width: 20,
height: 20,
),
],
),
),
],
),
const SizedBox(height: 8),
// 个人资料
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_homeGender, //性别
style: TextStyle(color: const Color(0xFF3D3D3D), fontSize: 16),
),
// 竖线分隔符
Container(
width: 1,
height: 16,
margin: const EdgeInsets.symmetric(horizontal: 12),
color: const Color(0xFFD8D8D8),
),
Text(
"${_age}岁", // 年龄
style: TextStyle(color: const Color(0xFF3D3D3D), fontSize: 16),
),
// 竖线分隔符
Container(
width: 1,
height: 16,
margin: const EdgeInsets.symmetric(horizontal: 12),
color: const Color(0xFFD8D8D8),
),
Text(
_homeHeight,//身高
style: TextStyle(color: const Color(0xFF3D3D3D), fontSize: 16),
),
Text(
"cm",
style: TextStyle(color: const Color(0xFF3D3D3D), fontSize: 16),
),
],
),
],
);
}
}
personal_information_page
Dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_flutter/preference_service.dart';//8.
class PersonInformationPage extends StatefulWidget {
const PersonInformationPage({super.key,
required this.onNameChanged,
required this.onGenderChanged,
required this.onBirthdayChanged,
required this.onHeightChanged
});
final Function(String) onNameChanged;
final Function(String) onGenderChanged;
final Function(String) onBirthdayChanged;
final Function(String) onHeightChanged;
@override
State<StatefulWidget> createState() => _PersonInformationPageState();
}
class _PersonInformationPageState extends State<PersonInformationPage> {
var _selectedGender = "男";//性别
var _name = "西西没烦恼"; //昵称
var _birthday = "2025-10-01";//初始日期
var _height = "183"; //身高
final TextEditingController _nameController = TextEditingController();
@override
void initState(){
super.initState();
_nameController.text = _name;
_loadInitialData(); // 9.加载初始数据
}
//10. 加载初始数据
Future<void> _loadInitialData() async {
try {
final userData = await PreferenceService.loadUserData();
setState(() {
_name = userData['name']!;
_selectedGender = userData['gender']!;
_birthday = userData['birthday']!;
_height = userData['height']!;
_nameController.text = _name;
});
} catch (e) {
print('加载初始数据失败: $e');
}
}
@override
void dispose(){
_nameController.dispose();
super.dispose();
}
// 构建UI
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: double.infinity,
padding: const EdgeInsets.all(16),
// 主页背景颜色
decoration: const BoxDecoration(
gradient: LinearGradient( //颜色渐变
begin: Alignment.topCenter,
end: Alignment.centerRight,
colors: [
Color(0xFF62F4F4),
Color(0xFFF7F7F9),
],
),
),
child: SingleChildScrollView( //可滚动的页面
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(height: 96),
// 头像:裁剪成圆形
ClipOval(
child: Image.asset(
"assets/images/apple.png",
width: 90,
height: 90,
fit: BoxFit.cover,
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"点击更换头像",
style: TextStyle(
color: const Color(0xFF3D3D3D),
fontWeight: FontWeight.bold,
fontSize: 24,
),
),
const SizedBox(width: 10),
],
),
const SizedBox(height: 8),
Text(
"点击可更换个人信息",
style: TextStyle(
color: const Color(0xFF3D3D3D).withOpacity(0.6),
fontSize: 12,
),
),
const SizedBox(height: 38),
// 个人信息列表
buildListInformation(),
],
),
),
),
);
}
//===========================个人信息列表=======================================//
Widget buildListInformation() {
return Padding(
padding: const EdgeInsets.only(top: 10),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 31),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 昵称
GestureDetector(
onTap: () {
ChangeNameDialog();
},
child: Row(
children: [
Text(
"昵称",
style: TextStyle(fontSize: 16, color: Color(0xFF3D3D3D)),
),
const Spacer(),
Text(
_name,
style: TextStyle(
color: Color(0xFF3D3D3D).withOpacity(0.6),
fontSize: 14,
),
),
const SizedBox(width: 6),
Image.asset(
"assets/images/cherry.png",
width: 30,
height: 30,
),
],
),
),
const SizedBox(height: 28),
// 性别
GestureDetector(
onTap: () {
changeGenderDialog();
},
child: Row(
children: [
Text(
"性别",
style: TextStyle(fontSize: 16, color: Color(0xFF3D3D3D)),
),
const Spacer(),
Text(
_selectedGender, // 显示当前选中性别
style: TextStyle(
color: Color(0xFF3D3D3D).withOpacity(0.6),
fontSize: 14,
),
),
const SizedBox(width: 6),
Image.asset(
"assets/images/cherry.png",
width: 30,
height: 30,
),
],
),
),
const SizedBox(height: 28),
// 生日
GestureDetector(
onTap: () {
changeBirthdayDialog();
},
child: Row(
children: [
Text(
"生日",
style: TextStyle(fontSize: 16, color: Color(0xFF3D3D3D)),
),
const Spacer(),
Text(
_birthday,
style: TextStyle(
color: Color(0xFF3D3D3D).withOpacity(0.6),
fontSize: 14,
),
),
const SizedBox(width: 6),
Image.asset(
"assets/images/cherry.png",
width: 30,
height: 30,
),
],
),
),
const SizedBox(height: 28),
// 身高
GestureDetector(
onTap: () {
//3.设置修改身高的提示框
changeHeightDialog();
},
child: Row(
children: [
Text(
"身高",
style: TextStyle(fontSize: 16, color: Color(0xFF3D3D3D)),
),
const Spacer(),
Text(
_height,
style: TextStyle(
color: Color(0xFF3D3D3D).withOpacity(0.6),
fontSize: 14,
),
),
Text(
"cm",
style: TextStyle(
color: Color(0xFF3D3D3D).withOpacity(0.6),
fontSize: 14,
),
),
const SizedBox(width: 6),
Image.asset(
"assets/images/cherry.png",
width: 30,
height: 30,
),
],
),
),
],
),
),
);
}
//========================修改名字的弹窗========================================//
void ChangeNameDialog() {
_nameController.text = _name; //设置当前名字到输入框
showDialog(
context: context,
builder: (builder) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
child: Container(
width: 263,
padding: EdgeInsets.only(top: 20,bottom: 29,left: 34,right: 34),
decoration: BoxDecoration(
color: Colors.white, //背景颜色
borderRadius: BorderRadius.circular(26),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"修改昵称",
style: const TextStyle(
color: Colors.black,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 16),
Container(
padding: EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Color(0xFFD8D8D8),//输入框颜色
borderRadius: BorderRadius.circular(7),
),
child: TextField( //创建文本输入框组件
controller: _nameController, //控制器:用于获取或者设置输入框文本内容
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
//hintText: "请输入",//提示性文字
hintStyle: TextStyle(color: Colors.white54),
border: InputBorder.none,
),
),
),
SizedBox(height: 20),
Row(
children: [
//取消按钮
Expanded(
child: Container(
height: 44, // 固定高度
child: TextButton(
onPressed: () => Navigator.pop(context),
style: TextButton.styleFrom(
backgroundColor: Color(0xFFD8D8D8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
child: Text(
"取消",
style: TextStyle(color: Color(0xFF3D3D3D), fontSize: 16),
),
),
),
),
SizedBox(width: 50),
//确定按钮
Expanded(
child: Container(
height: 44, // 固定高度
child: TextButton(
onPressed: () {//确定按钮具体的实现逻辑
if(_nameController.text.trim().isNotEmpty){ //检查输入框内容是否非空
String newName = _nameController.text.trim(); //获取输入框的文本内容
setState(() {
_name = newName; //当前页面状态,触发界面重绘
});
widget.onNameChanged(newName); //调用回调函数通知首页(home_page)
Navigator.pop(context);//关闭对话框
}
},
style: TextButton.styleFrom(
backgroundColor: Color(0xff1F8FFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
child: Text(
"确定",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
),
],
)
],
),
),
);
},
).then((value) { //处理异步操作完成后的回调
if(value != null && mounted) { //检查返回的结果不为空 && 检查当前组件是否还在挂载状态(避免在组件销毁后更新状态)
Navigator.pop(context, value); //返回上一页并传递数据
}
});
}
//========================修改性别的弹窗========================================//
void changeGenderDialog() {
showDialog(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.white, //背景颜色
shape: RoundedRectangleBorder( //形状配置
borderRadius: BorderRadius.circular(26),
),
child: Container(
width: 250, // 自定义宽度
padding: EdgeInsets.all(16), // 内边距
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 男性选项
ListTile(
dense: true, //紧凑模式
title: Text("男", style: TextStyle(fontSize: 16, color: Color(0xFF3D3D3D))),
trailing: _selectedGender == "男" //只有选中"男",才显示男性行尾部图标(三元运算符)
? Image.asset("assets/images/cherry.png", width: 24, height: 24)
: null,
//点击事件
onTap: () {
Navigator.pop(context);//关闭对话框
setState(() {
_selectedGender = "男"; //更新性别变量值
});
widget.onGenderChanged("男");// 调用回调函数,传值回主页
},
),
// 女性选项
ListTile(
dense: true,//紧凑模式
title: Text("女", style: TextStyle(fontSize: 16, color: Color(0xFF3D3D3D))),
trailing: _selectedGender == "女" //只有选中"女",才显示女性行尾部图标
? Image.asset("assets/images/cherry.png", width: 24, height: 24)
: null,
onTap: () {
Navigator.pop(context);
setState(() {
_selectedGender = "女"; //更新性别变量值
});
widget.onGenderChanged("女");// 调用回调函数,传值回主页
},
),
],
),
),
);
},
);
}
//构建选择生日的弹窗
//==========================选择生日的弹窗=====================================//
void changeBirthdayDialog() {
//1.设定初始值
int selectedYear = 1900; //从哪一年开始
int selectedMonth = 1; //默认选中月份
int selectedDay = 1; //默认选中日期
//2.设置年的初始选中项为2000:意思就是往后滑动一百项,1900+100 = 2000
final controllerYear = FixedExtentScrollController(initialItem: 100);
showDialog(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
child: Container(
width: 320,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 16),
// 3.自定义年月日选择器
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 年份选择
Expanded(
child: Column(
children: [
SizedBox(
height: 120,
child: Stack(
children: [
CupertinoPicker(
itemExtent: 40, //选中项的高度
scrollController: controllerYear,//设置初始选中项的值(2000)
onSelectedItemChanged: (int index) { //当选中项变化时触发,参数为索引
selectedYear = 1900 + index; //存储用户选择的数据(将数组索引转换为实际的年份值)
},
// 移除选择覆盖层
selectionOverlay: null, //不移除会有一个默认的灰色背景颜色
//DateTime.now().year获取当年年份
//List.generate(126,(index)):生成指定长度的列表
children: List.generate(DateTime.now().year - 1899, (index) {
return Center(
child: Text(
'${1900 + index}', //年滑动框显示的文本
style: TextStyle(fontSize: 18),//文本的样式
),
);
}),
),
// 上横线
Positioned(
top: 40, // 调整到合适位置
left: 0,
right: 0,
child: Container(
height: 1,
color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
Positioned(
top: 48, // 调整到合适位置
left: 70,// 调整到合适位置
right: 0,
child: Container(
child: Text("年",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),
//color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
// 下横线
Positioned(
bottom: 40, // 调整到合适位置
left: 0,
right: 0,
child: Container(
height: 1,
color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
],
),
),
],
),
),
// 月份选择
Expanded(
child: Column(
children: [
SizedBox(
height: 120,
child: Stack(
children: [
CupertinoPicker(
itemExtent: 40,
onSelectedItemChanged: (int index) {
selectedMonth = index + 1;
},
// 移除选择覆盖层
selectionOverlay: null,
children: List.generate(12, (index) {
return Center(
child: Text(
'${index + 1}',//月滑动框显示的文本
style: TextStyle(fontSize: 18),
),
);
}),
),
Positioned(
top: 40,
left: 0,
right: 0,
child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),
),
Positioned(
top: 48, // 调整到合适位置
left: 60,
right: 0,
child: Container(
child: Text("月",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),
//color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
Positioned(
bottom: 40,
left: 0,
right: 0,
child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),
),
],
),
),
],
),
),
// 日期选择
Expanded(
child: Column(
children: [
SizedBox(
height: 120,
child: Stack(
children: [
CupertinoPicker(
itemExtent: 40,
onSelectedItemChanged: (int index) {
selectedDay = index + 1;
},
// 移除选择覆盖层
selectionOverlay: null,
children: List.generate(31, (index) {
return Center(
child: Text(
'${index + 1}',//日滑动框显示的文本
style: TextStyle(fontSize: 18),
),
);
}),
),
Positioned(
top: 40,
left: 0,
right: 0,
child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),
),
Positioned(
top: 48, // 调整到合适位置
left: 60,
right: 0,
child: Container(
child: Text("日",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),
//color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
Positioned(
bottom: 40,
left: 0,
right: 0,
child: Container(height: 1, color: Color(0xFFD8D8D8).withOpacity(0.6),),
),
],
),
),
],
),
),
],
),
const SizedBox(height: 16),
// 4.按钮的实现逻辑
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
style: TextButton.styleFrom(
backgroundColor: Color(0xFFD8D8D8),
foregroundColor: Color(0xFF3D3D3D),
minimumSize: Size(100, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
onPressed: () => Navigator.pop(context),
child: Text(
"取消",
style: TextStyle(fontSize: 16),
),
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Color(0xFF1F8FFF),
foregroundColor: Colors.white,
minimumSize: Size(100, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
onPressed: () {
//将年、月、日组合成标准格式的日期字符串(YYYY-MM-DD:eg:2000-01-01)
//.padLeft(2,'0'):往左侧填充0.确保2位数字
String formattedDate = "$selectedYear-${selectedMonth.toString().padLeft(2, '0')}-${selectedDay.toString().padLeft(2, '0')}";
Navigator.pop(context);
setState(() {
_birthday = formattedDate;
});
widget.onBirthdayChanged(formattedDate); //通过回调函数,把新的日期传给主页
},
child: Text(
"确定",
style: TextStyle(fontSize: 16),
),
),
],
),
],
),
),
);
},
);
}
//4.定义选择身高的提示框
//=========================选择身高的提示框=======================================//
void changeHeightDialog(){
final int heightMin = 100;//默认最小的身高值
final controllerHeight = FixedExtentScrollController(initialItem: 60);//默认选中的值:100+60 = 160
showDialog(
context: context,
builder: (context){
return Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 250, // 限制 Dialog 最大高度
),
child: Container(
width: 320,
padding: EdgeInsets.only(top: 40,left: 16,right: 16,bottom: 30),
child: Column(
mainAxisSize: MainAxisSize.min, // 重要:使用最小空间
children: [
// 身高选择
Container(
height: 100,
child: Stack(
children: [
CupertinoPicker(
itemExtent: 40, //单个项的高度
scrollController: controllerHeight, //设置默认选中的值
onSelectedItemChanged: (int index) {
},
selectionOverlay: null,
children: List.generate(121, (index) {
return Center(
child: Text(
'${heightMin + index}',
style: TextStyle(fontSize: 18),
),
);
}),
),
// 上横线
Positioned(
top: 35, // 调整到合适位置
left: 0,
right: 0,
child: Container(
height: 1,
color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
Positioned(
top: 35, // 调整到合适位置
right: 70,// 调整到合适位置
child: Container(
child: Text("cm",style: TextStyle(color: Color(0xFF3D3D3D),fontSize: 20),),
//color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
// 下横线
Positioned(
bottom: 35, // 调整到合适位置
left: 0,
right: 0,
child: Container(
height: 1,
color: Color(0xFFD8D8D8).withOpacity(0.6),
),
),
],
),
),
const SizedBox(height: 20),
// 确认按钮
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
style: TextButton.styleFrom(
backgroundColor: Color(0xFFD8D8D8),
foregroundColor: Color(0xFF3D3D3D),
minimumSize: Size(100, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
onPressed: () => Navigator.pop(context),
child:Text(
"取消",
style: TextStyle(fontSize: 16),
),
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Color(0xFF1F8FFF),
foregroundColor: Colors.white,
minimumSize: Size(100, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
onPressed: () {
// 从控制器获取当前选中的索引
int selectedIndex = controllerHeight.selectedItem;
// 转换为实际身高值:100 + 索引
int heightValue = 100 + selectedIndex;
Navigator.pop(context);
setState(() {
_height = heightValue.toString();//更新此页面的UI
});
widget.onHeightChanged(heightValue.toString()); //把身高通过回调函数传给主页
},
child: Text(
"确认",
style: TextStyle(fontSize: 16),
),
),
],
),
],
),
),
),
);
}
);
}
}
preference_service
Dart
import 'package:shared_preferences/shared_preferences.dart'; //导入本地存储包
class PreferenceService {
//常量定义--存储键名
static const String _keyName = 'user_name';
static const String _keyGender = 'user_gender';
static const String _keyBirthday = 'user_birthday';
static const String _keyHeight = 'user_height';
// 保存用户数据
static Future<void> saveUserData({
String? name,
String? gender,
String? birthday,
String? height,
}) async {
//获取SharedPreferences实例
//await:等待异步操作完成
//getInstance():单例模式,确保整个应用只有一个实例
final prefs = await SharedPreferences.getInstance();
if (name != null) await prefs.setString(_keyName, name);
if (gender != null) await prefs.setString(_keyGender, gender);
if (birthday != null) await prefs.setString(_keyBirthday, birthday);
if (height != null) await prefs.setString(_keyHeight, height);
}
// 加载用户数据
static Future<Map<String, String>> loadUserData() async {
final prefs = await SharedPreferences.getInstance();
return {
'name': prefs.getString(_keyName) ?? '西西没烦恼',
'gender': prefs.getString(_keyGender) ?? '男',
'birthday': prefs.getString(_keyBirthday) ?? '2000-01-01',
'height': prefs.getString(_keyHeight) ?? '183',
};
}
}