gpt4 book ai didi

Flutter AnimatedList with Provider Pattern(带有提供者模式的颤动动画列表)

转载 作者:bug小助手 更新时间:2023-10-25 15:52:25 34 4
gpt4 key购买 nike



I have model which implements ChangeNotifier

我有一个实现ChangeNotiizer的模型



class DataModel with ChangeNotifier{
List<Data> data = List<Data>();

void addData(Data data){
data.add(data);
notifyListeners();
}
}


and a ListView which listens to those changes:

以及监听这些更改的ListView:



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);
},
);
},
);
}
}


so far so good, when an item is added to the list in the model, the change notification triggers a rebuild of the Listview and I see the new data. But I cant wrap my head around using this with a AnimatedList instead of a ListView. Preferably id like to keep my model as it is, seeing as the animation is a concern of the ui and not of my logic.

到目前为止,当一个项目被添加到模型中的列表中时,更改通知触发Listview的重建,我看到了新的数据。但我不能将其用于AnimatedList而不是ListView。我希望我的模型保持原样,因为动画是一个用户界面的问题,而不是我的逻辑。



The changenotifier always gives me a uptodate version of my data, but what i really need is a "item added" or "item removed" notification.

ChangeNotify总是为我提供最新版本的数据,但我真正需要的是一个“已添加项目”或“已删除项目”通知。



Is there a best practice way of doing this?

有没有这样做的最佳实践方法?


更多回答

I am exactly same spot, it doesn't seem like a good fit with provider pattern and moreover - I am doing sorting on the entire list and would like some build in animation for stuff moving around . did you find any workable solution for that ?

我完全一样,它似乎不太适合提供商模式,而且-我正在对整个列表进行排序,希望在动画中构建一些东西四处移动。你找到什么可行的解决方案了吗?

优秀答案推荐

This is the result of my trial.
It's a riverpod version, but I think it's the same for providers.

这是我审判的结果。这是一个河荚版本,但我认为它对提供商来说是一样的。


There are two points.

有两点。



  1. Initialize the state in the parent widget of the widget that uses
    AnimatedList.

  2. Add / delete AnimatedList and add / delete states asynchronously by using async.


main.dart


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));
}
}

provider.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();
}
}

sample repository is here.

样本库在这里。



I recently started to learn Flutter and was surprised to find that this topic isn't covered properly anywhere. I came up with two approaches which I called Basic and Advanced. Let's start from Basic. It's named like that because Provider is called within the same widget where AnimatedList is built.

我最近开始学习扑翼,并惊讶地发现这个主题在任何地方都没有得到很好的讨论。我提出了两种方法,我称之为基本方法和高级方法。让我们从基础开始吧。之所以这样命名,是因为在构建AnimatedList的同一个小部件中调用了Provider。


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),
),
);
}
}

Advanced approach differs in that it encapsulates AnimatedListState. I took this idea from Flutter's AnimatedList docs.

高级方法的不同之处在于它封装了AnimatedListState。我是从Ffltter的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),
);
}
}

All code is published on Github. Now I want to elaborate a bit on your proposition of having "item added" or "item removed" notifications. From what I understand it goes against Flutter's philosophy where widget is a UI config. When a widget's state changes, Flutter diffs against its previous state and magically applies the diff to UI. That's why I didn't use "item added", "item removed" notifications in my implementations. However I think it should be possible to do because I saw a similar approach in Firestore subscription to document changes although for now I can't figure how to implement the same with Provider. Provider's documentation is kind of poor. After a careful reading I can't say how to implement partial updates with Provider. May be ProxyProvider with its update could help or may be ListenableProvider. Let me know if you could find the solution to your proposition.

所有代码都发布在Github上。现在我想详细说明一下您的建议,即“添加物品”或“删除物品”通知。据我所知,这与Ffltter的理念背道而驰,即Widget是一个UI配置。当窗口小部件的状态发生变化时,Ffltter会与之前的状态不同,并神奇地将DIFF应用到用户界面。这就是为什么我在我的实现中没有使用“已添加项目”和“已删除项目”通知。然而,我认为这应该是可能的,因为我在FiRestore订阅中看到了类似的文档更改方法,尽管目前我不知道如何用Provider实现相同的方法。提供商的文档有点差。仔细阅读之后,我说不出如何使用Provider实现部分更新。可能是ProxyProvider,其更新可能会有所帮助,也可能是ListenableProvider。如果你能找到你的建议的解决方案,请告诉我。



This is an old post, but I'm adding this here if anyone stumbles to this question and hasn't found a proper solution.

这是一个古老的帖子,但如果有人遇到这个问题而没有找到合适的解决方案,我会在这里补充这一点。


I wanted some form of height transition working on list of items added to a sidebar I had. It was basically a basket for a purchase. I also use Provider as a state management solution.

我想要某种形式的高度转换工作的项目列表添加到我的侧边栏。它基本上是一个购物篮子。我还使用Provider作为状态管理解决方案。


AnimatedList was not a great option here, because you need to control the number of items using the GlobalKey. My functions for adding an item and removing an item were elsewhere in the application (product cards etc.), so hooking them to the key would have been too complex.

AnimatedList在这里不是一个很好的选择,因为您需要使用GlobalKey控制条目的数量。我的添加和删除项目的函数在应用程序的其他地方(产品卡等),所以将它们挂钩到密钥上太复杂了。


So instead I opted in animating a Column() widget that was wrapping my child items, with AnimatedSize() that does as it's named, animates the height automatically.

因此,我选择为包装我的子项的Column()小部件设置动画,使用AnimatedSize()自动设置高度的动画效果。


I then added an animation to the list item widgets I had. This provides me with a height transition when I add an item to the list, and the list item itself fades in nicely with it.

然后,我将一个动画添加到我拥有的List Item小部件中。当我向列表添加项时,这为我提供了一个高度转换,列表项本身也随之淡入。


Here is an example:

下面是一个例子:


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
),
)
],
),
);
}
}

更多回答

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com