在这里插入图片描述

前言

个人信息页面是用户管理自己账户信息的重要入口。在口腔护理应用中,除了基本的用户信息外,还可以记录与口腔健康相关的个人信息,如牙齿状况、是否戴牙套等,为用户提供更个性化的服务。

本文将介绍如何在 Flutter 中实现一个功能完善的个人信息管理页面。

功能设计

个人信息页面需要实现以下功能:

  • 头像展示:显示用户头像和更换入口
  • 基本信息:昵称、年龄、性别、手机号
  • 口腔信息:牙齿状况、是否戴牙套、是否有假牙
  • 信息编辑:支持修改可编辑的信息项

页面基础结构

个人信息页面使用 StatelessWidget 实现:

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('个人信息')),
      body: Consumer<AppProvider>(
        builder: (context, provider, _) {
          return SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [

使用 Consumer 监听 AppProvider 的数据变化,SingleChildScrollView 确保内容可以滚动。

头像区域

头像区域包含头像显示和更换按钮:

                Container(
                  padding: const EdgeInsets.all(24),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(16),
                  ),
                  child: Column(
                    children: [
                      Container(
                        width: 100,
                        height: 100,
                        decoration: BoxDecoration(
                          color: const Color(0xFF26A69A).withOpacity(0.1),
                          shape: BoxShape.circle,
                        ),
                        child: const Icon(Icons.person, size: 50, 
                            color: Color(0xFF26A69A)),
                      ),

头像使用圆形容器,主题色浅色背景配合人物图标。实际项目中应该显示用户上传的头像图片。

更换头像按钮:

                      const SizedBox(height: 12),
                      TextButton(
                        onPressed: () {
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('更换头像功能开发中')),
                          );
                        },
                        child: const Text('更换头像'),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 16),

更换头像按钮使用 TextButton,点击时显示提示。

基本信息区域

基本信息使用列表形式展示:

                Container(
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(16),
                  ),
                  child: Column(
                    children: [
                      _buildInfoItem('昵称', provider.userName, 
                          () => _editName(context, provider)),
                      const Divider(height: 1, indent: 16),
                      _buildInfoItem('年龄', '${provider.userAge}岁', 
                          () => _editAge(context, provider)),
                      const Divider(height: 1, indent: 16),
                      _buildInfoItem('性别', provider.userGender, 
                          () => _editGender(context, provider)),
                      const Divider(height: 1, indent: 16),
                      _buildInfoItem('手机号', '138****8888', null),
                    ],
                  ),
                ),

使用 Divider 分隔各信息项,indent 设置左侧缩进。手机号不可编辑,传入 null 作为回调。

信息项组件

封装信息项组件:

Widget _buildInfoItem(String label, String value, VoidCallback? onTap) {
  return ListTile(
    title: Text(label),
    trailing: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(value, style: TextStyle(color: Colors.grey.shade600)),
        if (onTap != null) 
            const Icon(Icons.chevron_right, color: Colors.grey),
      ],
    ),
    onTap: onTap,
  );
}

使用 ListTile 实现信息项,trailing 区域显示值和箭头图标。只有可编辑的项才显示箭头。

口腔信息区域

口腔相关的个人信息:

                const SizedBox(height: 16),
                Container(
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(16),
                  ),
                  child: Column(
                    children: [
                      _buildInfoItem('牙齿状况', '良好', null),
                      const Divider(height: 1, indent: 16),
                      _buildInfoItem('是否戴牙套', '否', null),
                      const Divider(height: 1, indent: 16),
                      _buildInfoItem('是否有假牙', '否', null),
                    ],
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }

口腔信息目前为静态展示,实际项目中可以添加编辑功能。

编辑昵称对话框

点击昵称项弹出编辑对话框:

void _editName(BuildContext context, AppProvider provider) {
  final controller = TextEditingController(text: provider.userName);
  showDialog(
    context: context,
    builder: (ctx) => AlertDialog(
      title: const Text('修改昵称'),
      content: TextField(
        controller: controller,
        decoration: const InputDecoration(hintText: '请输入昵称'),
      ),
      actions: [
        TextButton(
            onPressed: () => Navigator.pop(ctx), 
            child: const Text('取消')),
        ElevatedButton(
          onPressed: () {
            if (controller.text.isNotEmpty) {
              provider.updateUserName(controller.text);
            }
            Navigator.pop(ctx);
          },
          child: const Text('保存'),
        ),
      ],
    ),
  );
}

使用 TextEditingController 预填当前昵称,保存时调用 Provider 的更新方法。

编辑年龄对话框

年龄使用加减按钮编辑:

void _editAge(BuildContext context, AppProvider provider) {
  int age = provider.userAge;
  showDialog(
    context: context,
    builder: (ctx) => StatefulBuilder(
      builder: (context, setState) => AlertDialog(
        title: const Text('修改年龄'),
        content: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              onPressed: () => setState(() => age = (age - 1).clamp(1, 120)),
              icon: const Icon(Icons.remove_circle_outline),
            ),
            Text('$age', style: const TextStyle(fontSize: 24, 
                fontWeight: FontWeight.bold)),
            IconButton(
              onPressed: () => setState(() => age = (age + 1).clamp(1, 120)),
              icon: const Icon(Icons.add_circle_outline),
            ),
          ],
        ),

使用 StatefulBuilder 在对话框内管理状态,clamp 方法限制年龄范围在1-120之间。

保存按钮:

        actions: [
          TextButton(
              onPressed: () => Navigator.pop(ctx), 
              child: const Text('取消')),
          ElevatedButton(
            onPressed: () {
              provider.updateUserAge(age);
              Navigator.pop(ctx);
            },
            child: const Text('保存'),
          ),
        ],
      ),
    ),
  );
}

保存时调用 Provider 的更新方法。

编辑性别对话框

性别使用单选按钮选择:

void _editGender(BuildContext context, AppProvider provider) {
  showDialog(
    context: context,
    builder: (ctx) => AlertDialog(
      title: const Text('选择性别'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            title: const Text('男'),
            leading: Radio<String>(
              value: '男',
              groupValue: provider.userGender,
              onChanged: (v) {
                provider.updateUserGender(v!);
                Navigator.pop(ctx);
              },
            ),
          ),
          ListTile(
            title: const Text('女'),
            leading: Radio<String>(
              value: '女',
              groupValue: provider.userGender,
              onChanged: (v) {
                provider.updateUserGender(v!);
                Navigator.pop(ctx);
              },
            ),
          ),
        ],
      ),
    ),
  );
}

使用 Radio 组件实现单选,选择后立即保存并关闭对话框。

Provider 数据管理

AppProvider 中管理用户信息:

String _userName = '用户';
int _userAge = 25;
String _userGender = '男';

String get userName => _userName;
int get userAge => _userAge;
String get userGender => _userGender;

void updateUserName(String name) {
  _userName = name;
  notifyListeners();
}

void updateUserAge(int age) {
  _userAge = age;
  notifyListeners();
}

void updateUserGender(String gender) {
  _userGender = gender;
  notifyListeners();
}

提供 getter 和 setter 方法,每次更新后通知界面刷新。

头像上传功能

实现头像上传功能:

import 'package:image_picker/image_picker.dart';

Future<void> _pickImage(BuildContext context) async {
  final picker = ImagePicker();
  final source = await showDialog<ImageSource>(
    context: context,
    builder: (ctx) => AlertDialog(
      title: const Text('选择图片来源'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            leading: const Icon(Icons.camera_alt),
            title: const Text('拍照'),
            onTap: () => Navigator.pop(ctx, ImageSource.camera),
          ),
          ListTile(
            leading: const Icon(Icons.photo_library),
            title: const Text('相册'),
            onTap: () => Navigator.pop(ctx, ImageSource.gallery),
          ),
        ],
      ),
    ),
  );
  
  if (source != null) {
    final image = await picker.pickImage(source: source);
    if (image != null) {
      // 上传图片并更新头像
      provider.updateAvatar(image.path);
    }
  }
}

使用 image_picker 插件选择图片,支持拍照和从相册选择。

头像显示

显示用户头像:

Container(
  width: 100,
  height: 100,
  decoration: BoxDecoration(
    shape: BoxShape.circle,
    image: provider.avatarUrl != null
        ? DecorationImage(
            image: NetworkImage(provider.avatarUrl!),
            fit: BoxFit.cover,
          )
        : null,
    color: provider.avatarUrl == null 
        ? const Color(0xFF26A69A).withOpacity(0.1) 
        : null,
  ),
  child: provider.avatarUrl == null
      ? const Icon(Icons.person, size: 50, color: Color(0xFF26A69A))
      : null,
)

有头像时显示图片,没有头像时显示默认图标。

手机号验证

添加手机号验证功能:

void _verifyPhone(BuildContext context) {
  final phoneController = TextEditingController();
  final codeController = TextEditingController();
  
  showDialog(
    context: context,
    builder: (ctx) => AlertDialog(
      title: const Text('绑定手机号'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextField(
            controller: phoneController,
            decoration: const InputDecoration(labelText: '手机号'),
            keyboardType: TextInputType.phone,
          ),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: TextField(
                  controller: codeController,
                  decoration: const InputDecoration(labelText: '验证码'),
                  keyboardType: TextInputType.number,
                ),
              ),
              TextButton(
                onPressed: () {
                  // 发送验证码
                },
                child: const Text('获取验证码'),
              ),
            ],
          ),
        ],
      ),
      actions: [
        TextButton(
            onPressed: () => Navigator.pop(ctx), 
            child: const Text('取消')),
        ElevatedButton(
          onPressed: () {
            // 验证并绑定
          },
          child: const Text('绑定'),
        ),
      ],
    ),
  );
}

手机号绑定需要验证码验证。

数据持久化

使用 SharedPreferences 持久化用户信息:

import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveUserInfo() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('userName', _userName);
  await prefs.setInt('userAge', _userAge);
  await prefs.setString('userGender', _userGender);
}

Future<void> loadUserInfo() async {
  final prefs = await SharedPreferences.getInstance();
  _userName = prefs.getString('userName') ?? '用户';
  _userAge = prefs.getInt('userAge') ?? 25;
  _userGender = prefs.getString('userGender') ?? '男';
  notifyListeners();
}

应用启动时加载用户信息,修改后保存到本地。

退出登录功能

添加退出登录功能:

ElevatedButton(
  onPressed: () {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('确认退出'),
        content: const Text('确定要退出登录吗?'),
        actions: [
          TextButton(
              onPressed: () => Navigator.pop(ctx), 
              child: const Text('取消')),
          ElevatedButton(
            onPressed: () {
              provider.logout();
              Navigator.pop(ctx);
              // 跳转到登录页
            },
            child: const Text('退出'),
          ),
        ],
      ),
    );
  },
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.red,
    foregroundColor: Colors.white,
  ),
  child: const Text('退出登录'),
)

退出前显示确认对话框,避免误操作。

账号注销功能

添加账号注销入口:

TextButton(
  onPressed: () {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('注销账号'),
        content: const Text('注销后所有数据将被删除且无法恢复,确定要注销吗?'),
        actions: [
          TextButton(
              onPressed: () => Navigator.pop(ctx), 
              child: const Text('取消')),
          ElevatedButton(
            onPressed: () {
              // 执行注销
            },
            style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
            child: const Text('确认注销'),
          ),
        ],
      ),
    );
  },
  child: const Text('注销账号', style: TextStyle(color: Colors.red)),
)

账号注销是敏感操作,需要明确提示风险。

总结

本文详细介绍了口腔护理 App 中个人信息功能的实现。通过合理的界面布局和交互设计,我们构建了一个功能完善的个人信息管理页面。核心技术点包括:

  • 使用 ListTile 实现信息列表项
  • 通过 AlertDialog 实现编辑对话框
  • 使用 Radio 组件实现单选
  • 使用 StatefulBuilder 在对话框中管理状态

个人信息页面是用户管理账户的重要入口,希望本文的实现对你有所帮助。

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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐