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

Flutter中ListView.builder滚动时Widget状态丢失问题求助

问题原因

滚动后UI状态丢失的核心问题是自定义Seat类未正确实现对象相等性判断,加上SeatWidget每次build时都会创建新的Seat实例,导致Provider中存储的选中座位对象,和滚动重建后的Seat实例无法通过contains方法匹配。

Dart中默认的Object ==运算符判断的是对象引用是否相同,而非属性值。当ListView滚动导致SeatWidget重建时,你在build方法里新建的Seat(seatIndex: widget.seatIndex, seatType: widget.seatType)是全新的对象——即使座位号和类型完全一致,也和Provider中保存的选中座位不是同一个引用,因此selectionButtonProvider.isSeatSelected(seat)会返回false,UI显示未选中状态,但Provider里的列表数据其实并未丢失。

解决方案

1. 重写Seat类的相等性判断逻辑

修改Seat类,让它根据seatIndex和seatType来判断对象是否相等:

class Seat {
  final int seatIndex;
  final String seatType;

  Seat({required this.seatIndex, required this.seatType});

  // 重写==运算符,基于属性值判断相等
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Seat &&
          runtimeType == other.runtimeType &&
          seatIndex == other.seatIndex &&
          seatType == other.seatType;

  // 重写hashCode,基于属性值生成哈希值
  @override
  int get hashCode => seatIndex.hashCode ^ seatType.hashCode;
}

2. 优化SeatWidget中Seat实例的创建

避免在build方法中重复创建Seat对象,将其缓存到State类中:

class SeatWidget extends StatefulWidget {
  const SeatWidget({
    Key? key,
    required this.seatIndex,
    required this.seatType,
  }) : super(key: key);

  final int seatIndex;
  final String seatType;

  @override
  State<SeatWidget> createState() => _SeatWidgetState();
}

class _SeatWidgetState extends State<SeatWidget> {
  late final Seat _seat;

  @override
  void initState() {
    super.initState();
    // 仅在初始化时创建一次Seat实例
    _seat = Seat(seatIndex: widget.seatIndex, seatType: widget.seatType);
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<SelectionButtonProvider>(
      builder: (context, selectionButtonProvider, child) {
        return GestureDetector(
          onTap: () {
            selectionButtonProvider.addSeat(_seat);
          },
          child: Container(
            height: 60,
            width: 60,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(8),
              color: selectionButtonProvider.isSeatSelected(_seat) 
                  ? const Color(0xff126DCA) 
                  : const Color(0xffCEEAFF),
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  widget.seatIndex.toString(),
                  style: TextStyle(
                    fontSize: 16,
                    color: selectionButtonProvider.isSeatSelected(_seat) 
                        ? const Color(0xffCEEAFF) 
                        : const Color(0xff126DCA),
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  widget.seatType,
                  style: TextStyle(
                    fontSize: 12,
                    color: selectionButtonProvider.isSeatSelected(_seat) 
                        ? const Color(0xffCEEAFF) 
                        : const Color(0xff126DCA),
                    fontWeight: FontWeight.normal,
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

3. 可选:为Widget设置稳定的Key

给CabinWidget和SeatWidget添加基于唯一标识的key,帮助Flutter更精准地复用Widget,减少不必要的重建:

  • 在ListView.builder中:
return CabinWidget(
  key: ValueKey(index),
  index: index,
);
  • 在CabinWidget创建SeatWidget时:
SeatWidget(
  key: ValueKey(1 + index * 8),
  seatIndex: 1 + index * 8,
  seatType: "Upper",
),

(其余SeatWidget均按此方式,用seatIndex作为key)

验证修改

完成上述调整后,滚动ListView时,重建的SeatWidget会通过属性值判断座位是否被选中,而非对象引用,UI状态就能和Provider中的数据保持一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:44:58