Flutter项目中REST API数据的排序与筛选功能实现示例
Flutter列表添加排序、筛选及搜索功能实现方案
需求说明
本人从事Flutter开发已有2个月,当前项目采用Bloc状态管理从REST API获取数据,并通过ListView.builder进行列表展示。现需为该列表添加排序、筛选及搜索功能,恳请提供实现示例与宝贵技术建议。
现有代码
class Upcoming extends StatefulWidget { Upcoming({Key? key}) : super(key: key); @override State<Upcoming> createState() => _UpcomingState(); } class _UpcomingState extends State<Upcoming> { @override Widget build(BuildContext context) { return BlocBuilder<MOdelBloc, MOdelState>( builder: (context, state) { if (state is MOdelInitial) { context.read<MOdelBloc>().add(LoadEVENT()); return const Center( child: CircularProgressIndicator(), ); } else if (state is ModelLoading) { return const Center( child: CircularProgressIndicator(), ); } else if (state is ModelLoadedstate) { return buildupcoming(state.fakestore); } else if (state is ErrorSatte) { return const Center( child: Text('errorstate'), ); } return const Center(child: Text('error')); }, ); } } Widget buildupcoming(List<CryptoCurrency> currency) { return Scaffold( appBar: AppBar( backgroundColor: Colors.yellow.shade400, leading: const Icon( Icons.portrait_rounded, ), title: const Center( child: Text( 'Upcoming Matches', style: TextStyle(fontWeight: FontWeight.bold), ), ), actions: const [ Icon( Icons.headset, ), SizedBox( width: 5, ), Icon(Icons.message) ], ), body: SingleChildScrollView( scrollDirection: Axis.vertical, child: Container( height: double.maxFinite, margin: const EdgeInsets.all(10), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () {}, child: const Text('CRICKET')), const SizedBox( width: 15, ), ElevatedButton( onPressed: () {}, child: const Icon(Icons.sports_football)) ], ), const SizedBox( height: 10, ), Row( children: const [ TextWidget(text: 'My Rooms', size: 20), Spacer(), Text( 'View all', style: TextStyle(color: Colors.black45), ) ], ), const SizedBox( height: 5, ), Container( height: 170, child: PageView.builder( scrollDirection: Axis.horizontal, itemCount: 3, itemBuilder: (context, index) { return Container( margin: const EdgeInsets.only(left: 5), child: Column(children: [ SliderCARD( text1: currency[2].fullname.toString(), text2: currency[3].fullname, minitext1: currency[2].price.toString(), minitext2: currency[4].price.toString(), color: Colors.indigo, height: 100, ), Row(children: [IconButton(onPressed: ( ){}, icon: Icon(Icons.arrow_back)),IconButton(onPressed: (){}, icon: Icon(Icons.arrow_back_outlined))],)])); })), ListView.builder( physics:const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: currency.length, itemBuilder: (context, index) { final CryptoCurrency cryptoCurrency = currency[index]; return Container( margin: const EdgeInsets.only(bottom: 10), height: 100, child: SliderCARD( text1: cryptoCurrency.fullname, text2: currency[3].fullname, minitext1: cryptoCurrency.price.toString(), minitext2: currency[4].price.toString(), color: Colors.white, height: 100, )); }) ])), )); }
实现示例
1. 调整Bloc状态与事件
扩展Bloc状态,维护原始数据、过滤后数据及操作参数,确保状态可追踪:
// 事件定义 abstract class MOdelEvent {} class LoadEVENT extends MOdelEvent {} class UpdateSearchQuery extends MOdelEvent { final String query; UpdateSearchQuery(this.query); } class UpdateFilter extends MOdelEvent { final String sportType; UpdateFilter(this.sportType); } class ToggleSort extends MOdelEvent { final bool isAscending; ToggleSort(this.isAscending); } // 状态定义 class ModelLoadedstate extends MOdelState { final List<CryptoCurrency> originalData; final List<CryptoCurrency> displayData; final String searchQuery; final String currentFilter; final bool isPriceAscending; ModelLoadedstate({ required this.originalData, required this.displayData, this.searchQuery = '', this.currentFilter = 'ALL', this.isPriceAscending = true, }); ModelLoadedstate copyWith({ List<CryptoCurrency>? originalData, List<CryptoCurrency>? displayData, String? searchQuery, String? currentFilter, bool? isPriceAscending, }) { return ModelLoadedstate( originalData: originalData ?? this.originalData, displayData: displayData ?? this.displayData, searchQuery: searchQuery ?? this.searchQuery, currentFilter: currentFilter ?? this.currentFilter, isPriceAscending: isPriceAscending ?? this.isPriceAscending, ); } }
2. Bloc核心逻辑实现
在Bloc中封装过滤、排序逻辑,确保数据处理与UI解耦:
class MOdelBloc extends Bloc<MOdelEvent, MOdelState> { MOdelBloc() : super(MOdelInitial()) { on<LoadEVENT>((event, emit) async { emit(ModelLoading()); try { final data = await fetchCryptoData(); emit(ModelLoadedstate( originalData: data, displayData: data, )); } catch (e) { emit(ErrorSatte()); } }); on<UpdateSearchQuery>((event, emit) { if (state is ModelLoadedstate) { final currentState = state as ModelLoadedstate; final filtered = _applyFilters(currentState.originalData, event.query, currentState.currentFilter); final sorted = _applySort(filtered, currentState.isPriceAscending); emit(currentState.copyWith( searchQuery: event.query, displayData: sorted, )); } }); on<UpdateFilter>((event, emit) { if (state is ModelLoadedstate) { final currentState = state as ModelLoadedstate; final filtered = _applyFilters(currentState.originalData, currentState.searchQuery, event.sportType); final sorted = _applySort(filtered, currentState.isPriceAscending); emit(currentState.copyWith( currentFilter: event.sportType, displayData: sorted, )); } }); on<ToggleSort>((event, emit) { if (state is ModelLoadedstate) { final currentState = state as ModelLoadedstate; final sorted = _applySort(currentState.displayData, event.isAscending); emit(currentState.copyWith( isPriceAscending: event.isAscending, displayData: sorted, )); } }); } List<CryptoCurrency> _applyFilters(List<CryptoCurrency> data, String query, String filter) { var result = data; if (filter != 'ALL') { result = result.where((item) => item.sportType == filter).toList(); } if (query.isNotEmpty) { result = result.where((item) => item.fullname.toLowerCase().contains(query.toLowerCase())).toList(); } return result; } List<CryptoCurrency> _applySort(List<CryptoCurrency> data, bool isAscending) { final sorted = List.from(data); sorted.sort((a, b) { final priceA = double.parse(a.price); final priceB = double.parse(b.price); return isAscending ? priceA.compareTo(priceB) : priceB.compareTo(priceA); }); return sorted; } Future<List<CryptoCurrency>> fetchCryptoData() async { await Future.delayed(const Duration(seconds: 1)); return List.generate(10, (index) => CryptoCurrency( fullname: 'Crypto $index', price: '${(100 + index * 10).toStringAsFixed(2)}', sportType: index % 2 == 0 ? 'CRICKET' : 'FOOTBALL', )); } } class CryptoCurrency { final String fullname; final String price; final String sportType; CryptoCurrency({required this.fullname, required this.price, required this.sportType}); }
3. UI层更新与交互绑定
修改UI组件,添加搜索、筛选、排序控件,并绑定Bloc事件:
class Upcoming extends StatefulWidget { Upcoming({Key? key}) : super(key: key); @override State<Upcoming> createState() => _UpcomingState(); } class _UpcomingState extends State<Upcoming> { final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _searchController.addListener(() { context.read<MOdelBloc>().add(UpdateSearchQuery(_searchController.text)); }); } @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocBuilder<MOdelBloc, MOdelState>( builder: (context, state) { if (state is MOdelInitial) { context.read<MOdelBloc>().add(LoadEVENT()); return const Center(child: CircularProgressIndicator()); } else if (state is ModelLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is ModelLoadedstate) { return buildupcoming(context, state); } else if (state is ErrorSatte) { return const Center(child: Text('errorstate')); } return const Center(child: Text('error')); }, ); } } Widget buildupcoming(BuildContext context, ModelLoadedstate state) { return Scaffold( appBar: AppBar( backgroundColor: Colors.yellow.shade400, leading: const Icon(Icons.portrait_rounded), title: const Center( child: Text( 'Upcoming Matches', style: TextStyle(fontWeight: FontWeight.bold), ), ), actions: const [ Icon(Icons.headset), SizedBox(width: 5), Icon(Icons.message) ], ), body: SingleChildScrollView( scrollDirection: Axis.vertical, child: Container( margin: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: _searchController, decoration: const InputDecoration( hintText: '搜索赛事...', prefixIcon: Icon(Icons.search), border: OutlineInputBorder(), ), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () => context.read<MOdelBloc>().add(const UpdateFilter('ALL')), style: ElevatedButton.styleFrom( backgroundColor: state.currentFilter == 'ALL' ? Colors.blue : null, ), child: const Text('全部'), ), const SizedBox(width: 15), ElevatedButton( onPressed: () => context.read<MOdelBloc>().add(const UpdateFilter('CRICKET')), style: ElevatedButton.styleFrom( backgroundColor: state.currentFilter == 'CRICKET' ? Colors.blue : null, ), child: const Text('CRICKET'), ), const SizedBox(width: 15), ElevatedButton( onPressed: () => context.read<MOdelBloc>().add(const UpdateFilter('FOOTBALL')), style: ElevatedButton.styleFrom( backgroundColor: state.currentFilter == 'FOOTBALL' ? Colors.blue : null, ), child: const Icon(Icons.sports_football), ), ], ), const SizedBox(height: 10), Row( children: [ const TextWidget(text: 'My Rooms', size: 20), const Spacer(), TextButton( onPressed: () => context.read<MOdelBloc>().add(ToggleSort(!state.isPriceAscending)), child: Row( children: [ const Text('按价格排序'), Icon(state.isPriceAscending ? Icons.arrow_upward : Icons.arrow_downward), ], ), ), const Text( 'View all', style: TextStyle(color: Colors.black45), ) ], ), const SizedBox(height: 5), Container( height: 170, child: PageView.builder( scrollDirection: Axis.horizontal, itemCount: 3, itemBuilder: (context, index) { if (state.displayData.length < 5) return const SizedBox(); return Container( margin: const EdgeInsets.only(left: 5), child: Column( children: [ SliderCARD( text1: state.displayData[2].fullname, text2: state.displayData[3].fullname, minitext1: state.displayData[2].price, minitext2: state.displayData[4].price, color: Colors.indigo, height: 100, ), Row( children: [ IconButton(onPressed: () {}, icon: const Icon(Icons.arrow_back)), IconButton(onPressed: () {}, icon: const Icon(Icons.arrow_back_outlined)), ], ), ], ), ); }, ), ), const SizedBox(height: 10), state.displayData.isEmpty ? const Center(child: Text('暂无匹配数据')) : ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: state.displayData.length, itemBuilder: (context, index) { final crypto = state.displayData[index]; return Container( margin: const EdgeInsets.only(bottom: 10), height: 100, child: SliderCARD( text1: crypto.fullname, text2: state.displayData.length > 3 ? state.displayData[3].fullname : '', minitext1: crypto.price, minitext2: state.displayData.length > 4 ? state.displayData[4].price : '', color: Colors.white, height: 100, ), ); }, ), ], ), ), ), ); } class TextWidget extends StatelessWidget { final String text; final double size; const TextWidget({required this.text, required this.size, Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text(text, style: TextStyle(fontSize: size, fontWeight: FontWeight.bold)); } } class SliderCARD extends StatelessWidget { final String text1; final String text2; final String minitext1; final String minitext2; final Color color; final double height; const SliderCARD({ required this.text1, required this.text2, required this.minitext1, required this.minitext2, required this.color, required this.height, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Container( height: height, width: 200, color: color,
相关产品推荐
相关产品推荐

