我的 AnimatedList
在颤动时遇到问题,下面是 AnimatedList
的以下实现。该列表有效,动画正常,但是当我在点击列表中的一张卡片后调用 clearEverythingButMe()
方法时,当 AnimatedList
删除所有其他项目时,不会保留该小部件的状态并将剩余的项目移回顶部。我不明白为什么会发生这种情况,因为键都被保留了,甚至模型中小部件的哈希码在 clearEverythingButMe()
之后也保持不变。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gym_notes/exercisesList/exercisesDB.dart';
class NiceList extends StatefulWidget {
const NiceList({required Key key}) : super(key: key);
@override
NiceListState createState() => NiceListState();
}
class NiceListState extends State<NiceList> {
GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
late ListModel _list;
@override
void initState() {
super.initState();
_list = ListModel(
listKey: _listKey,
initialItems: <Widget>[],
removedItemBuilder: _buildRemovedItem,
);
WidgetsBinding.instance!.addPostFrameCallback((_) {
_addCategories();
});
}
void testAdd() {}
Tween<Offset> _offset = Tween(begin: Offset(0, 3), end: Offset(0, 0));
Widget _buildItem(
BuildContext context, int index, Animation<double> animation) {
return SlideTransition(
position: animation.drive(_offset), child: _list[index]);
}
var shrinkTween = Tween<double>(begin: 0.0, end: 1.0);
var _tween =
Tween<Offset>(begin: const Offset(1, 0), end: const Offset(0, 0));
Widget _buildRemovedItem(
Widget item, BuildContext context, Animation<double> animation) {
return SizeTransition(
axisAlignment: 0.0,
sizeFactor: shrinkTween
.animate(new CurvedAnimation(parent: animation, curve: Curves.ease)),
child: SlideTransition(
position: _tween.animate(
new CurvedAnimation(parent: animation, curve: Curves.easeOut)),
child: item),
);
}
void _addCategories() {
Future ft = Future(() {});
ExercisesDB.exercises.forEach((String exercise) {
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 30), () {
_list.insertCategory(_list.length, exercise);
});
});
});
}
void removeEverything() {
_list.clearList();
}
@override
Widget build(BuildContext context) {
return Scrollbar(
child: AnimatedList(
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
key: _listKey,
itemBuilder: _buildItem,
),
);
}
}
class ListModel {
ListModel({
required this.listKey,
required this.removedItemBuilder,
Iterable? initialItems,
}) : _items = List<Widget>.from(initialItems ?? <Widget>[]);
final GlobalKey<AnimatedListState> listKey;
final dynamic removedItemBuilder;
final List<Widget> _items;
AnimatedListState? get _animatedList => listKey.currentState;
void insertCategory(int index, String category) {
_items.insert(index, CategoryCard(Key(category), category, this));
_animatedList!.insertItem(index);
}
void insertExercise(int index, String exercise) {
_items.insert(index, CategoryCard(Key(exercise), exercise, this));
_animatedList!.insertItem(index);
}
Widget removeAtByKey(Key key) {
var index =
_items.indexOf(_items.firstWhere((element) => element.key == key));
final Widget removedItem = _items.removeAt(index);
_animatedList!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return removedItemBuilder(removedItem, context, animation);
},
);
return removedItem;
}
Future<void> clearAndExpand(String category) async {
//first remove everything but
Future ft = Future(() {});
_items.forEach((var exercise) {
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 10), () {
if (exercise.key != Key(category)) {
removeAtByKey(exercise.key!);
}
});
});
});
await Future.delayed(Duration(milliseconds: 100));
//then add the exercises
var exercises = ExercisesDB.exercisesSub[category];
exercises!.forEach((element) {
//for every category in the databse, check to see if the key belongs to them
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 10), () {
insertExercise(length, element);
});
});
});
}
void clearEverythingButMe(Key key) {
Future ft = Future(() {});
_items.forEach((var exercise) {
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 50), () {
if (exercise.key != key) {
removeAtByKey(exercise.key!);
}
});
});
});
}
void addExercises(String category) {
var exercises = ExercisesDB.exercisesSub[category];
Future ft = Future(() {});
exercises!.forEach((element) {
//for every category in the databse, check to see if the key belongs to them
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 10), () {
insertExercise(length - 1, element);
});
});
});
}
void addEverythingBackButMe(Key key, String cat_name) {
Future ft = Future(() {});
ExercisesDB.exercises.forEach((element) {
//for every category in the databse, check to see if the key belongs to them
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 10), () {
if (Key(element) != key) {
var index = ExercisesDB.exercises.indexOf(element);
insertCategory(index, element);
}
});
});
});
}
void clearList() {
Future ft = Future(() {});
_items.forEach((var exercise) {
ft = ft.then((data) {
return Future.delayed(const Duration(milliseconds: 10), () {
removeAt(0);
});
});
});
}
Widget removeAt(int index) {
final Widget removedItem = _items.removeAt(index);
_animatedList!.removeItem(index,
(BuildContext context, Animation<double> animation) {
return removedItemBuilder(removedItem, context, animation);
}, duration: Duration(milliseconds: 10));
return removedItem;
}
int get length => _items.length;
Widget operator [](int index) => _items[index];
int indexOf(Widget item) => _items.indexOf(item);
}
class CategoryCard extends StatefulWidget {
CategoryCard(Key key, this.categoryName, this.listModel) : super(key: key);
final String categoryName;
final ListModel listModel;
@override
_CategoryCardState createState() => _CategoryCardState();
}
class _CategoryCardState extends State<CategoryCard>
with AutomaticKeepAliveClientMixin {
var count = 0;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
Future<void> toggle() async {
count += 1;
widget.listModel.clearEverythingButMe(widget.key!);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
toggle();
});
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 4),
child: Card(
shadowColor: Colors.black,
child: Center(
child: Text(
"$count ${widget.categoryName}",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 34, fontWeight: FontWeight.w900),
),
),
),
),
);
}
@override
bool get wantKeepAlive => true;
}
点击列表中的第一个项目时,在顶部,删除其他所有内容时会保留状态,因为列表不必移动它,但对于其他所有内容,状态中的计数器重置为 0 . 希望有人能解释为什么尽管有键和我在我的有状态小部件中使用了 AutomaticKeepAliveClientMixin
,它的行为仍然如此。