Flutter---个人信息(5)---持久化存储

个人信息的案例虽然通过对话框可以更新主页面和子页面的值,但是退出了这个软件,再回到这两个页面,会发现还是显示默认值,这是因为值没有保存起来,退出软件,设置的值就丢失了。需要使用数据持久化技术

持久化数据流说明

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


}
相关推荐
芝麻开门-新起点7 小时前
flutter 生命周期管理:从 Widget 到 State 的完整解析
开发语言·javascript·ecmascript
陈果然DeepVersion7 小时前
Java大厂面试真题:Spring Boot+Kafka+AI智能客服场景全流程解析(五)
java·spring boot·kafka·向量数据库·大厂面试·rag·ai智能客服
FAFU_kyp8 小时前
Spring Boot 邮件发送系统 - 从零到精通教程
java·网络·spring boot
脚踏实地的大梦想家8 小时前
【Docker】P2 Docker 命令:从Nginx部署到镜像分享的全流程指南
java·nginx·docker
ConardLi8 小时前
Easy Dataset 已经突破 11.5K Star,这次又带来多项功能更新!
前端·javascript·后端
Blossom.1188 小时前
把AI“编”进草垫:1KB决策树让宠物垫自己报「如厕记录」
java·人工智能·python·算法·决策树·机器学习·宠物
芒克芒克8 小时前
ssm框架之Spring(上)
java·后端·spring
冴羽8 小时前
10 个被严重低估的 JS 特性,直接少写 500 行代码
前端·javascript·性能优化
消失的旧时光-19438 小时前
Android ble理解
java·kotlin