flutter_for_openharmony家庭相册app实战+家人Tab实现

做家庭相册App,家人管理是核心功能之一。
今天来聊聊家人Tab页面的实现,这个页面承载了家庭成员的展示和管理。
整体思路
家人Tab作为底部导航的第二个页面,用户切换过来第一眼看到的就是家庭成员列表。
我希望它能够清晰展示所有家人、提供快捷功能入口、支持添加新成员。
想清楚这些之后,页面的布局也就大致有了方向。
顶部放快捷操作,下面用网格展示家人列表,右下角放添加按钮。
页面框架搭建
先把基本的页面框架搭起来:
class FamilyTab extends StatelessWidget {
const FamilyTab({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('家人'),
actions: [
IconButton(
icon: const Icon(Icons.account_tree),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const FamilyTreeScreen()),
),
),
],
),
这里我用
Scaffold作为页面的基础结构,这是Flutter页面的标准写法。
AppBar里放了标题"家人",简洁明了,用户一眼就知道这是什么页面。右上角放了一个家庭树图标按钮,点击后跳转到家庭树页面,方便用户查看家庭关系结构。
使用CustomScrollView
页面主体部分我用了CustomScrollView配合Sliver系列组件:
body: Consumer<FamilyProvider>(
builder: (context, provider, _) {
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: _buildQuickActions(context),
),
用
Consumer包裹是为了监听FamilyProvider的数据变化,当家人数据更新时页面会自动刷新。
CustomScrollView的好处是整个页面可以统一滚动,不会出现嵌套滚动冲突的问题。
SliverToBoxAdapter可以把普通Widget转换成Sliver,这样就能放进CustomScrollView里了。
家人列表标题
在快捷操作下面放一个标题,显示家人数量:
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Text(
'家庭成员 (${provider.members.length})',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
),
),
标题里显示家人数量,用户不用数就知道有多少人,这是一个小细节但很实用。
字体设成18号加粗,作为区块标题足够醒目。
四周加16的内边距,和其他元素保持一致的间距。
家人网格布局
用SliverGrid展示家人列表:
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 16.w,
crossAxisSpacing: 16.w,
childAspectRatio: 0.8,
),
SliverPadding给网格加上左右内边距,不会贴着屏幕边缘。设置三列布局,每行显示三个家人,在手机屏幕上刚好合适。
childAspectRatio设成0.8让卡片稍微高一点,这样头像和名字都能显示完整。
delegate: SliverChildBuilderDelegate(
(context, index) {
final member = provider.members[index];
return _buildMemberCard(context, member);
},
childCount: provider.members.length,
),
),
),
SliverChildBuilderDelegate按需构建子项,只有可见的卡片才会被创建,性能更好。从
provider.members获取家人数据,遍历构建每个家人卡片。
childCount告诉Flutter总共有多少个子项,这样滚动条才能正确显示。
底部留白
在列表底部留一些空间,避免被悬浮按钮遮挡:
SliverToBoxAdapter(
child: SizedBox(height: 100.h),
),
],
);
},
),
底部留100的高度,确保最后一行家人卡片不会被悬浮按钮挡住。
这是一个容易忽略的细节,但对用户体验影响很大。
悬浮添加按钮
右下角放一个悬浮按钮用来添加家人:
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const AddMemberScreen()),
),
child: const Icon(Icons.person_add),
),
);
}
FloatingActionButton是Material Design的标准组件,用户对这个位置和样式很熟悉。用
person_add图标,一看就知道是添加人员的功能。点击后跳转到添加家人页面,交互很直观。
快捷操作区域
我加了两个快捷入口卡片,分别是家庭分组和家庭回忆:
Widget _buildQuickActions(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16.w),
child: Row(
children: [
Expanded(
child: _buildActionCard(
context,
icon: Icons.groups,
title: '家庭分组',
color: Colors.blue,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GroupListScreen()),
),
),
),
用
Row和Expanded让两个卡片平分宽度,在不同屏幕尺寸下都能自适应。家庭分组用蓝色,可以按小家庭来管理成员。
点击后跳转到分组列表页面。
SizedBox(width: 12.w),
Expanded(
child: _buildActionCard(
context,
icon: Icons.auto_stories,
title: '家庭回忆',
color: Colors.purple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const MemoryListScreen()),
),
),
),
],
),
);
}
中间的
SizedBox控制两个卡片之间的间距,12的宽度刚好合适。家庭回忆用紫色,和分组形成视觉区分。
书本图标暗示"故事"和"回忆"的含义。
快捷卡片组件
把卡片的构建抽成单独的方法,避免重复代码:
Widget _buildActionCard(
BuildContext context, {
required IconData icon,
required String title,
required Color color,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
背景色用主色调的10%透明度,这样既有颜色区分又不会太抢眼。
InkWell包裹让点击有水波纹效果,给用户反馈。圆角设成12,和整体风格统一。
child: Row(
children: [
Icon(icon, color: color, size: 24.sp),
SizedBox(width: 8.w),
Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: color,
),
),
],
),
),
);
}
图标和文字用同一个颜色,保持视觉一致性。
字体稍微加粗一点,让标题更醒目。
图标和文字水平排列,紧凑但不拥挤。
家人卡片设计
家人卡片是这个页面最重要的部分:
Widget _buildMemberCard(BuildContext context, FamilyMember member) {
return InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => MemberDetailScreen(member: member)),
),
child: Column(
children: [
Container(
width: 70.w,
height: 70.w,
decoration: BoxDecoration(
color: _getColorForMember(member.avatar),
shape: BoxShape.circle,
),
点击整个卡片都能跳转到家人详情页,把
member对象传过去。头像用圆形容器,70的尺寸在三列布局下刚好合适。
背景色根据家人动态生成,每个人颜色不同。
child: Center(
child: Text(
member.name.substring(0, 1),
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
头像里显示名字的第一个字,比如"爸"、“妈”,一目了然。
白色文字在彩色背景上很清晰,对比度足够。
字体28号加粗,在70的圆形里大小合适。
SizedBox(height: 8.h),
Text(
member.name,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
头像下面显示完整名字,限制一行超出省略。
字体稍微加粗,和关系文字形成层次。
Text(
member.relationship,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),
],
),
);
}
关系用灰色小字显示,比如"父亲"、“母亲”,作为辅助信息。
12号字体比名字小一号,形成主次关系。
颜色映射方法
颜色根据家人的avatar字段哈希值来选:
Color _getColorForMember(String avatar) {
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[avatar.hashCode % colors.length];
}
}
准备了8种颜色,用哈希值取模来选择,保证同一个人每次打开颜色都固定。
这些颜色都是Material Design的标准色,饱和度适中,作为头像背景很合适。
不同家人大概率颜色不同,视觉上容易区分。
数据流转
家人数据从FamilyProvider获取:
Consumer<FamilyProvider>(
builder: (context, provider, _) {
// 使用 provider.members
},
)
Consumer会在FamilyProvider数据变化时自动重建UI。添加或删除家人后,列表会自动更新,不需要手动刷新。
小结
家人Tab页面的核心就是这些了。
用CustomScrollView统一管理滚动,快捷操作提供常用功能入口。
家人卡片用颜色加首字的方式展示,简洁又有辨识度。
下一篇会写家人详情页的实现。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)