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

Flutter列表过滤异常:过滤后首项始终显示原列表首项问题

问题分析与修复方案

核心问题

  1. TextEditingController 初始化位置错误:你把searchController放在了build方法里,每次组件重建(比如调用setState或Provider数据变化)都会重新创建控制器,不仅丢失输入框文本状态,还可能干扰过滤逻辑的正确性。
  2. ListView 未给子项设置唯一Key:Flutter的ListView.builder会复用widget实例,当过滤后列表数据变化时,第一个item因无唯一标识,会复用未过滤时的旧widget,导致数据不更新。

修复步骤

1. 调整TextEditingController的初始化位置

将控制器移到State类的成员变量中,在initState初始化,dispose时销毁以避免内存泄漏:

class _DiscoverPageState extends State<DiscoverPage> {
  late TextEditingController searchController;

  @override
  void initState() {
    super.initState();
    searchController = TextEditingController();
  }

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

  // ... 其他代码
}

2. 给GameCard添加唯一Key

在ListView.builder的itemBuilder中,给GameCard传入基于游戏唯一标识的Key(比如游戏id),确保widget能正确更新:

Expanded(
  child: ListView.builder(
    itemCount: filteredGames.length,
    itemBuilder: (context, index) {
      final game = filteredGames[index];
      return GameCard(
        key: ValueKey(game.id), 
        game: game,
      );
    },
  ),
)

3. 优化搜索触发逻辑(可选)

Provider的searchGames会自动通知组件重建,无需额外调用setState:

suffixIcon: InkWell(
  onTap: () {
    context.read<FilterProvider>().searchGames(searchController.text);
  },
  child: Icon(Icons.search_sharp, color: Colors.white),
),

修复后的完整代码

// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables

import 'package:flutter/material.dart';
import 'package:megacosm_game_store/data/games_data.dart';
import 'package:megacosm_game_store/models/game_model.dart';
import 'package:megacosm_game_store/providers/filter_provider.dart';
import 'package:megacosm_game_store/utils/blackray_app_bar.dart';
import 'package:megacosm_game_store/utils/user_option_drawer.dart';
import 'package:megacosm_game_store/widgets/game_card.dart';
import 'package:provider/provider.dart';

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

  @override
  State<DiscoverPage> createState() => _DiscoverPageState();
}

class _DiscoverPageState extends State<DiscoverPage> {
  late TextEditingController searchController;

  @override
  void initState() {
    super.initState();
    searchController = TextEditingController();
  }

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

  @override
  Widget build(BuildContext context) {
    List<Game> filteredGames = context.watch<FilterProvider>().filteredGames;
    return Scaffold(
      endDrawer: UserOptionsDrawer(),
      appBar: BlackRayAppBar(),
      body: Container(
        color: const Color.fromRGBO(16, 16, 16, 1),
        child: Column(children: [
          Container(
            height: 60,
            color: const Color.fromRGBO(16, 16, 16, 1),
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 10),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  SizedBox(
                    width: 250,
                    height: 40,
                    child: TextFormField(
                      controller: searchController,
                      cursorColor: Colors.white,
                      decoration: InputDecoration(
                          contentPadding: const EdgeInsets.symmetric(horizontal: 20),
                          suffixIcon: InkWell(
                              onTap: () {
                                context.read<FilterProvider>().searchGames(searchController.text);
                              },
                              child: const Icon(
                                Icons.search_sharp,
                                color: Colors.white,
                              )),
                          focusColor: Colors.grey.shade900,
                          fillColor: Colors.grey.shade800,
                          filled: true,
                          border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(20),
                            borderSide: BorderSide.none,
                          ),
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(20),
                            borderSide:
                                const BorderSide(width: 2, color: Colors.white24),
                          )),
                    ),
                  ),
                  InkWell(
                      onTap: () {
                        setState(() {});
                      },
                      child: const Icon(
                        Icons.filter_alt_sharp,
                        size: 30,
                      ))
                ],
              ),
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: filteredGames.length,
              itemBuilder: (context, index) {
                final game = filteredGames[index];
                return GameCard(
                  key: ValueKey(game.id),
                  game: game,
                );
              },
            ),
          )
        ]),
      ),
    );
  }
}

验证说明

修改后,过滤后的列表首项会正确更新,输入框文本状态也能保持稳定。如果Game模型没有id字段,可使用其他唯一属性(比如不重复的name)作为ValueKey的参数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:15:55