Flutter for OpenHarmony 微动漫App实战:标签筛选功能实现

通过网盘分享的文件:flutter1.zip

链接: https://pan.baidu.com/s/1jkLZ9mZXjNm0LgP6FTVRzw 提取码: 2t97

标签筛选是内容类App的常见功能,让用户快速切换不同类型的内容。微动漫App的发现页面用标签筛选热门、当季、即将上映、随机推荐等不同类型的动漫。

这篇文章会实现标签筛选功能,讲解 FilterChip 组件的使用、横向滚动列表、筛选状态管理,以及如何让筛选和内容加载联动。


标签筛选的设计思路

标签筛选通常放在页面顶部,横向排列,可以滚动。点击标签切换选中状态,同时加载对应的内容。

视觉设计:选中的标签要有明显的区分,通常用颜色或背景。

交互设计:点击即切换,不需要确认按钮。

状态管理:记录当前选中的标签,切换时重新加载数据。


页面状态定义

dart 复制代码
import 'package:flutter/material.dart';
import '../services/api_service.dart';
import '../models/anime.dart';
import '../widgets/anime_card.dart';
import '../widgets/shimmer_loading.dart';

class ExploreScreen extends StatefulWidget {
  const ExploreScreen({super.key});

  @override
  State<ExploreScreen> createState() => _ExploreScreenState();
}

class _ExploreScreenState extends State<ExploreScreen> {
  int _selectedFilter = 0;
  List<Anime> _animes = [];
  bool _isLoading = true;
  int _currentPage = 1;
  final ScrollController _scrollController = ScrollController();
  bool _showBackToTop = false;

  final List<String> _filters = [
    '热门',
    '当季',
    '即将',
    '随机',
  ];

_selectedFilter 记录当前选中的标签索引,默认选中第一个。

_filters 是标签列表,定义了所有可选的筛选项。

_animes 存储当前筛选条件下的动漫列表。

_isLoading 标记加载状态。


标签列表布局

dart 复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('发现')),
    body: Column(
      children: [
        SizedBox(
          height: 50,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            padding: const EdgeInsets.symmetric(horizontal: 8),
            itemCount: _filters.length,
            itemBuilder: (_, i) => Padding(
              padding: const EdgeInsets.symmetric(horizontal: 4),
              child: FilterChip(
                label: Text(_filters[i]),
                selected: _selectedFilter == i,
                onSelected: (selected) {
                  setState(() {
                    _selectedFilter = i;
                    _currentPage = 1;
                  });
                  _loadAnimes();
                },
              ),
            ),
          ),
        ),
        Expanded(
          child: // 内容区域
        ),
      ],
    ),
  );
}

Column 垂直排列标签栏和内容区域。

SizedBox 固定标签栏高度为 50。

ListView.builder 横向滚动,scrollDirection: Axis.horizontal

Expanded 让内容区域占满剩余空间。


FilterChip 组件详解

dart 复制代码
FilterChip(
  label: Text(_filters[i]),
  selected: _selectedFilter == i,
  onSelected: (selected) {
    setState(() {
      _selectedFilter = i;
      _currentPage = 1;
    });
    _loadAnimes();
  },
)

FilterChip 是 Material Design 的筛选标签组件。

label 是标签文本。

selected 控制是否选中,选中时会有不同的样式。

onSelected 在点击时触发,参数 selected 是新的选中状态。


FilterChip vs ChoiceChip vs Chip

Flutter 提供了几种 Chip 组件:

Chip:基础标签,可以有删除按钮。

FilterChip:筛选标签,有选中/未选中状态,可以多选。

ChoiceChip:选择标签,有选中/未选中状态,通常单选。

ActionChip:动作标签,点击触发动作,没有选中状态。

InputChip:输入标签,可以有头像和删除按钮。

对于单选筛选,FilterChip 和 ChoiceChip 都可以,视觉效果略有不同。


自定义 FilterChip 样式

dart 复制代码
FilterChip(
  label: Text(_filters[i]),
  selected: _selectedFilter == i,
  selectedColor: Theme.of(context).primaryColor.withOpacity(0.2),
  checkmarkColor: Theme.of(context).primaryColor,
  labelStyle: TextStyle(
    color: _selectedFilter == i
        ? Theme.of(context).primaryColor
        : Colors.grey[700],
  ),
  onSelected: (selected) {
    // 处理选中
  },
)

selectedColor 是选中时的背景色。

checkmarkColor 是选中时勾选图标的颜色。

labelStyle 可以根据选中状态设置不同的文字样式。


隐藏勾选图标

FilterChip 默认选中时显示勾选图标,可以隐藏:

dart 复制代码
FilterChip(
  label: Text(_filters[i]),
  selected: _selectedFilter == i,
  showCheckmark: false,
  onSelected: (selected) {
    // 处理选中
  },
)

showCheckmark: false 隐藏勾选图标,只用颜色区分选中状态。


数据加载逻辑

dart 复制代码
Future<void> _loadAnimes() async {
  setState(() => _isLoading = true);
  try {
    List<Anime> animes = [];
    switch (_selectedFilter) {
      case 0:
        animes = await ApiService.getTopAnime(page: _currentPage);
        break;
      case 1:
        animes = await ApiService.getSeasonalAnime(page: _currentPage);
        break;
      case 2:
        animes = await ApiService.getUpcomingAnime(page: _currentPage);
        break;
      case 3:
        final random = await ApiService.getRandomAnime();
        animes = random != null ? [random] : [];
        break;
    }
    setState(() {
      _animes = animes;
      _isLoading = false;
    });
  } catch (e) {
    setState(() => _isLoading = false);
  }
}

根据 _selectedFilter 调用不同的 API。

switch 语句处理不同的筛选条件。

加载前设置 _isLoading = true,加载完成后设置为 false。


内容区域

dart 复制代码
Expanded(
  child: _isLoading
      ? const ShimmerLoading(itemCount: 8, isGrid: true)
      : GridView.builder(
          controller: _scrollController,
          padding: const EdgeInsets.all(12),
          gridDelegate:
              const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            childAspectRatio: 0.7,
            crossAxisSpacing: 12,
            mainAxisSpacing: 12,
          ),
          itemCount: _animes.length,
          itemBuilder: (_, i) => AnimeCard(anime: _animes[i]),
        ),
),

加载中显示骨架屏,加载完成显示网格。

GridView.builder 展示动漫卡片,2 列布局。


回到顶部按钮

切换筛选后内容会变,可能需要回到顶部:

dart 复制代码
@override
void initState() {
  super.initState();
  _loadAnimes();
  _scrollController.addListener(_onScroll);
}

void _onScroll() {
  if (_scrollController.offset > 300 && !_showBackToTop) {
    setState(() => _showBackToTop = true);
  } else if (_scrollController.offset <= 300 && _showBackToTop) {
    setState(() => _showBackToTop = false);
  }
}

void _scrollToTop() {
  _scrollController.animateTo(
    0,
    duration: const Duration(milliseconds: 300),
    curve: Curves.easeOut,
  );
}

监听滚动位置,超过 300 像素时显示回到顶部按钮。

animateTo 平滑滚动到顶部。

dart 复制代码
floatingActionButton: _showBackToTop
    ? FloatingActionButton(
        mini: true,
        onPressed: _scrollToTop,
        child: const Icon(Icons.arrow_upward),
      )
    : null,

mini: true 使用小尺寸的 FAB。


多选筛选

如果需要多选,可以用 Set 存储选中的标签:

dart 复制代码
Set<int> _selectedFilters = {0};

FilterChip(
  label: Text(_filters[i]),
  selected: _selectedFilters.contains(i),
  onSelected: (selected) {
    setState(() {
      if (selected) {
        _selectedFilters.add(i);
      } else {
        _selectedFilters.remove(i);
      }
    });
    _loadAnimes();
  },
)

Set 自动去重,适合存储多选状态。

contains 检查是否选中,addremove 切换状态。


标签带图标

可以给标签加图标:

dart 复制代码
FilterChip(
  avatar: Icon(
    _getFilterIcon(i),
    size: 18,
  ),
  label: Text(_filters[i]),
  selected: _selectedFilter == i,
  onSelected: (selected) {
    // 处理选中
  },
)

IconData _getFilterIcon(int index) {
  switch (index) {
    case 0:
      return Icons.trending_up;
    case 1:
      return Icons.calendar_today;
    case 2:
      return Icons.upcoming;
    case 3:
      return Icons.shuffle;
    default:
      return Icons.filter_list;
  }
}

avatar 属性可以放图标或图片,显示在标签文本前面。


标签带数量

可以显示每个筛选条件的数量:

dart 复制代码
FilterChip(
  label: Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      Text(_filters[i]),
      if (_filterCounts[i] > 0) ...[
        const SizedBox(width: 4),
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
          decoration: BoxDecoration(
            color: Colors.grey[200],
            borderRadius: BorderRadius.circular(10),
          ),
          child: Text(
            '${_filterCounts[i]}',
            style: const TextStyle(fontSize: 10),
          ),
        ),
      ],
    ],
  ),
  selected: _selectedFilter == i,
  onSelected: (selected) {
    // 处理选中
  },
)

在标签文本后面加一个小圆角矩形显示数量。


动画效果

切换筛选时可以加动画:

dart 复制代码
AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  child: _isLoading
      ? const ShimmerLoading(key: ValueKey('loading'))
      : GridView.builder(
          key: ValueKey(_selectedFilter),
          // 其他配置
        ),
)

AnimatedSwitcher 在子组件切换时添加淡入淡出动画。

key 必须不同,AnimatedSwitcher 才知道是不同的组件。


深色模式适配

FilterChip 会自动适配深色模式,但可以进一步定制:

dart 复制代码
FilterChip(
  label: Text(_filters[i]),
  selected: _selectedFilter == i,
  backgroundColor: Theme.of(context).brightness == Brightness.dark
      ? Colors.grey[800]
      : Colors.grey[200],
  selectedColor: Theme.of(context).primaryColor.withOpacity(0.3),
  onSelected: (selected) {
    // 处理选中
  },
)

根据主题设置不同的背景色。


小结

标签筛选功能涉及的技术点:FilterChip 组件横向 ListView状态管理switch 条件分支ScrollController 滚动监听FloatingActionButton 回到顶部

FilterChip 是实现筛选标签的标准组件,提供了选中/未选中状态、自定义样式、图标支持等功能。

筛选和内容加载的联动是关键:切换筛选 → 更新状态 → 重新加载数据 → 更新 UI。

好的筛选设计能帮助用户快速找到想要的内容,是内容类 App 的重要功能。


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

相关推荐
mjhcsp1 小时前
如何做一个网站?
android
2501_915909061 小时前
在无需越狱的前提下如何对 iOS 设备进行文件管理与数据导出
android·macos·ios·小程序·uni-app·cocoa·iphone
EndingCoder1 小时前
构建工具集成:Webpack 和 TypeScript
前端·webpack·typescript
卡西里弗斯奥2 小时前
【Tomcat】部署Web服务器之Tomcat
服务器·前端·tomcat
阿蒙Amon2 小时前
C#每日面试题-索引器和迭代器的区别
开发语言·windows·c#
vortex52 小时前
php-fpm + nginx 环境搭建配置与常见问题解决
开发语言·nginx·php
赤狐先生2 小时前
第三步--根据python基础语法完成一个简单的深度学习模拟
开发语言·python·深度学习
kirk_wang2 小时前
Flutter艺术探索-Hive高性能存储:NoSQL数据库实战
flutter·移动开发·flutter教程·移动开发教程
Sheldon一蓑烟雨任平生2 小时前
Sass 星空(Sass + keyframes 实现星空动画)
前端·css·vue3·sass·keyframes