在这里插入图片描述

回忆列表是记录家庭美好时光的地方,每个回忆都包含标题、日期、描述和相关照片。今天我们来实现这个功能,让用户可以方便地浏览和管理所有回忆。

设计思路

回忆列表采用卡片式布局,每个卡片展示回忆的关键信息。列表按时间倒序排列,最新的回忆显示在最前面。点击卡片可以查看回忆的详细内容。

创建页面结构

先搭建基本的页面框架:

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/memory.dart';
import 'memory_detail_screen.dart';
import 'add_memory_screen.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('回忆列表'),
        elevation: 0,
      ),
      body: Consumer<FamilyProvider>(
        builder: (context, provider, _) {
          final memories = provider.memories;
          
          if (memories.isEmpty) {
            return _buildEmptyState(context);
          }
          
          return ListView.builder(
            padding: EdgeInsets.all(16.w),
            itemCount: memories.length,
            itemBuilder: (context, index) {
              final memory = memories[index];
              return _buildMemoryCard(context, memory);
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (_) => const AddMemoryScreen(),
            ),
          );
        },
        backgroundColor: const Color(0xFFE91E63),
        child: const Icon(Icons.add),
      ),
    );
  }
}

这里用Consumer监听FamilyProvider,当回忆数据变化时页面会自动更新。ListView.builder可以高效地渲染大量回忆卡片。右下角的悬浮按钮用来添加新回忆。

空状态设计

当还没有回忆时,显示一个友好的提示界面:

Widget _buildEmptyState(BuildContext context) {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(
          Icons.photo_library_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],
          ),
        ),
        SizedBox(height: 24.h),
        ElevatedButton.icon(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const AddMemoryScreen(),
              ),
            );
          },
          icon: const Icon(Icons.add),
          label: const Text('添加回忆'),
          style: ElevatedButton.styleFrom(
            backgroundColor: const Color(0xFFE91E63),
            padding: EdgeInsets.symmetric(
              horizontal: 24.w,
              vertical: 12.h,
            ),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(24.r),
            ),
          ),
        ),
      ],
    ),
  );
}

空状态不仅有图标和文字提示,还加了一个按钮,用户可以直接点击开始添加回忆。这样的设计比较友好,不会让用户感到迷茫。

回忆卡片设计

每个回忆用一个精美的卡片来展示:

Widget _buildMemoryCard(BuildContext context, Memory memory) {
  return Card(
    margin: EdgeInsets.only(bottom: 16.h),
    elevation: 2,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(16.r),
    ),
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => MemoryDetailScreen(memory: memory),
          ),
        );
      },
      borderRadius: BorderRadius.circular(16.r),
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildCardHeader(memory),
            if (memory.description.isNotEmpty) ...[
              SizedBox(height: 12.h),
              _buildDescription(memory.description),
            ],
            SizedBox(height: 12.h),
            _buildCardFooter(memory),
          ],
        ),
      ),
    ),
  );
}

卡片用InkWell包装,点击时有水波纹效果。内容分为三部分:头部显示标题和日期,中间显示描述,底部显示照片和家人数量。

卡片头部

头部包含标题和日期标签:

Widget _buildCardHeader(Memory memory) {
  return Row(
    children: [
      Expanded(
        child: Text(
          memory.title,
          style: TextStyle(
            fontSize: 18.sp,
            fontWeight: FontWeight.bold,
            color: Colors.black87,
          ),
          maxLines: 2,
          overflow: TextOverflow.ellipsis,
        ),
      ),
      SizedBox(width: 12.w),
      Container(
        padding: EdgeInsets.symmetric(
          horizontal: 12.w,
          vertical: 6.h,
        ),
        decoration: BoxDecoration(
          color: const Color(0xFFE91E63).withOpacity(0.1),
          borderRadius: BorderRadius.circular(16.r),
        ),
        child: Text(
          DateFormat('MM-dd').format(memory.date),
          style: TextStyle(
            fontSize: 12.sp,
            color: const Color(0xFFE91E63),
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
    ],
  );
}

标题用粗体显示,最多两行,超出部分用省略号。日期用一个圆角标签展示,颜色和主题色保持一致。

描述文本

如果回忆有描述,就显示出来:

Widget _buildDescription(String description) {
  return Text(
    description,
    style: TextStyle(
      fontSize: 14.sp,
      color: Colors.grey[700],
      height: 1.5,
    ),
    maxLines: 3,
    overflow: TextOverflow.ellipsis,
  );
}

描述文字用灰色,行高设为1.5让文字看起来不那么拥挤。最多显示三行,超出的部分用省略号表示。

卡片底部

底部显示照片数量和参与的家人数量:

Widget _buildCardFooter(Memory memory) {
  return Row(
    children: [
      _buildInfoChip(
        icon: Icons.photo_outlined,
        label: '${memory.photoIds.length} 张照片',
        color: const Color(0xFF2196F3),
      ),
      SizedBox(width: 12.w),
      _buildInfoChip(
        icon: Icons.people_outline,
        label: '${memory.memberIds.length} 位家人',
        color: const Color(0xFF4CAF50),
      ),
      const Spacer(),
      Icon(
        Icons.chevron_right,
        color: Colors.grey[400],
        size: 20.sp,
      ),
    ],
  );
}

Widget _buildInfoChip({
  required IconData icon,
  required String label,
  required Color color,
}) {
  return Container(
    padding: EdgeInsets.symmetric(
      horizontal: 10.w,
      vertical: 6.h,
    ),
    decoration: BoxDecoration(
      color: color.withOpacity(0.1),
      borderRadius: BorderRadius.circular(12.r),
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(
          icon,
          size: 14.sp,
          color: color,
        ),
        SizedBox(width: 4.w),
        Text(
          label,
          style: TextStyle(
            fontSize: 12.sp,
            color: color,
            fontWeight: FontWeight.w500,
          ),
        ),
      ],
    ),
  );
}

照片和家人数量用小标签展示,每个标签有图标和文字。不同的信息用不同的颜色,这样一眼就能看出来。右边的箭头提示用户可以点击查看详情。

添加下拉刷新

让用户可以下拉刷新回忆列表:


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('回忆列表'),
      elevation: 0,
    ),
    body: Consumer<FamilyProvider>(
      builder: (context, provider, _) {
        final memories = provider.memories;
        
        if (memories.isEmpty) {
          return _buildEmptyState(context);
        }
        
        return RefreshIndicator(
          onRefresh: () async {
            await provider.refreshMemories();
          },
          color: const Color(0xFFE91E63),
          child: ListView.builder(
            padding: EdgeInsets.all(16.w),
            itemCount: memories.length,
            itemBuilder: (context, index) {
              final memory = memories[index];
              return _buildMemoryCard(context, memory);
            },
          ),
        );
      },
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => const AddMemoryScreen(),
          ),
        );
      },
      backgroundColor: const Color(0xFFE91E63),
      child: const Icon(Icons.add),
    ),
  );
}

RefreshIndicator包装ListView,用户下拉时会触发刷新。刷新指示器的颜色设为主题色,保持视觉统一。

添加筛选功能

在AppBar上添加筛选按钮,让用户可以按条件筛选回忆:


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('回忆列表'),
      elevation: 0,
      actions: [
        IconButton(
          icon: const Icon(Icons.filter_list),
          onPressed: () => _showFilterOptions(context),
        ),
      ],
    ),
    body: Consumer<FamilyProvider>(
      builder: (context, provider, _) {
        final memories = provider.memories;
        
        if (memories.isEmpty) {
          return _buildEmptyState(context);
        }
        
        return RefreshIndicator(
          onRefresh: () async {
            await provider.refreshMemories();
          },
          color: const Color(0xFFE91E63),
          child: ListView.builder(
            padding: EdgeInsets.all(16.w),
            itemCount: memories.length,
            itemBuilder: (context, index) {
              final memory = memories[index];
              return _buildMemoryCard(context, memory);
            },
          ),
        );
      },
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => const AddMemoryScreen(),
          ),
        );
      },
      backgroundColor: const Color(0xFFE91E63),
      child: const Icon(Icons.add),
    ),
  );
}

右上角的筛选按钮点击后会弹出筛选选项,用户可以选择按时间、按家人等条件来筛选回忆。

筛选选项对话框

显示各种筛选条件:

void _showFilterOptions(BuildContext context) {
  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),
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 16.w),
            child: Row(
              children: [
                Text(
                  '筛选条件',
                  style: TextStyle(
                    fontSize: 18.sp,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const Spacer(),
                TextButton(
                  onPressed: () {
                    Navigator.pop(sheetContext);
                    context.read<FamilyProvider>().clearMemoryFilter();
                  },
                  child: const Text('清除'),
                ),
              ],
            ),
          ),
          const Divider(),
          ListTile(
            leading: const Icon(Icons.calendar_today, color: Color(0xFF2196F3)),
            title: const Text('按时间排序'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () {
              Navigator.pop(sheetContext);
              _showSortOptions(context);
            },
          ),
          ListTile(
            leading: const Icon(Icons.people, color: Color(0xFF4CAF50)),
            title: const Text('按家人筛选'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () {
              Navigator.pop(sheetContext);
              _showMemberFilter(context);
            },
          ),
          ListTile(
            leading: const Icon(Icons.photo, color: Color(0xFFFF9800)),
            title: const Text('只看有照片的'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () {
              Navigator.pop(sheetContext);
              context.read<FamilyProvider>().filterMemoriesWithPhotos();
            },
          ),
          SizedBox(height: 16.h),
        ],
      ),
    ),
  );
}

底部菜单提供了三个筛选选项:按时间排序、按家人筛选、只看有照片的回忆。每个选项都有对应的图标和颜色,让界面更加生动。

排序选项

让用户选择按时间升序还是降序:

void _showSortOptions(BuildContext context) {
  showDialog(
    context: context,
    builder: (dialogContext) => AlertDialog(
      title: const Text('排序方式'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          RadioListTile<String>(
            title: const Text('最新在前'),
            value: 'desc',
            groupValue: 'desc',
            onChanged: (value) {
              context.read<FamilyProvider>().sortMemories(descending: true);
              Navigator.pop(dialogContext);
            },
            activeColor: const Color(0xFFE91E63),
          ),
          RadioListTile<String>(
            title: const Text('最早在前'),
            value: 'asc',
            groupValue: 'desc',
            onChanged: (value) {
              context.read<FamilyProvider>().sortMemories(descending: false);
              Navigator.pop(dialogContext);
            },
            activeColor: const Color(0xFFE91E63),
          ),
        ],
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
    ),
  );
}

用RadioListTile实现单选,用户选择后立即应用排序并关闭对话框。

家人筛选

让用户选择要查看哪位家人的回忆:

void _showMemberFilter(BuildContext context) {
  final provider = context.read<FamilyProvider>();
  final members = provider.familyMembers;
  
  showDialog(
    context: context,
    builder: (dialogContext) => AlertDialog(
      title: const Text('选择家人'),
      content: SizedBox(
        width: double.maxFinite,
        child: ListView.builder(
          shrinkWrap: true,
          itemCount: members.length,
          itemBuilder: (context, index) {
            final member = members[index];
            return ListTile(
              leading: CircleAvatar(
                backgroundColor: _getColorForMember(member.avatar),
                child: Text(
                  member.name.substring(0, 1),
                  style: const TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
              title: Text(member.name),
              onTap: () {
                provider.filterMemoriesByMember(member.id);
                Navigator.pop(dialogContext);
              },
            );
          },
        ),
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.r),
      ),
    ),
  );
}

Color _getColorForMember(String avatar) {
  final colors = [
    const Color(0xFFE91E63),
    const Color(0xFF9C27B0),
    const Color(0xFF3F51B5),
    const Color(0xFF2196F3),
    const Color(0xFF009688),
    const Color(0xFF4CAF50),
  ];
  return colors[avatar.hashCode.abs() % colors.length];
}

对话框里显示所有家人的列表,每个家人有头像和名字。用户点击某个家人,就只显示包含这个家人的回忆。

总结

回忆列表页面通过精美的卡片设计展示所有回忆,每个卡片包含标题、日期、描述和统计信息。下拉刷新和筛选功能让用户可以更方便地管理和查找回忆。空状态设计友好,引导用户添加第一个回忆。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐