在这里插入图片描述

编辑家人功能允许用户修改已有家庭成员的信息,包括更新姓名、关系、生日、联系方式等。一个好的编辑界面应该预填充现有数据,让用户能够方便地修改需要更新的字段。今天我们来实现这个功能。

设计思路

编辑家人页面与添加家人页面类似,但需要预填充现有数据。用户可以修改任何字段,也可以删除该家人。页面底部提供删除按钮,方便用户管理家庭成员。表单验证确保必填字段不为空。

创建页面结构

先搭建基本框架:

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../providers/family_provider.dart';
import '../models/family_member.dart';

class EditMemberScreen extends StatefulWidget {
  final FamilyMember member;

  const EditMemberScreen({
    super.key,
    required this.member,
  });

  
  State<EditMemberScreen> createState() => _EditMemberScreenState();
}

class _EditMemberScreenState extends State<EditMemberScreen> {
  final _formKey = GlobalKey<FormState>();
  late TextEditingController _nameController;
  late TextEditingController _phoneController;
  late TextEditingController _emailController;
  late TextEditingController _addressController;
  late TextEditingController _notesController;
  late String _selectedRelationship;
  DateTime? _birthday;

  final List<String> _relationships = [
    '父亲', '母亲', '祖父', '祖母', '外祖父', '外祖母',
    '儿子', '女儿', '兄弟', '姐妹', '叔叔', '阿姨',
    '表兄弟', '表姐妹', '配偶', '其他'
  ];

  
  void initState() {
    super.initState();
    _nameController = TextEditingController(text: widget.member.name);
    _phoneController = TextEditingController(text: widget.member.phone ?? '');
    _emailController = TextEditingController(text: widget.member.email ?? '');
    _addressController = TextEditingController(text: widget.member.address ?? '');
    _notesController = TextEditingController(text: widget.member.notes ?? '');
    _selectedRelationship = widget.member.relationship;
    _birthday = widget.member.birthday;
  }

  
  void dispose() {
    _nameController.dispose();
    _phoneController.dispose();
    _emailController.dispose();
    _addressController.dispose();
    _notesController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('编辑家人信息'),
        elevation: 0,
        actions: [
          TextButton(
            onPressed: _saveMember,
            child: Text(
              '保存',
              style: TextStyle(
                color: Colors.white,
                fontSize: 16.sp,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
        ],
      ),
      body: Form(
        key: _formKey,
        child: ListView(
          padding: EdgeInsets.all(16.w),
          children: [
            _buildNameField(),
            SizedBox(height: 16.h),
            _buildRelationshipField(),
            SizedBox(height: 16.h),
            _buildBirthdayField(),
            SizedBox(height: 16.h),
            _buildPhoneField(),
            SizedBox(height: 16.h),
            _buildEmailField(),
            SizedBox(height: 16.h),
            _buildAddressField(),
            SizedBox(height: 16.h),
            _buildNotesField(),
            SizedBox(height: 32.h),
            _buildDeleteButton(),
            SizedBox(height: 24.h),
          ],
        ),
      ),
    );
  }
}

initState中使用现有数据初始化所有Controller和状态变量,这样用户打开页面时就能看到当前的信息。dispose方法里记得释放所有Controller资源。

姓名输入框

姓名是必填字段:

Widget _buildNameField() {
  return TextFormField(
    controller: _nameController,
    decoration: InputDecoration(
      labelText: '姓名',
      hintText: '请输入姓名',
      prefixIcon: const Icon(Icons.person, color: Color(0xFFE91E63)),
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: BorderSide(color: Colors.grey[300]!),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: const BorderSide(
          color: Color(0xFFE91E63),
          width: 2,
        ),
      ),
      filled: true,
      fillColor: Colors.grey[50],
    ),
    validator: (value) {
      if (value == null || value.isEmpty) {
        return '请输入姓名';
      }
      if (value.length < 2) {
        return '姓名至少2个字符';
      }
      return null;
    },
    textInputAction: TextInputAction.next,
  );
}

输入框用圆角边框,聚焦时边框变成粉色。prefixIcon显示人物图标,验证器检查姓名不能为空且至少2个字符。

关系选择器

用下拉菜单选择关系:

Widget _buildRelationshipField() {
  return DropdownButtonFormField<String>(
    value: _selectedRelationship,
    decoration: InputDecoration(
      labelText: '关系',
      prefixIcon: const Icon(Icons.family_restroom, color: Color(0xFFE91E63)),
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: BorderSide(color: Colors.grey[300]!),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: const BorderSide(
          color: Color(0xFFE91E63),
          width: 2,
        ),
      ),
      filled: true,
      fillColor: Colors.grey[50],
    ),
    items: _relationships.map((r) {
      return DropdownMenuItem(
        value: r,
        child: Text(r),
      );
    }).toList(),
    onChanged: (value) {
      setState(() {
        _selectedRelationship = value!;
      });
    },
    validator: (value) {
      if (value == null || value.isEmpty) {
        return '请选择关系';
      }
      return null;
    },
  );
}

下拉菜单提供多种关系选项,用户可以方便地选择。选择后更新状态,验证器确保用户选择了关系。

生日选择器

用日期选择器选择生日:

Widget _buildBirthdayField() {
  return Container(
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
      border: Border.all(color: Colors.grey[300]!),
    ),
    child: ListTile(
      leading: Container(
        padding: EdgeInsets.all(8.w),
        decoration: BoxDecoration(
          color: const Color(0xFFE91E63).withOpacity(0.1),
          borderRadius: BorderRadius.circular(8.r),
        ),
        child: const Icon(
          Icons.cake,
          color: Color(0xFFE91E63),
        ),
      ),
      title: const Text(
        '生日',
        style: TextStyle(fontWeight: FontWeight.w500),
      ),
      subtitle: Text(
        _birthday != null
            ? DateFormat('yyyy年MM月dd日').format(_birthday!)
            : '未设置',
        style: TextStyle(
          color: _birthday != null
              ? const Color(0xFFE91E63)
              : Colors.grey[600],
          fontSize: 14.sp,
        ),
      ),
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (_birthday != null)
            IconButton(
              icon: const Icon(Icons.clear, size: 20),
              onPressed: () {
                setState(() {
                  _birthday = null;
                });
              },
            ),
          const Icon(Icons.chevron_right, color: Colors.grey),
        ],
      ),
      onTap: _selectBirthday,
    ),
  );
}

Future<void> _selectBirthday() async {
  final date = await showDatePicker(
    context: context,
    initialDate: _birthday ?? DateTime.now(),
    firstDate: DateTime(1900),
    lastDate: DateTime.now(),
    locale: const Locale('zh', 'CN'),
    builder: (context, child) {
      return Theme(
        data: Theme.of(context).copyWith(
          colorScheme: const ColorScheme.light(
            primary: Color(0xFFE91E63),
            onPrimary: Colors.white,
            surface: Colors.white,
            onSurface: Colors.black,
          ),
        ),
        child: child!,
      );
    },
  );
  
  if (date != null) {
    setState(() {
      _birthday = date;
    });
  }
}

生日选择器用ListTile包装在圆角容器里,左边有蛋糕图标。如果已设置生日,右边显示清除按钮。日期选择器用主题色包装,保持视觉一致。

电话输入框

电话是可选字段:

Widget _buildPhoneField() {
  return TextFormField(
    controller: _phoneController,
    decoration: InputDecoration(
      labelText: '电话(可选)',
      hintText: '请输入电话号码',
      prefixIcon: const Icon(Icons.phone, color: Color(0xFFE91E63)),
      suffixIcon: _phoneController.text.isNotEmpty
          ? IconButton(
              icon: const Icon(Icons.clear, size: 20),
              onPressed: () {
                setState(() {
                  _phoneController.clear();
                });
              },
            )
          : null,
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: BorderSide(color: Colors.grey[300]!),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: const BorderSide(
          color: Color(0xFFE91E63),
          width: 2,
        ),
      ),
      filled: true,
      fillColor: Colors.grey[50],
    ),
    keyboardType: TextInputType.phone,
    textInputAction: TextInputAction.next,
  );
}

电话输入框用phone键盘类型,方便用户输入。如果有内容,右边显示清除按钮。

邮箱输入框

邮箱也是可选字段:

Widget _buildEmailField() {
  return TextFormField(
    controller: _emailController,
    decoration: InputDecoration(
      labelText: '邮箱(可选)',
      hintText: '请输入邮箱地址',
      prefixIcon: const Icon(Icons.email, color: Color(0xFFE91E63)),
      suffixIcon: _emailController.text.isNotEmpty
          ? IconButton(
              icon: const Icon(Icons.clear, size: 20),
              onPressed: () {
                setState(() {
                  _emailController.clear();
                });
              },
            )
          : null,
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: BorderSide(color: Colors.grey[300]!),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: const BorderSide(
          color: Color(0xFFE91E63),
          width: 2,
        ),
      ),
      filled: true,
      fillColor: Colors.grey[50],
    ),
    keyboardType: TextInputType.emailAddress,
    textInputAction: TextInputAction.next,
    validator: (value) {
      if (value != null && value.isNotEmpty) {
        if (!value.contains('@')) {
          return '请输入有效的邮箱地址';
        }
      }
      return null;
    },
  );
}

邮箱输入框用emailAddress键盘类型。验证器检查邮箱格式,但只在用户输入了内容时才验证。

地址输入框

地址输入框:

Widget _buildAddressField() {
  return TextFormField(
    controller: _addressController,
    decoration: InputDecoration(
      labelText: '地址(可选)',
      hintText: '请输入地址',
      prefixIcon: const Icon(Icons.location_on, color: Color(0xFFE91E63)),
      suffixIcon: _addressController.text.isNotEmpty
          ? IconButton(
              icon: const Icon(Icons.clear, size: 20),
              onPressed: () {
                setState(() {
                  _addressController.clear();
                });
              },
            )
          : null,
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: BorderSide(color: Colors.grey[300]!),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: const BorderSide(
          color: Color(0xFFE91E63),
          width: 2,
        ),
      ),
      filled: true,
      fillColor: Colors.grey[50],
    ),
    textInputAction: TextInputAction.next,
  );
}

地址输入框用定位图标,如果有内容显示清除按钮。

备注输入框

备注用多行输入框:

Widget _buildNotesField() {
  return TextFormField(
    controller: _notesController,
    decoration: InputDecoration(
      labelText: '备注(可选)',
      hintText: '添加一些备注信息',
      alignLabelWithHint: true,
      prefixIcon: Padding(
        padding: EdgeInsets.only(bottom: 60.h),
        child: const Icon(Icons.note, color: Color(0xFFE91E63)),
      ),
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: BorderSide(color: Colors.grey[300]!),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12.r),
        borderSide: const BorderSide(
          color: Color(0xFFE91E63),
          width: 2,
        ),
      ),
      filled: true,
      fillColor: Colors.grey[50],
    ),
    maxLines: 3,
    minLines: 3,
    textInputAction: TextInputAction.done,
  );
}

备注输入框用3行高度,图标用Padding调整位置让它在顶部对齐。

删除按钮

页面底部的删除按钮:

Widget _buildDeleteButton() {
  return OutlinedButton.icon(
    onPressed: () => _showDeleteDialog(),
    icon: const Icon(Icons.delete_forever),
    label: const Text('删除此家人'),
    style: OutlinedButton.styleFrom(
      foregroundColor: const Color(0xFFF44336),
      side: const BorderSide(
        color: Color(0xFFF44336),
        width: 2,
      ),
      padding: EdgeInsets.symmetric(vertical: 16.h),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
    ),
  );
}

删除按钮用红色边框和文字,表示这是危险操作。按钮有图标和文字,点击后显示确认对话框。

保存更新

验证表单并保存更新:

void _saveMember() {
  if (_formKey.currentState!.validate()) {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (_) => const Center(
        child: CircularProgressIndicator(
          color: Color(0xFFE91E63),
        ),
      ),
    );

    final updatedMember = widget.member.copyWith(
      name: _nameController.text.trim(),
      relationship: _selectedRelationship,
      birthday: _birthday,
      phone: _phoneController.text.trim().isNotEmpty
          ? _phoneController.text.trim()
          : null,
      email: _emailController.text.trim().isNotEmpty
          ? _emailController.text.trim()
          : null,
      address: _addressController.text.trim().isNotEmpty
          ? _addressController.text.trim()
          : null,
      notes: _notesController.text.trim().isNotEmpty
          ? _notesController.text.trim()
          : null,
    );

    Future.delayed(const Duration(milliseconds: 500), () {
      context.read<FamilyProvider>().updateMember(updatedMember);
      
      Navigator.pop(context);
      Navigator.pop(context);
      
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Row(
            children: [
              const Icon(Icons.check_circle, color: Colors.white),
              SizedBox(width: 8.w),
              const Text('信息更新成功'),
            ],
          ),
          backgroundColor: const Color(0xFF4CAF50),
          behavior: SnackBarBehavior.floating,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(8.r),
          ),
          margin: EdgeInsets.all(16.w),
          duration: const Duration(seconds: 2),
        ),
      );
    });
  }
}

保存前先验证表单,验证通过后显示加载提示。用copyWith方法创建新对象,只更新修改的字段。空字符串转换为null。通过Provider更新数据,关闭页面并显示成功提示。

删除确认对话框

删除前需要用户确认:

void _showDeleteDialog() {
  showDialog(
    context: context,
    builder: (dialogContext) => AlertDialog(
      title: Row(
        children: [
          const Icon(
            Icons.warning_amber_rounded,
            color: Color(0xFFF44336),
          ),
          SizedBox(width: 8.w),
          const Text('删除家人'),
        ],
      ),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '确定要删除"${widget.member.name}"吗?',
            style: TextStyle(fontSize: 15.sp),
          ),
          SizedBox(height: 8.h),
          Text(
            '删除后将无法恢复,相关的照片和回忆不会被删除。',
            style: TextStyle(
              fontSize: 13.sp,
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16.r),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(dialogContext),
          child: Text(
            '取消',
            style: TextStyle(
              color: Colors.grey[600],
              fontSize: 15.sp,
            ),
          ),
        ),
        TextButton(
          onPressed: () {
            context.read<FamilyProvider>().deleteMember(widget.member.id);
            
            Navigator.pop(dialogContext);
            Navigator.pop(context);
            Navigator.pop(context);
            
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('已删除"${widget.member.name}"'),
                behavior: SnackBarBehavior.floating,
                backgroundColor: const Color(0xFFF44336),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8.r),
                ),
                margin: EdgeInsets.all(16.w),
              ),
            );
          },
          child: Text(
            '删除',
            style: TextStyle(
              color: const Color(0xFFF44336),
              fontSize: 15.sp,
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ],
    ),
  );
}

删除对话框有警告图标,内容说明删除后无法恢复但相关数据不会被删除。删除按钮用红色表示危险操作。删除后连续pop三次,关闭对话框、编辑页面和详情页面,返回列表页。

总结

编辑家人页面通过预填充数据让用户能够方便地修改信息。所有输入框都有清除按钮,方便用户快速清空。表单验证确保必填字段不为空,邮箱格式正确。copyWith方法确保只更新修改的字段。删除功能提供了完整的家人管理能力,删除前有详细的确认提示。整个页面交互流畅,用户体验良好。

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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐