在这里插入图片描述

前言

收藏功能是内容类应用的标配功能。用户在浏览文章时,可以将感兴趣的内容收藏起来,方便日后查阅。在口腔护理应用中,用户可以收藏有价值的口腔健康知识文章,建立自己的知识库。

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

功能设计

收藏页面需要实现以下功能:

  • 收藏列表:展示所有已收藏的文章
  • 空状态处理:没有收藏时显示友好提示
  • 取消收藏:支持在列表中直接取消收藏
  • 跳转详情:点击文章跳转到详情页

页面基础结构

收藏页面使用 StatelessWidget 实现:

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('我的收藏')),
      body: Consumer<AppProvider>(
        builder: (context, provider, _) {
          final favorites = provider.articles
              .where((a) => a.isFavorite).toList();

使用 Consumer 监听数据变化,通过 where 方法过滤出已收藏的文章。

空状态处理

没有收藏时显示友好的空状态:

          if (favorites.isEmpty) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.favorite_border, size: 80, 
                      color: Colors.grey.shade300),
                  const SizedBox(height: 16),
                  Text('暂无收藏', 
                      style: TextStyle(color: Colors.grey.shade500, fontSize: 16)),
                  const SizedBox(height: 8),
                  Text('去知识库收藏感兴趣的文章吧', 
                      style: TextStyle(color: Colors.grey.shade400)),
                ],
              ),
            );
          }

空状态使用爱心图标和引导文字,提示用户去收藏文章。

收藏列表构建

使用 ListView.builder 构建收藏列表:

          return ListView.builder(
            padding: const EdgeInsets.all(16),
            itemCount: favorites.length,
            itemBuilder: (context, index) {
              final article = favorites[index];
              return GestureDetector(
                onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => ArticleDetailPage(article: article)),
                ),

点击文章卡片跳转到详情页,使用 MaterialPageRoute 进行页面导航。

文章卡片设计

收藏的文章使用卡片形式展示:

                child: Container(
                  margin: const EdgeInsets.only(bottom: 12),
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Row(
                    children: [
                      Container(
                        width: 60,
                        height: 60,
                        decoration: BoxDecoration(
                          color: const Color(0xFF26A69A).withOpacity(0.1),
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: const Icon(Icons.article, 
                            color: Color(0xFF26A69A)),
                      ),

左侧使用方形图标容器,主题色浅色背景配合文章图标。

文章标题和分类标签:

                      const SizedBox(width: 12),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              article.title,
                              style: const TextStyle(fontWeight: FontWeight.bold),
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                            ),
                            const SizedBox(height: 4),
                            Container(
                              padding: const EdgeInsets.symmetric(
                                  horizontal: 6, vertical: 2),
                              decoration: BoxDecoration(
                                color: const Color(0xFF26A69A).withOpacity(0.1),
                                borderRadius: BorderRadius.circular(4),
                              ),
                              child: Text(
                                article.category,
                                style: const TextStyle(
                                    color: Color(0xFF26A69A), fontSize: 11),
                              ),
                            ),
                          ],
                        ),
                      ),

标题最多显示两行,超出部分显示省略号。分类标签使用小型彩色标签形式。

取消收藏按钮:

                      IconButton(
                        icon: const Icon(Icons.favorite, color: Colors.red),
                        onPressed: () => provider.toggleArticleFavorite(article.id),
                      ),
                    ],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

红色实心爱心图标表示已收藏状态,点击可取消收藏。

Provider 收藏管理

AppProvider 中管理收藏状态:

void toggleArticleFavorite(String id) {
  final index = _articles.indexWhere((a) => a.id == id);
  if (index != -1) {
    final old = _articles[index];
    _articles[index] = OralArticle(
      id: old.id,
      title: old.title,
      content: old.content,
      category: old.category,
      publishDate: old.publishDate,
      readCount: old.readCount,
      isFavorite: !old.isFavorite,
    );
    notifyListeners();
  }
}

切换收藏状态后通知界面更新。

收藏数量统计

获取收藏数量:

int get favoriteCount => _articles.where((a) => a.isFavorite).length;

可以在个人中心显示收藏数量。

批量取消收藏

添加批量取消收藏功能:

void clearAllFavorites() {
  for (int i = 0; i < _articles.length; i++) {
    if (_articles[i].isFavorite) {
      final old = _articles[i];
      _articles[i] = OralArticle(
        id: old.id,
        title: old.title,
        content: old.content,
        category: old.category,
        publishDate: old.publishDate,
        readCount: old.readCount,
        isFavorite: false,
      );
    }
  }
  notifyListeners();
}

清空所有收藏。

清空收藏确认

在页面添加清空收藏的入口:

AppBar(
  title: const Text('我的收藏'),
  actions: [
    if (favorites.isNotEmpty)
      IconButton(
        icon: const Icon(Icons.delete_outline),
        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.clearAllFavorites();
                    Navigator.pop(ctx);
                  },
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                  child: const Text('清空'),
                ),
              ],
            ),
          );
        },
      ),
  ],
)

清空前显示确认对话框,避免误操作。

收藏分类筛选

按文章分类筛选收藏:

String _selectedCategory = '全部';

List<OralArticle> get filteredFavorites {
  var result = _articles.where((a) => a.isFavorite).toList();
  if (_selectedCategory != '全部') {
    result = result.where((a) => a.category == _selectedCategory).toList();
  }
  return result;
}

支持按分类查看收藏的文章。

筛选按钮组

添加分类筛选按钮:

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  child: Row(
    children: categories.map((category) => Padding(
      padding: const EdgeInsets.only(right: 8),
      child: FilterChip(
        label: Text(category),
        selected: _selectedCategory == category,
        onSelected: (selected) {
          setState(() => _selectedCategory = category);
        },
        selectedColor: const Color(0xFF26A69A).withOpacity(0.2),
      ),
    )).toList(),
  ),
)

使用 FilterChip 实现分类筛选。

收藏排序功能

支持按收藏时间排序:

List<OralArticle> get sortedFavorites {
  final result = _articles.where((a) => a.isFavorite).toList();
  result.sort((a, b) => b.favoriteDate!.compareTo(a.favoriteDate!));
  return result;
}

最新收藏的文章排在前面。

收藏时间记录

在数据模型中添加收藏时间:

class OralArticle {
  // ...
  final DateTime? favoriteDate;

  OralArticle({
    // ...
    this.favoriteDate,
  });
}

收藏时记录时间,用于排序和显示。

显示收藏时间

在卡片中显示收藏时间:

if (article.favoriteDate != null)
  Text(
    '收藏于 ${DateFormat('MM-dd').format(article.favoriteDate!)}',
    style: TextStyle(color: Colors.grey.shade400, fontSize: 11),
  ),

显示收藏的日期。

滑动删除功能

添加滑动删除收藏:

Dismissible(
  key: Key(article.id),
  direction: DismissDirection.endToStart,
  background: Container(
    color: Colors.red,
    alignment: Alignment.centerRight,
    padding: const EdgeInsets.only(right: 16),
    child: const Icon(Icons.delete, color: Colors.white),
  ),
  onDismissed: (direction) {
    provider.toggleArticleFavorite(article.id);
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: const Text('已取消收藏'),
        action: SnackBarAction(
          label: '撤销',
          onPressed: () {
            provider.toggleArticleFavorite(article.id);
          },
        ),
      ),
    );
  },
  child: _buildArticleCard(article),
)

滑动删除后显示撤销选项。

搜索收藏功能

添加收藏搜索:

TextField(
  decoration: InputDecoration(
    hintText: '搜索收藏',
    prefixIcon: const Icon(Icons.search),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(8),
    ),
    filled: true,
    fillColor: Colors.white,
  ),
  onChanged: (value) {
    setState(() => _searchKeyword = value);
  },
)

List<OralArticle> get searchedFavorites {
  if (_searchKeyword.isEmpty) return favorites;
  return favorites.where((a) => 
    a.title.contains(_searchKeyword)
  ).toList();
}

支持按标题搜索收藏的文章。

收藏导出功能

导出收藏列表:

String exportFavorites() {
  final buffer = StringBuffer();
  buffer.writeln('我的收藏');
  buffer.writeln('导出时间:${DateFormat('yyyy-MM-dd').format(DateTime.now())}');
  buffer.writeln('');
  
  for (var article in favorites) {
    buffer.writeln('标题:${article.title}');
    buffer.writeln('分类:${article.category}');
    buffer.writeln('---');
  }
  
  return buffer.toString();
}

可以将收藏列表导出为文本。

收藏同步功能

如果有账号系统,可以同步收藏:

Future<void> syncFavorites() async {
  // 上传本地收藏到服务器
  final localFavorites = _articles.where((a) => a.isFavorite).toList();
  await api.uploadFavorites(localFavorites);
  
  // 下载服务器收藏到本地
  final remoteFavorites = await api.downloadFavorites();
  // 合并收藏
}

实现多设备收藏同步。

空状态优化

为空状态添加快捷入口:

if (favorites.isEmpty) {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.favorite_border, size: 80, color: Colors.grey.shade300),
        const SizedBox(height: 16),
        Text('暂无收藏', 
            style: TextStyle(color: Colors.grey.shade500, fontSize: 16)),
        const SizedBox(height: 8),
        Text('去知识库收藏感兴趣的文章吧', 
            style: TextStyle(color: Colors.grey.shade400)),
        const SizedBox(height: 16),
        ElevatedButton.icon(
          onPressed: () {
            // 跳转到知识库
          },
          icon: const Icon(Icons.explore),
          label: const Text('去发现'),
        ),
      ],
    ),
  );
}

添加跳转到知识库的快捷按钮。

总结

本文详细介绍了口腔护理 App 中收藏功能的实现。通过合理的界面设计和交互逻辑,我们构建了一个实用的收藏管理页面。核心技术点包括:

  • 使用 where 方法过滤收藏的文章
  • 通过 GestureDetector 实现点击跳转
  • 使用 IconButton 实现取消收藏
  • 友好的空状态处理

收藏功能帮助用户管理感兴趣的内容,是提升用户体验的重要功能。

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

Logo

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

更多推荐