Flutter电影APP对接TMDB API实现影片类型筛选功能咨询
Flutter电影APP按类型筛选功能实现方案
1. 调整Provider数据结构(核心解决原始数据源被修改的问题)
将原始数据源和筛选后展示用的数据源完全拆分,同时新增变量存储当前选中的类型ID列表,原始数据源仅在接口请求时更新,永远不会被筛选逻辑修改:
// 原始数据源,仅接口请求时更新,全程不参与筛选修改 List<nowPlaying.Result> _originalNowPlaying = []; List<moviesModel.Result> _originalPopular = []; List<moviesModel.Result> _originalTopRated = []; List<moviesModel.Result> _originalUpcoming = []; // 筛选后对外暴露的展示用数据源 List<nowPlaying.Result> _filteredNowPlaying = []; List<moviesModel.Result> _filteredPopular = []; List<moviesModel.Result> _filteredTopRated = []; List<moviesModel.Result> _filteredUpcoming = []; // 存储当前选中的类型ID集合 Set<int> _selectedGenreIds = {}; Set<int> get selectedGenreIds => _selectedGenreIds; // 页面直接读取筛选后的数据源即可 List<nowPlaying.Result> get getNowPlaying => [..._filteredNowPlaying]; List<moviesModel.Result> get getPopular => [..._filteredPopular]; List<moviesModel.Result> get getTopRated => [..._filteredTopRated]; List<moviesModel.Result> get getUpcoming => [..._filteredUpcoming];
修改原有接口请求逻辑,请求成功后仅更新原始数据源,再触发筛选更新展示数据:
Future<void> getNowPlayingMovies({required String type}) async { try { nowPlaying.NowPlayingModel? nowPlayingModel = await GetMoviesTvShows.nowPlayingMovies(type: type, page: 1); if (nowPlayingModel != null) { List<nowPlaying.Result> temp = []; nowPlayingModel.results.forEach((result) { if (result.title.isNotEmpty && result.overview.isNotEmpty) { temp.add(result); } }); // 仅更新原始数据源 _originalNowPlaying = temp; // 触发筛选更新展示数据 _applyFilter(); notifyListeners(); } } catch (e, s) { print(e); print(s); } } // 热门、TOP榜、即将上映的接口请求方法同理,仅修改对应原始数据源后调用_applyFilter即可
2. 重写筛选逻辑,支持多类型筛选
新增通用筛选私有方法,每次选中/取消类型时自动调用,重新从原始数据源过滤得到展示数据:
// 通用筛选方法 void _applyFilter() { // 无选中类型时,展示数据直接等于原始全量数据 if (_selectedGenreIds.isEmpty) { _filteredNowPlaying = List.from(_originalNowPlaying); _filteredPopular = List.from(_originalPopular); _filteredTopRated = List.from(_originalTopRated); _filteredUpcoming = List.from(_originalUpcoming); notifyListeners(); return; } // 有选中类型的场景: // 当前逻辑为「满足任意一个选中类型即可展示」,如果需要「同时满足所有选中类型才展示」,把下方的any改为every即可 _filteredNowPlaying = _originalNowPlaying.where((movie) => movie.genreIds.any((genreId) => _selectedGenreIds.contains(genreId)) ).toList(); _filteredPopular = _originalPopular.where((movie) => movie.genreIds.any((genreId) => _selectedGenreIds.contains(genreId)) ).toList(); _filteredTopRated = _originalTopRated.where((movie) => movie.genreIds.any((genreId) => _selectedGenreIds.contains(genreId)) ).toList(); _filteredUpcoming = _originalUpcoming.where((movie) => movie.genreIds.any((genreId) => _selectedGenreIds.contains(genreId)) ).toList(); notifyListeners(); } // 对外暴露的类型选中/取消切换方法 void toggleGenre(int genreId) { if (_selectedGenreIds.contains(genreId)) { _selectedGenreIds.remove(genreId); } else { _selectedGenreIds.add(genreId); } // 切换后触发筛选 _applyFilter(); }
3. 修改Genres类型选择组件
移除组件内部的选中状态存储逻辑,改为和Provider的选中集合联动:
class Genres extends StatelessWidget { // 类型选中回调,向外传递选中的类型ID final Function(int genreId) onGenreTap; // 外部传入当前选中的类型ID集合,控制选中样式 final Set<int> selectedGenreIds; const Genres({ Key? key, required this.onGenreTap, required this.selectedGenreIds, }) : super(key: key); final genres = const [ [28, "Action"], [12, "Adventure"], [16, "Animation"], [35, "Comedy"], [80, "Crime"], [99, "Documentary"], [18, "Drama"], [10751, "Family"], [14, "Fantasy"], [36, "History"], [27, "Horror"], [10402, "Music"], [9648, "Mystery"], [10749, "Romance"], [878, "Science Fiction"], [10770, "TV Movie"], [53, "Thriller"], [10752, "War"], [37, "Western"] ]; @override Widget build(BuildContext context) { SizeConfig().init(context); return Container( margin: const EdgeInsets.symmetric(vertical: kDefaultPadding / 2), height: SizeConfig.screenHeight * 0.05, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: genres.length, itemBuilder: (ctx, index) { final genreId = genres[index][0] as int; final isSelected = selectedGenreIds.contains(genreId); return InkWell( onTap: () => onGenreTap(genreId), child: Container( alignment: Alignment.center, margin: const EdgeInsets.only(left: kDefaultPadding / 2), padding: const EdgeInsets.symmetric( horizontal: kDefaultPadding, vertical: kDefaultPadding / 4), decoration: BoxDecoration( color: isSelected ? Colors.amber : Colors.transparent, border: Border.all( color: isSelected ? Colors.transparent : Colors.black26), borderRadius: BorderRadius.circular(20.0)), child: Text( genres[index].last.toString(), style: TextStyle( color: isSelected ? Colors.white : kTextColor.withOpacity(0.8), fontSize: SizeConfig.safeBlockHorizontal * 4), ), ), ); }), ); } }
在使用Genres组件的页面绑定Provider逻辑即可:
Consumer<你的Provider类名>( builder: (context, provider, child) { return Genres( selectedGenreIds: provider.selectedGenreIds, onGenreTap: (genreId) { provider.toggleGenre(genreId); }, ); }, )
多类型筛选逻辑说明
默认实现的是「单类型/多类型任意匹配」逻辑,即只要影片属于任意一个选中的类型就会展示;如果需要实现「多类型交集匹配」,即影片必须同时属于所有选中的类型才展示,仅需要将_applyFilter方法中的any方法替换为every方法即可。
内容的提问来源于stack exchange,提问作者Shubham Vimal
相关产品推荐
相关产品推荐

