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

Flutter ListView实时过滤失效:ListView.builder无法实时渲染结果求助

Flutter ListView.builder 无法实时渲染搜索结果的解决方案

问题根源

  1. FutureBuilder 重复重置数据
    你在FutureBuilder的builder回调中每次都将items和originalSpecialities重新赋值为snapshot.data!,导致每次调用setState(搜索触发时),列表都会被重置为原始数据,过滤结果被覆盖。

  2. ModalBottomSheet 上下文隔离
    showModalBottomSheet创建的弹窗拥有独立的Widget上下文,外部页面的setState无法触发弹窗内部ListView.builder的重建,因此即使items已被过滤,弹窗列表也不会更新。

修复后的代码

class _UnitsPage extends State<UnitsPage> {
  late Future<List<UnitModel>> units;
  late Future<List<Speciality>> specialities;
  Speciality? _selectedSpeciality;
  List<Speciality> items = [];
  List<Speciality> originalSpecialities = [];

  final TextEditingController _searchSpeciality = TextEditingController();

  late Future<Position> position;

  @override
  void initState() {
    super.initState();
    units = getUnits();
    // 初始化specialities并仅赋值一次原始数据
    specialities.then((data) {
      setState(() {
        originalSpecialities = data;
        items = data;
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
    _searchSpeciality.dispose();
  }

  Future<void> _refreshUnits() async {
    setState(() {
      units = getUnits(specialityId: _selectedSpeciality?.id ?? 0);
    });
  }

  @override
  Widget build(BuildContext context) {
    String selectedSpeciality = 'Selecteaza specialitatea';
    if (_selectedSpeciality != null) {
      selectedSpeciality = _selectedSpeciality!.name;
    }

    return Scaffold(
      drawer: const Menu(),
      appBar: AppBar(
        systemOverlayStyle: const SystemUiOverlayStyle(
          statusBarIconBrightness: Brightness.light,
          statusBarBrightness: Brightness.light,
        ),
        flexibleSpace: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
              colors: [Color(0xff058efc), Color(0xff74c0fc)],
            ),
          ),
        ),
        title: Transform.translate(
          offset: const Offset(-20.0, 0.0),
          child: const Text('Unități medicale', style: TextStyle(color: Colors.white)),
        ),
        iconTheme: const IconThemeData(color: Colors.white),
      ),
      body: Padding(
        padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
        child: Column(
          children: [
            // 替换FutureBuilder为条件渲染,避免重复重置数据
            if (originalSpecialities.isEmpty)
              const CircularProgressIndicator()
            else
              BootstrapRow(
                children: [
                  BootstrapCol(
                    child: TextButton(
                      child: Text(selectedSpeciality),
                      onPressed: () {
                        showModalBottomSheet(
                          isScrollControlled: true,
                          context: context,
                          builder: (context) {
                            // 使用StatefulBuilder让弹窗内部响应状态变化
                            return StatefulBuilder(
                              builder: (context, setState) {
                                return FractionallySizedBox(
                                  heightFactor: 0.8,
                                  child: SingleChildScrollView(
                                    child: BootstrapRow(
                                      children: [
                                        BootstrapCol(
                                          child: Padding(
                                            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16),
                                            child: TextField(
                                              controller: _searchSpeciality,
                                              onChanged: (search) {
                                                // 在弹窗内部的setState中更新items
                                                setState(() {
                                                  items = originalSpecialities
                                                      .where((item) => item.name.toLowerCase().contains(search.toLowerCase()))
                                                      .toList();
                                                });
                                              },
                                            ),
                                          ),
                                        ),
                                        BootstrapCol(
                                          child: SizedBox(
                                            height: 700,
                                            child: ListView.builder(
                                              itemCount: items.length,
                                              itemBuilder: (context, index) {
                                                return ListTile(
                                                  tileColor: (_selectedSpeciality != null && _selectedSpeciality!.id == items[index].id) ? const Color(0xff058efc) : null,
                                                  textColor: (_selectedSpeciality != null && _selectedSpeciality!.id == items[index].id) ? Colors.white : null,
                                                  onTap: () {
                                                    // 更新外部页面的选中状态
                                                    this.setState(() {
                                                      _selectedSpeciality = items[index];
                                                    });
                                                    Navigator.pop(context);
                                                  },
                                                  title: Text(items[index].name),
                                                );
                                              },
                                            ),
                                          ),
                                        )
                                      ],
                                    ),
                                  ),
                                );
                              },
                            );
                          },
                        );
                      },
                    ),
                  ),
                  BootstrapCol(
                    child: ElevatedButton(
                      onPressed: _refreshUnits,
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.all(0),
                      ),
                      child: const Text("Caută"),
                    ),
                  )
                ],
              ),
            Expanded(
              child: FutureBuilder<List<UnitModel>>(
                future: units,
                builder: (context, snapshot) {
                  switch (snapshot.connectionState) {
                    case ConnectionState.none:
                    case ConnectionState.waiting:
                    case ConnectionState.active:
                      return const Center(child: CircularProgressIndicator(color: Colors.blue));
                    case ConnectionState.done:
                      if (snapshot.hasData) {
                        return BootstrapRow(
                          children: [
                            for (UnitModel unit in snapshot.data!)
                              BootstrapCol(
                                xs: 6,
                                child: UnitWidget(unit: unit),
                              )
                          ],
                        );
                      } else {
                        return const Text('No data available');
                      }
                  }
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键修改说明

  1. 原始数据仅初始化一次
    在initState中监听specialities Future的完成事件,只给originalSpecialities和items赋值一次原始数据,避免FutureBuilder每次重建时重置列表。

  2. 用StatefulBuilder包裹弹窗内容
    StatefulBuilder可以让弹窗内部拥有独立的状态更新能力,搜索时调用弹窗内部的setState,直接触发ListView.builder的重建,实时渲染过滤结果。

  3. 简化FutureBuilder逻辑
    替换原来的FutureBuilder为条件渲染,仅在原始数据未加载时显示加载指示器,避免不必要的重建。

内容的提问来源于stack exchange,提问作者Alexandru Silasi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 05:24:53