Flutter中如何实现ListView内JSON数据项的搜索功能
问题根因
- 你在
_searchBar组件的FutureBuilder中修改的routes是局部变量,作用域仅在该FutureBuilder的builder函数内,完全无法影响到body中ListView渲染使用的路由列表。每次触发setState页面重绘时,body的FutureBuilder会重新调用service.getMarshrutWithStops拉取全量数据,所以筛选结果永远不会生效。 - 重复请求接口:body和_searchBar中分别调用了一次
service.getMarshrutWithStops,不仅浪费性能,也导致两份数据源完全独立,筛选逻辑无法同步。 - 没有维护全局的状态变量存储全量数据和搜索结果,筛选后的数据无法传递给列表渲染层。
- 额外bug:
onFavoritePress方法中声明的List<RouteWithStops> routes没有赋值就直接使用routes[index],会触发空指针异常。
修复方案(20分钟即可完成)
步骤1:在State类中新增全局状态变量
class _MarshrutesPageState extends State<MarshrutesPage> { Box<RouteWithStops> favoriteRoutesBox; TransportService service = getIt<TransportService>(); // 新增下面3个变量 List<RouteWithStops> _allRoutes = []; // 存储全量路由数据 List<RouteWithStops> _filteredRoutes = []; // 存储筛选后的路由数据 bool _isLoading = true; // 数据加载状态
步骤2:在initState中一次性拉取全量数据
@override void initState() { super.initState(); favoriteRoutesBox = Hive.box(favoritesBox); // 页面初始化时一次性拉取数据,不用在build里重复请求 _loadRouteData(); } Future<void> _loadRouteData() async { final routes = await service.getMarshrutWithStops(widget.ttId); setState(() { _allRoutes = routes; _filteredRoutes = routes; // 初始状态筛选结果等于全量数据 _isLoading = false; }); }
步骤3:修改搜索栏逻辑,不需要再单独拉取数据
_searchBar() { return Padding( padding: const EdgeInsets.all(8), child: TextField( decoration: InputDecoration( hintText: 'Search', hoverColor: Colors.green, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), prefixIcon: Icon(Icons.search), ), onChanged: (text) { text = text.toLowerCase(); setState(() { // 基于全量数据筛选,修改全局的_filteredRoutes _filteredRoutes = _allRoutes.where((element) { var routesTitle = element.route.mrTitle.toLowerCase(); return routesTitle.contains(text); }).toList(); }); }, ), ); }
步骤4:修改body的渲染逻辑,使用_filteredRoutes渲染列表
去掉body里的FutureBuilder,直接用状态变量判断:
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(/* 原有逻辑无需修改 */), // 修改body部分 body: _isLoading ? Center(child: CircularProgressIndicator()) : ValueListenableBuilder( valueListenable: favoriteRoutesBox.listenable(), builder: (BuildContext context, value, Widget child) { return ListView.builder( // 用_filteredRoutes的长度计算itemCount itemCount: _filteredRoutes.length + 1, itemBuilder: (context, index) { return index == 0 ? _searchBar() : _listItem(index - 1, _filteredRoutes); }, ); }, ), bottomNavigationBar: /* 原有逻辑无需修改 */, ); }
步骤5:修复onFavoritePress的空指针bug
void onFavoritePress(int index) { // 删掉原来未赋值的routes变量,直接用_filteredRoutes if (favoriteRoutesBox.containsKey(index)) { favoriteRoutesBox.delete(index); return; } favoriteRoutesBox.put(index, _filteredRoutes[index]); }
额外优化建议
你可以把getMarshrutWithStops方法里的await api.fetchSchedule()提到请求逻辑外层,现在每次调用都会拉取一次排班数据,如果数据不需要实时更新可以做个缓存,避免重复请求。
内容的提问来源于stack exchange,提问作者user16357470
相关产品推荐
相关产品推荐

