我有实现 ChangeNotifier 的模型
class DataModel with ChangeNotifier{
List<Data> data = List<Data>();
void addData(Data data){
data.add(data);
notifyListeners();
}
}
以及侦听这些更改的列表视图:
class DataListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<DataModel>(
builder: (context, model, child) {
return ListView.builder(
itemCount: model.data.length,
itemBuilder: (context, index) {
return Text(model.data[index].value);
},
);
},
);
}
}
到目前为止一切顺利,当将项目添加到模型中的列表中时,更改通知会触发 Listview 的重建,我会看到新数据。但是我不能用动画列表而不是列表视图来使用它。最好是 id 喜欢保持我的模型原样,因为动画是 UI 而不是我的逻辑的问题。
changenotififier 总是为我提供数据的最新版本,但我真正需要的是"项目已添加"或"项目已删除"通知。
有没有最佳实践方法?
这是我试验的结果。 这是一个河荚版本,但我认为对于供应商来说是一样的。
有两点。
- 初始化使用 动画列表。 添加/删除
- 动画列表并使用异步异步添加/删除状态。
主飞镖
import 'package:animatedlist_riverpod_sample/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hooks_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
class Home extends HookWidget {
const Home({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final todoList = useProvider(todoListProvider.state);
return Scaffold(appBar: AppBar(title: Text('Todo[${todoList.length}]')), body: TodoListView());
}
}
class TodoListView extends HookWidget {
TodoListView({Key key}) : super(key: key);
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final todoList = useProvider(todoListProvider.state);
@override
Widget build(BuildContext context) {
return AnimatedList(
key: _listKey,
initialItemCount: todoList.length,
itemBuilder: (context, index, animation) =>
_buildItem(todoList[index], animation, index, context),
);
}
Slidable _buildItem(Todo todo, Animation<double> animation, int index, BuildContext context) {
return Slidable(
actionPane: SlidableDrawerActionPane(),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: ListTile(title: Text(todo.description), subtitle: Text(todo.id), onTap: () => {})),
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () {
_listKey.currentState.removeItem(
index, (context, animation) => _buildItem(todo, animation, index, context),
duration: Duration(milliseconds: 200));
_removeItem(context, todo);
},
),
],
);
}
void _removeItem(BuildContext context, Todo todo) async {
await Future.delayed(
Duration(milliseconds: 200), () => context.read(todoListProvider).remove(todo));
}
}
提供者.dart
import 'package:hooks_riverpod/all.dart';
final todoListProvider = StateNotifierProvider<TodoList>((ref) {
return TodoList([
Todo(id: '0', description: 'Todo1'),
Todo(id: '1', description: 'Todo2'),
Todo(id: '2', description: 'Todo3'),
]);
});
class Todo {
Todo({
this.id,
this.description,
});
final String id;
final String description;
}
class TodoList extends StateNotifier<List<Todo>> {
TodoList([List<Todo> initialTodos]) : super(initialTodos ?? []);
void add(String description) {
state = [
...state,
Todo(description: description),
];
}
void remove(Todo target) {
state = state.where((todo) => todo.id != target.id).toList();
}
}
示例存储库在此处。
我最近开始学习 Flutter,并惊讶地发现这个主题在任何地方都没有正确涵盖。我想出了两种方法,我称之为基本和高级。让我们从基本开始。之所以这样命名,是因为提供程序是在构建 AnimatedList 的同一小部件中调用的。
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
int get length => _list.length;
operator [](index) => _list[index];
int add() {
final int index = length;
_list.add('$index');
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
notifyListeners();
return user;
}
}
class BasicApp extends StatelessWidget {
const BasicApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(create: (_) => Users(), child: AnimatedListDemo()));
}
}
class AnimatedListDemo extends StatelessWidget {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
AnimatedListDemo({Key? key}) : super(key: key);
void addUser(Users users) {
final int index = users.add();
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
}
void deleteUser(Users users, int index) {
String user = users.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(users, user));
},
duration: const Duration(seconds: 1),
);
}
Widget _buildItem(Users users, String user, [int? removeIndex]) {
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteUser(users, removeIndex),
)
: null,
);
}
@override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Basic AnimatedList Provider Demo'),
),
body: AnimatedList(
key: _listKey,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(users, users[index], index),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => addUser(users),
tooltip: 'Add an item',
child: const Icon(Icons.add),
),
);
}
}
高级方法的不同之处在于它封装了AnimatedListState。我从 Flutter 的 AnimatedList 文档中获得了这个想法。
typedef RemovedItemBuilder = Widget Function(
String user, BuildContext context, Animation<double> animation);
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
final RemovedItemBuilder _removedItemBuilder;
Users(this._removedItemBuilder);
int get length => _list.length;
operator [](index) => _list[index];
GlobalKey<AnimatedListState> get listKey => _listKey;
int add() {
final int index = length;
_list.add('$index');
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
_listKey.currentState!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return _removedItemBuilder(user, context, animation);
},
duration: const Duration(seconds: 1),
);
notifyListeners();
return user;
}
}
class AdvancedApp extends StatelessWidget {
const AdvancedApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: AnimatedListDemo());
}
}
class AnimatedListDemo extends StatelessWidget {
const AnimatedListDemo({Key? key}) : super(key: key);
Widget _buildItem(BuildContext context, String user, [int? removeIndex]) {
Users users = Provider.of<Users>(context, listen: false);
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => users.removeAt(removeIndex),
)
: null,
);
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(create: (_) => Users((user, context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(context, user));
}), child: Scaffold(
appBar: AppBar(
title: const Text('Advanced AnimatedList Provider Demo'),
),
body: Consumer<Users>(builder: (BuildContext context, Users users, _){
return AnimatedList(
key: users.listKey,
shrinkWrap: true,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(context, users[index], index),
);
},
);
}),
floatingActionButton: const AddButtonSeparateWidget(),
));
}
}
class AddButtonSeparateWidget extends StatelessWidget {
const AddButtonSeparateWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return FloatingActionButton(
onPressed: users.add,
tooltip: 'Add an item',
child: const Icon(Icons.add),
);
}
}
所有代码都发布在Github上。现在我想详细说明一下您关于"添加项目"或"已删除项目"通知的主张。据我了解,它违背了 Flutter 的理念,其中小部件是一个 UI 配置。当小部件的状态发生变化时,Flutter 会与其以前的状态进行差异,并神奇地将差异应用于 UI。这就是为什么我没有在我的实现中使用"项目添加"、"项目已删除"通知的原因。但是我认为应该可以做到,因为我在 Firestore 订阅中看到了一种类似的方法来更改文档更改,尽管目前我不知道如何使用提供程序实现相同的方法。提供商的文档有点差。仔细阅读后,我不能说如何使用提供程序实现部分更新。可能是ProxyProvider及其update
可能会有所帮助,也可能是ListenableProvider。让我知道您是否可以找到您的主张的解决方案。
这是一篇旧帖子,但如果有人偶然发现这个问题并且没有找到合适的解决方案,我会在这里添加这个。
我想要某种形式的高度过渡来处理添加到侧边栏的项目列表。它基本上是一个购物篮子。我还使用提供程序作为状态管理解决方案。
AnimatedList
在这里不是一个很好的选择,因为您需要使用GlobalKey
控制项目数 .我用于添加项目和删除项目的函数位于应用程序的其他位置(产品卡等(,因此将它们挂接到密钥会太复杂了。
因此,我选择对包装我的子项目的Column()
小部件进行动画处理,AnimatedSize()
按照其名称自动对高度进行动画处理。
然后,我向我拥有的列表项小部件添加了一个动画。当我将项目添加到列表时,这为我提供了一个高度过渡,并且列表项本身会很好地淡入。
下面是一个示例:
class PurchaseRowItem extends StatefulWidget {
const PurchaseRowItem({Key? key, required this.item}) : super(key: key);
final PurchaseRow item;
@override
State<PurchaseRowItem> createState() => _PurchaseRowItemState();
}
class _PurchaseRowItemState extends State<PurchaseRowItem> {
bool _visible = false;
@override
void initState() {
super.initState();
// Opacity needs to start from 0 and transition to 1
// so we set it in initState by waiting 10ms
Future.delayed(const Duration(milliseconds: 10), () {
setState(() {
_visible = true;
});
});
}
@override
Widget build(BuildContext context) {
AnimatedSize(
curve: Curves.linear,
// Alignment is important if you want the list to flow from top to bottom so it doesn't jump when adding items
alignment: Alignment.topCenter,
duration: Duration(milliseconds: 250),
child: Column(
children: [
for (var item in purchase.rows)
AnimatedOpacity(
opacity: _visible ? 1 : 0,
duration: const Duration(milliseconds: 250),
child: Dismissible(
child: Container() // Add your list item here
),
)
],
),
);
}
}