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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 10:36:02