flutter_for_openharmony家庭相册app实战+家庭分组实现
本文介绍了Flutter家庭分组功能的实现方案。主要内容包括: 整体思路:采用分组列表形式,支持查看、添加和编辑分组 页面框架:使用Consumer监听数据变化,ListView.builder渲染分组列表 空状态设计:使用图标和提示文字引导用户创建分组 分组卡片:包含图标、名称和成员数量,提供点击效果 个性化设计:根据分组名称自动匹配图标和颜色 实现要点: 采用响应式设计适配不同屏幕 使用Ink

家庭分组功能可以让用户按照不同的关系或类别来管理家庭成员,比如父母、子女、祖辈等。这样分类之后,查看和管理起来会方便很多。今天我们就来实现这个功能。
整体思路
分组页面其实就是一个列表,每个分组显示组名和成员数量。用户点击某个分组,就能看到这个组里的所有成员。另外还要支持添加新分组和编辑现有分组。
创建分组页面
先来搭建页面的基本框架:
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import '../providers/family_provider.dart';
import '../models/family_group.dart';
import 'group_members_screen.dart';
class FamilyGroupScreen extends StatelessWidget {
const FamilyGroupScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('家庭分组'),
elevation: 0,
),
body: Consumer<FamilyProvider>(
builder: (context, provider, _) {
final groups = provider.getFamilyGroups();
if (groups.isEmpty) {
return _buildEmptyState();
}
return ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: groups.length,
itemBuilder: (context, index) {
final group = groups[index];
return _buildGroupCard(context, group, provider);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddGroupDialog(context),
backgroundColor: const Color(0xFFE91E63),
child: const Icon(Icons.add),
),
);
}
}
这里用Consumer来监听FamilyProvider的变化,当分组数据更新时页面会自动刷新。如果还没有分组,就显示一个空状态提示。ListView.builder用来展示分组列表,这样可以高效地渲染大量数据。
空状态提示
当用户还没创建任何分组时,显示一个友好的提示:
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.group_outlined,
size: 80.sp,
color: Colors.grey[300],
),
SizedBox(height: 20.h),
Text(
'还没有分组',
style: TextStyle(
fontSize: 18.sp,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 8.h),
Text(
'点击右下角按钮创建第一个分组',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[400],
),
),
],
),
);
}
空状态用了一个大图标加上两行文字,这样看起来不会太单调。第一行文字稍微大一点,第二行是提示操作的小字。颜色用灰色系,不会太突兀。
分组卡片设计
每个分组用一个卡片来展示,包含图标、名称、成员数量:
Widget _buildGroupCard(BuildContext context, FamilyGroup group, FamilyProvider provider) {
final members = provider.getMembersByIds(group.memberIds);
final color = _getColorForGroup(group.name);
return Card(
margin: EdgeInsets.only(bottom: 12.h),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GroupMembersScreen(group: group),
),
);
},
borderRadius: BorderRadius.circular(12.r),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Row(
children: [
Container(
width: 56.w,
height: 56.w,
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
_getIconForGroup(group.name),
color: color,
size: 28.sp,
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
group.name,
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
SizedBox(height: 4.h),
Text(
'${members.length} 位成员',
style: TextStyle(
fontSize: 13.sp,
color: Colors.grey[600],
),
),
],
),
),
Icon(
Icons.chevron_right,
color: Colors.grey[400],
size: 24.sp,
),
],
),
),
),
);
}
卡片左边是一个圆角方形的图标容器,用浅色背景配上深色图标。中间是分组名称和成员数量,右边是一个箭头表示可以点击进入。InkWell提供了点击水波纹效果,用户体验会更好。
图标和颜色处理
根据分组名称返回对应的图标和颜色:
IconData _getIconForGroup(String name) {
if (name.contains('父母') || name.contains('爸妈')) {
return Icons.people;
} else if (name.contains('子女') || name.contains('孩子')) {
return Icons.child_care;
} else if (name.contains('祖') || name.contains('爷爷') || name.contains('奶奶')) {
return Icons.elderly;
} else if (name.contains('兄弟姐妹')) {
return Icons.group;
}
return Icons.folder_shared;
}
Color _getColorForGroup(String name) {
final colors = [
const Color(0xFFE91E63), // 粉红
const Color(0xFF9C27B0), // 紫色
const Color(0xFF3F51B5), // 靛蓝
const Color(0xFF2196F3), // 蓝色
const Color(0xFF009688), // 青色
const Color(0xFF4CAF50), // 绿色
const Color(0xFFFF9800), // 橙色
const Color(0xFFFF5722), // 深橙
];
return colors[name.hashCode.abs() % colors.length];
}
这里用了一些简单的关键词匹配来选择图标,比如包含"父母"就用people图标。颜色则是用名称的哈希值来选择,这样同一个分组每次显示的颜色都是一样的。
添加分组对话框
点击右下角的加号按钮,弹出对话框让用户输入分组名称:
void _showAddGroupDialog(BuildContext context) {
final nameController = TextEditingController();
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('创建分组'),
content: TextField(
controller: nameController,
decoration: InputDecoration(
hintText: '请输入分组名称',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
),
autofocus: true,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
_createGroup(context, value.trim());
Navigator.pop(dialogContext);
}
},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(
'取消',
style: TextStyle(
color: Colors.grey[600],
fontSize: 15.sp,
),
),
),
TextButton(
onPressed: () {
final name = nameController.text.trim();
if (name.isNotEmpty) {
_createGroup(context, name);
Navigator.pop(dialogContext);
}
},
child: Text(
'创建',
style: TextStyle(
color: const Color(0xFFE91E63),
fontSize: 15.sp,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
对话框里就一个输入框,用户输入分组名称后点击创建按钮。autofocus设为true,对话框一弹出就自动聚焦到输入框,用户可以直接输入。onSubmitted处理回车键,这样用户输入完直接按回车就能创建。
创建分组逻辑
把新分组添加到Provider中:
void _createGroup(BuildContext context, String name) {
final group = FamilyGroup(
id: DateTime.now().millisecondsSinceEpoch.toString(),
name: name,
memberIds: [],
createdAt: DateTime.now(),
);
context.read<FamilyProvider>().addGroup(group);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('分组"$name"创建成功'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
margin: EdgeInsets.all(16.w),
),
);
}
创建分组时用当前时间戳作为ID,这样可以保证唯一性。memberIds初始化为空数组,用户后面可以往里面添加成员。创建成功后显示一个SnackBar提示,用户能清楚地知道操作成功了。
长按编辑功能
给卡片添加长按菜单,支持重命名和删除:
Widget _buildGroupCard(BuildContext context, FamilyGroup group, FamilyProvider provider) {
final members = provider.getMembersByIds(group.memberIds);
final color = _getColorForGroup(group.name);
return Card(
margin: EdgeInsets.only(bottom: 12.h),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GroupMembersScreen(group: group),
),
);
},
onLongPress: () => _showGroupOptions(context, group, provider),
borderRadius: BorderRadius.circular(12.r),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Row(
children: [
Container(
width: 56.w,
height: 56.w,
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
_getIconForGroup(group.name),
color: color,
size: 28.sp,
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
group.name,
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
SizedBox(height: 4.h),
Text(
'${members.length} 位成员',
style: TextStyle(
fontSize: 13.sp,
color: Colors.grey[600],
),
),
],
),
),
Icon(
Icons.chevron_right,
color: Colors.grey[400],
size: 24.sp,
),
],
),
),
),
);
}
onLongPress回调会在用户长按卡片时触发,弹出一个底部菜单显示可用的操作选项。
分组操作菜单
显示重命名和删除选项:
void _showGroupOptions(BuildContext context, FamilyGroup group, FamilyProvider provider) {
showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)),
),
builder: (sheetContext) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 8.h),
Container(
width: 40.w,
height: 4.h,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2.r),
),
),
SizedBox(height: 16.h),
ListTile(
leading: const Icon(Icons.edit, color: Color(0xFF2196F3)),
title: const Text('重命名'),
onTap: () {
Navigator.pop(sheetContext);
_showRenameDialog(context, group);
},
),
ListTile(
leading: const Icon(Icons.delete, color: Color(0xFFF44336)),
title: const Text('删除分组'),
onTap: () {
Navigator.pop(sheetContext);
_showDeleteConfirmDialog(context, group, provider);
},
),
SizedBox(height: 8.h),
],
),
),
);
}
底部菜单用ModalBottomSheet实现,顶部有个小横条表示可以下拉关闭。两个选项分别是重命名和删除,用不同的颜色图标来区分。
重命名对话框
让用户输入新的分组名称:
void _showRenameDialog(BuildContext context, FamilyGroup group) {
final nameController = TextEditingController(text: group.name);
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('重命名分组'),
content: TextField(
controller: nameController,
decoration: InputDecoration(
hintText: '请输入新名称',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
),
autofocus: true,
textInputAction: TextInputAction.done,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(
'取消',
style: TextStyle(color: Colors.grey[600]),
),
),
TextButton(
onPressed: () {
final newName = nameController.text.trim();
if (newName.isNotEmpty && newName != group.name) {
final updatedGroup = group.copyWith(name: newName);
context.read<FamilyProvider>().updateGroup(updatedGroup);
Navigator.pop(dialogContext);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('重命名成功'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
},
child: const Text(
'确定',
style: TextStyle(
color: Color(0xFFE91E63),
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
重命名对话框和创建对话框很像,只是输入框里预填了当前的名称。用户修改后点确定,就会更新分组信息。
删除确认对话框
删除是危险操作,需要二次确认:
void _showDeleteConfirmDialog(
BuildContext context,
FamilyGroup group,
FamilyProvider provider,
) {
final members = provider.getMembersByIds(group.memberIds);
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('删除分组'),
content: Text(
members.isEmpty
? '确定要删除"${group.name}"吗?'
: '分组"${group.name}"中还有${members.length}位成员,删除后成员不会被删除,只是移出分组。确定继续吗?',
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(
'取消',
style: TextStyle(color: Colors.grey[600]),
),
),
TextButton(
onPressed: () {
provider.deleteGroup(group.id);
Navigator.pop(dialogContext);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已删除"${group.name}"'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text(
'删除',
style: TextStyle(
color: Color(0xFFF44336),
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
如果分组里还有成员,对话框会特别提示一下,让用户知道删除分组不会删除成员。这样可以避免用户误操作。
总结
家庭分组功能通过卡片列表的形式展示所有分组,每个分组有独特的颜色和图标。用户可以轻松创建、重命名和删除分组,长按卡片就能看到更多操作选项。整个交互流程比较流畅,符合用户的使用习惯。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)