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

自定义Controller的ListView插入元素时行为异常求助

问题原因

你的代码存在两个核心问题,导致新增Widget的位置和标题不符合预期:

  1. Entry的State缓存了旧数据:_EntryState里的index和title是在initState中初始化的late final变量,而initState仅在Widget首次创建时执行一次。当列表数据变化后,widget.index会更新,但State里的缓存值不会同步更新,导致点击按钮时使用旧索引,新增位置错误,标题也显示旧内容。
  2. ListView子项缺少唯一Key:ListView.builder生成的Entry没有设置唯一key,Flutter会复用现有Widget,导致数据更新时Widget与数据的对应关系混乱,进一步加剧位置和标题的错误。
修复方案

1. 移除State中的缓存变量,直接使用最新参数

修改_EntryState,删除缓存的index和title,在build方法中直接获取最新的索引和标题:

class _EntryState extends State<Entry> {
  @override
  Widget build(BuildContext context) => ListTile(
        title: Text(widget.controller.items[widget.index]),
        trailing: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextButton(
              child: const Text('Add above'),
              onPressed: () => widget.controller.insertAbove(widget.index),
            ),
            TextButton(
              child: const Text('Add below'),
              onPressed: () => widget.controller.insertBelow(widget.index),
            ),
          ],
        ),
      );
}

2. 给ListView子项添加唯一Key

在CustomListView的itemBuilder中,给Entry添加key,确保Flutter能正确识别每个列表项:

itemBuilder: (context, index) => Entry(
  key: ValueKey(controller.items[index]), // 使用数据项作为唯一标识
  index: index,
  controller: controller,
),

如果后续会出现重复的items值,可以改用包含索引的唯一Key,比如:key: Key('entry-$index-${controller.items[index]}'),确保每个Key绝对唯一。

完整修复后的代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 40),
        child: CustomListView(
          controller: ListController(
            items: ['a', 'b', 'c'],
          ),
        ),
      ),
    );
  }
}

class CustomListView extends StatefulWidget {
  const CustomListView({
    super.key,
    required this.controller,
  });

  final ListController controller;

  @override
  State<CustomListView> createState() => _CustomListViewState();
}

class _CustomListViewState extends State<CustomListView> {
  late final ListController controller;

  @override
  void initState() {
    super.initState();
    controller = widget.controller..addListener(() => setState(() {}));
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      shrinkWrap: true,
      itemCount: controller.items.length,
      itemBuilder: (context, index) => Entry(
        key: ValueKey(controller.items[index]),
        index: index,
        controller: controller,
      ),
    );
  }
}

class Entry extends StatefulWidget {
  const Entry({
    super.key,
    required this.index,
    required this.controller,
  });

  final int index;
  final ListController controller;

  @override
  State<Entry> createState() => _EntryState();
}

class _EntryState extends State<Entry> {
  @override
  Widget build(BuildContext context) => ListTile(
        title: Text(widget.controller.items[widget.index]),
        trailing: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextButton(
              child: const Text('Add above'),
              onPressed: () => widget.controller.insertAbove(widget.index),
            ),
            TextButton(
              child: const Text('Add below'),
              onPressed: () => widget.controller.insertBelow(widget.index),
            ),
          ],
        ),
      );
}

class ListController extends ChangeNotifier {
  List<String> items;

  ListController({required this.items});

  void insertAbove(int index) {
    items.insert(index, 'above-${items[index]}');
    notifyListeners();
  }

  void insertBelow(int index) {
    items.insert(index + 1, 'below-${items[index]}');
    notifyListeners();
  }
}

修复后的效果

点击"Add above"或"Add below"时,会在当前项的正确位置新增子项,标题也会正确显示为above-原标题或below-原标题,不会出现位置偏移或标题错误的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:42:43