You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 12:45:34