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

Flutter ListView删除异常:总是移除最后一项的问题求助

修复Flutter列表移除指定项时总是删除最后一项的问题

问题根源

  1. Key的错误使用:你用ObjectKey(index)作为Widget的Key,当删除列表项后,后续项的索引会重新排列,Flutter的Widget复用机制会把旧Widget绑定到新的索引位置,导致删除操作的索引和实际数据不匹配。
  2. 状态未同步:CustomInput的TextEditingController只在initState初始化,当父组件更新widget.text时,控制器内容没有同步更新,导致UI显示和实际数据脱节。

修复步骤

1. 使用稳定唯一的Key

替换基于索引的Key为基于列表项内容的ValueKey,确保每个Widget的Key和数据绑定而非位置:

// 在createNamesInput的itemBuilder中
return ListTile(
  title: CustomInput(
    key: ValueKey(names[index]), // 用内容作为唯一标识的Key
    labelText: "Nome",
    onChanged: (String changed) => changeName(index, changed),
    text: names[index],
    onRemoved: () => removeName(index),
  ),
);

2. 同步CustomInput的控制器内容

在CustomInput的State类中重写didUpdateWidget方法,当父组件传递的widget.text变化时,更新控制器的文本:

@override
void didUpdateWidget(covariant CustomInput oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.text != widget.text) {
    inputController.text = widget.text;
  }
}

3. 可选优化:使用实体类存储数据

如果列表项可能重复,建议创建带唯一ID的实体类,避免内容重复导致Key冲突:

class Player {
  final String id;
  String name;

  Player(this.id, this.name);
}

后续用ValueKey(player.id)作为Key,确保Key的唯一性不受内容影响。

修改后的完整代码

create_game.dart

import 'package:flutter/material.dart';
import 'package:pontinho/components/custom_input.dart';

class CreateGame extends StatefulWidget {
  const CreateGame({super.key});

  @override
  State<CreateGame> createState() => _CreateGameState();
}

class _CreateGameState extends State<CreateGame> {
  List<String> names = [''];

  void changeName(int nameIndex, String change) {
    setState(() {
      names[nameIndex] = change;
    });
  }

  void removeName(int nameIndex) {
    print(names);
    print(nameIndex);
    setState(() {
      names.removeAt(nameIndex);
    });
  }

  ListView createNamesInput() {
    return ListView.builder(
      itemCount: names.length,
      shrinkWrap: true,
      itemBuilder: (context, index) {
        return ListTile(
          title: CustomInput(
            key: ValueKey(names[index]),
            labelText: "Nome",
            onChanged: (String changed) => changeName(index, changed),
            text: names[index],
            onRemoved: () => removeName(index),
          ),
        );
      },
    );
  }

  void addName() {
    setState(() {
      names.add('');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: GestureDetector(
          onTap: (() => Navigator.pop(context)),
          child: const Icon(
            Icons.arrow_back,
            color: Colors.black,
            size: 40,
          ),
        ),
        backgroundColor: Colors.white,
        titleTextStyle: const TextStyle(
          color: Colors.black,
          fontSize: 20,
        ),
        title: const Text("CRIE SEU JOGO"),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(
          vertical: 8,
          horizontal: 16,
        ),
        child: Column(
          children: [
            createNamesInput(),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 10),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  TextButton(
                    onPressed: addName,
                    child: Row(
                      children: const [
                        Icon(Icons.add),
                        Text('Adicionar Jogador'),
                      ],
                    ),
                  ),
                ],
              ),
            ),
            SizedBox(
              width: double.infinity,
              height: 50,
              child: ElevatedButton(
                onPressed: () => print('Iniciar!'),
                child: const Text('Iniciar!'),
              ),
            )
          ],
        ),
      ),
    );
  }
}

custom_input.dart

import 'package:flutter/material.dart';

typedef OneArgumentCallback = void Function(String changed);

class CustomInput extends StatefulWidget {
  final OneArgumentCallback onChanged;
  final VoidCallback onRemoved;
  final String labelText;
  final String text;

  const CustomInput({
    super.key,
    required this.onChanged,
    required this.labelText,
    required this.text,
    required this.onRemoved,
  });

  @override
  State<CustomInput> createState() => _CustomInputState();
}

class _CustomInputState extends State<CustomInput> {
  late final TextEditingController inputController;

  @override
  void initState() {
    super.initState();
    inputController = TextEditingController(text: widget.text);
  }

  @override
  void didUpdateWidget(covariant CustomInput oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 同步父组件更新后的文本内容
    if (oldWidget.text != widget.text) {
      inputController.text = widget.text;
    }
  }

  void changeContent(String value) {
    widget.onChanged(value);
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: inputController,
      textDirection: TextDirection.ltr,
      decoration: InputDecoration(
        border: const UnderlineInputBorder(),
        labelText: widget.labelText,
        suffixIcon: IconButton(
          onPressed: widget.onRemoved,
          icon: const Icon(
            Icons.close,
            color: Colors.red,
          ),
        ),
      ),
      autocorrect: false,
      onChanged: changeContent,
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 17:30:20