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

