Flutter中如何为Firebase RTDB驱动的ListView.builder新增项添加动画?
实现思路
你需要在State层维护一份本地的列表缓存,每次Stream返回新的全量列表时,对比新旧列表的差量,分别触发删除、新增的动画操作,再同步更新本地缓存即可。
具体实现步骤
- 首先在State类中定义全局Key、本地列表缓存和首次加载标记:
class _YourPageState extends State<YourPage> { final listKey = GlobalKey<AnimatedListState>(); List<AlimentarModel> _localAlimentarList = []; // 标记是否为首次加载,首次加载不需要触发动画 bool _isFirstLoad = true; }
- 修改StreamBuilder逻辑,增加差量对比和动画触发逻辑:
注意要先给
AlimentarModel准备唯一标识字段(比如id),用来判断两个元素是否为同一条记录,没有唯一id的话可以用日期+其他唯一属性组合判断。
StreamBuilder( stream: alimentarBloc.alimentarStream, builder: (BuildContext context, AsyncSnapshot<List<AlimentarModel>> snapshot){ if (snapshot.hasData) { final newAlimentarList = snapshot.data ?? []; // 处理列表为空的情况 if (newAlimentarList.isEmpty) { if (_localAlimentarList.isNotEmpty) { // 倒序删除所有本地元素,触发移除动画 for (int i = _localAlimentarList.length -1; i >=0; i--) { final removedItem = _localAlimentarList.removeAt(i); listKey.currentState?.removeItem( i, (context, animation) => _crearItem(context, alimentarBloc, removedItem, animation) ); } } return _imagenInicial(context); } // 先对新返回的列表做排序 newAlimentarList.sort((a, b) => b.fecha.compareTo(a.fecha)); if (_isFirstLoad) { // 首次加载直接赋值,不触发动画 _localAlimentarList = List.from(newAlimentarList); _isFirstLoad = false; } else { // 第一步:先处理删除的项,倒序遍历避免索引偏移 for (int i = _localAlimentarList.length - 1; i >= 0; i--) { final oldItem = _localAlimentarList[i]; if (!newAlimentarList.any((newItem) => newItem.id == oldItem.id)) { _localAlimentarList.removeAt(i); listKey.currentState?.removeItem( i, (context, animation) => _crearItem(context, alimentarBloc, oldItem, animation) ); } } // 第二步:处理新增的项 for (int i = 0; i < newAlimentarList.length; i++) { final newItem = newAlimentarList[i]; if (!_localAlimentarList.any((oldItem) => oldItem.id == newItem.id)) { _localAlimentarList.insert(i, newItem); listKey.currentState?.insertItem(i, duration: const Duration(milliseconds: 500)); } } } return Container( decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.vertical(bottom: Radius.circular(20.0)), ), child: Stack( children: <Widget>[ AnimatedList( key: listKey, initialItemCount: _localAlimentarList.length, itemBuilder: (context, i, animation) => _crearItem(context, alimentarBloc, _localAlimentarList[i], animation), padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0, bottom: 20.0), ), ], ), ); } else if (snapshot.hasError) { return Text(snapshot.error.toString()); } return Center (child: Image(image: AssetImage('assets/Preloader.gif'), height: 200.0,)); }, ),
注意事项
- 差量对比必须先处理删除再处理新增,避免索引计算错误
- 处理删除逻辑时要倒序遍历本地列表,否则删除元素后后续元素的索引会发生偏移,导致漏删或者删错
- AnimatedList的itemBuilder必须使用本地维护的
_localAlimentarList,不能直接使用Stream返回的新列表
内容的提问来源于stack exchange,提问作者David L
相关产品推荐
相关产品推荐

