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

Flutter中带Radio按钮的ListView点击卡顿性能优化问题

问题根因

当前写法直接在父组件调用setState,会触发父组件全量build,所有列表项无论是否修改都会重新走构建逻辑,100个列表项每次更新都重复构建,自然出现卡顿。

优化方案

方案1:将单个列表项抽为独立StatefulWidget

把每个问题对应的ListTile抽成独立的带状态组件,点击单选时只调用子组件自己的setState,仅刷新当前单个列表项,不会触发父页面全量重建。
示例代码:

// 独立的列表项组件
class QuestionItem extends StatefulWidget {
  final ItemModel question;
  final int index;
  const QuestionItem({super.key, required this.question, required this.index});

  @override
  State<QuestionItem> createState() => _QuestionItemState();
}

class _QuestionItemState extends State<QuestionItem> {
  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text("${widget.index + 1}. ${widget.question.name}"),
      subtitle: Column(
        children: [
          ListTile(
            onTap: () {
              setState(() {
                widget.question.checked = 'yes';
              });
            },
            title: const Text("Yes"),
            leading: Radio(
                value: 'yes',
                groupValue: widget.question.checked,
                onChanged: (val) {
                  setState(() {
                    widget.question.checked = 'yes';
                  });
                }),
          ),
          ListTile(
            title: const Text("No"),
            onTap: () {
              setState(() {
                widget.question.checked = 'no';
              });
            },
            leading: Radio(
                value: 'no',
                groupValue: widget.question.checked,
                onChanged: (val) {
                  setState(() {
                    widget.question.checked = 'no';
                  });
                }),
          )
        ],
      ),
    );
  }
}

// 父组件中使用ListView.builder加载
ListView.builder(
  itemCount: _questions.length,
  itemBuilder: (context, index) => QuestionItem(
    question: _questions[index],
    index: index,
  ),
)

方案2:使用ValueNotifier + ValueListenableBuilder实现局部刷新(无需抽离组件)

如果不想抽离组件,可以修改ItemModel的checked属性为ValueNotifier类型,仅监听单个选项的状态变化,更新时只会刷新被包裹的Radio区域,完全不需要调用父组件的setState。
示例修改:

  1. 修改ItemModel定义:
class ItemModel {
  final String name;
  final int id;
  // 把checked改成ValueNotifier
  final ValueNotifier<String> checked;
  ItemModel({required this.name, required this.id, required String checked}) : checked = ValueNotifier(checked);
}
  1. 列表项中使用ValueListenableBuilder包裹需要刷新的区域:
ListView.builder(
  itemCount: _questions.length,
  itemBuilder: (context, index) {
    final question = _questions[index];
    return ListTile(
      title: Text("${index + 1}. ${question.name}"),
      subtitle: Column(
        children: [
          ListTile(
            onTap: () => question.checked.value = 'yes',
            title: const Text("Yes"),
            leading: ValueListenableBuilder<String>(
              valueListenable: question.checked,
              builder: (context, value, child) => Radio(
                value: 'yes',
                groupValue: value,
                onChanged: (val) => question.checked.value = 'yes',
              ),
            ),
          ),
          ListTile(
            onTap: () => question.checked.value = 'no',
            title: const Text("No"),
            leading: ValueListenableBuilder<String>(
              valueListenable: question.checked,
              builder: (context, value, child) => Radio(
                value: 'no',
                groupValue: value,
                onChanged: (val) => question.checked.value = 'no',
              ),
            ),
          )
        ],
      ),
    );
  }
)

通用优化项

  • 必须使用ListView.builder构建列表,不要直接把所有100个列表项提前生成塞进ListView的children属性,builder会自动懒加载,仅渲染当前屏幕可见的列表项,大幅降低初始渲染和更新的性能消耗。
  • 所有静态不变的Widget都添加const修饰,比如const Text("Yes"),避免不必要的重复构建。
  • 合并重复逻辑:ListTile的onTap和Radio的onChanged功能完全一致,保留其中一个即可,减少冗余执行代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 02:54:00