在 StreamBuilder 中使用 AnimatedList
Posted
技术标签:
【中文标题】在 StreamBuilder 中使用 AnimatedList【英文标题】:Use AnimatedList inside a StreamBuilder 【发布时间】:2021-08-27 22:24:53 【问题描述】:我正在使用 firebase 构建一个聊天应用程序,并且我目前将每条消息作为文档存储在 firebase 的集合中。我使用 StreamBuilder 获取最新消息并显示它们。我想在收到和发送新消息时添加动画。我尝试过使用 Animatedlist,但是,我不知道如何使它与 StreamBuilder 一起使用。据我了解,每次添加新消息时,我都必须调用 insertItem 函数。有更聪明的方法吗?或者这将如何实现?
这是我目前所拥有的:
class Message
final String uid;
final String message;
final Timestamp timestamp;
Message(this.uid, this.timestamp, this.message);
class MessagesWidget extends StatefulWidget
final String receiver;
MessagesWidget(@required this.receiver);
@override
_MessagesWidgetState createState() => _MessagesWidgetState();
class _MessagesWidgetState extends State<MessagesWidget>
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
Tween<Offset> _offset = Tween(begin: Offset(1,0), end: Offset(0,0));
@override
Widget build(BuildContext context)
final user = Provider.of<User>(context);
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: StreamBuilder<List<Message>>(
stream: DatabaseService(uid: user.uid).getMessages(widget.receiver),
builder: (context, snapshot)
switch (snapshot.connectionState)
case ConnectionState.waiting:
return Loading();
default:
final messages = snapshot.data;
return messages.isEmpty
? SayHi(userID: widget.receiver,)
: AnimatedList(
key: _listKey,
physics: BouncingScrollPhysics(),
reverse: true,
initialItemCount: messages.length,
itemBuilder: (context, index, animation)
final message = messages[index];
return SlideTransition(
position: animation.drive(_offset),
child: MessageWidget(
message: message,
userID: widget.receiver,
isCurrentUser: message.uid == user.uid,
),
);
,
);
),
),
SizedBox(
height: 10,
),
NewMessage(
receiver: widget.receiver,
)
],
),
);
```
【问题讨论】:
【参考方案1】:您可以将小部件的 State
更新为以下内容:
class _MessagesWidgetState extends State<MessagesWidget>
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
Tween<Offset> _offset = Tween(begin: Offset(1, 0), end: Offset(0, 0));
Stream<List<Message>> stream;
List<Message> currentMessageList = [];
User user;
@override
void initState()
super.initState();
user = Provider.of<User>(context, listen: false);
stream = DatabaseService(uid: user.uid).getMessages(widget.receiver);
stream.listen((newMessages)
final List<Message> messageList = newMessages;
if (_listKey.currentState != null &&
_listKey.currentState.widget.initialItemCount < messageList.length)
List<Message> updateList =
messageList.where((e) => !currentMessageList.contains(e)).toList();
for (var update in updateList)
final int updateIndex = messageList.indexOf(update);
_listKey.currentState.insertItem(updateIndex);
currentMessageList = messageList;
);
@override
Widget build(BuildContext context)
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: StreamBuilder<List<Message>>(
stream: stream,
builder: (context, snapshot)
switch (snapshot.connectionState)
case ConnectionState.waiting:
return Loading();
default:
final messages = snapshot.data;
return messages.isEmpty
? SayHi(
userID: widget.receiver,
)
: AnimatedList(
key: _listKey,
physics: BouncingScrollPhysics(),
reverse: true,
initialItemCount: messages.length,
itemBuilder: (context, index, animation)
final message = messages[index];
return SlideTransition(
position: animation.drive(_offset),
child: MessageWidget(
message: message,
userID: widget.receiver,
isCurrentUser: message.uid == user.uid,
),
);
,
);
),
),
SizedBox(
height: 10,
),
NewMessage(
receiver: widget.receiver,
)
],
),
);
另外,将您的 Message
类更新为以下代码:
// Using the equatable package, remember to add it to your pubspec.yaml file
import 'package:equatable/equatable.dart';
class Message extends Equatable
final String uid;
final String message;
final Timestamp timestamp;
Message(this.uid, this.timestamp, this.message);
@override
List<Object> get props => [uid, message, timestamp];
说明:
上面的State
代码执行以下操作:
-
它将当前消息存储在构建方法之外的列表
currentMessageList
中
它侦听流以获取新消息并将新列表与currentMessageList
中的前一个列表进行比较。
它获取列表和循环之间的差异,以更新特定索引updateIndex
处的AnimatedList
小部件。
上面的Message
代码执行以下操作:
==
运算符和对象hashcode
以允许在此行中进行检查:List<Message> updateList = messageList.where((e) => !currentMessageList.contains(e)).toList();
按预期工作。 [如果不覆盖这些 getter,检查将失败,因为具有相同值的两个不同 Message
对象将不等价]。
它使用equatable 包来避免样板。
【讨论】:
谢谢!这种方法似乎奏效了。但是,由于某种原因,整个列表都是动画的,而不仅仅是一条新消息。你会碰巧知道如何改变它吗?非常感谢您的详细回答。 当你说“整个列表是动画的”时,这是指屏幕第一次打开的时候还是你向集合中添加新文档的时候?另外,请附上屏幕录像,因为这将有助于调试。 我的意思是整个消息列表都是动画的,而不仅仅是添加的消息。这是因为这行代码:List<Message> updateList = (messageList.where((e) => !currentMessageList.contains(e))).toList();
updateList 始终包含流的所有元素,这意味着它们被插入到动画列表中,因此所有元素都是动画的。我不知道如何插入视频,但是,它基本上只是在我添加新消息时使整个列表动画。
你在流监听器的 initState 中有这行:currentMessageList = messageList;
吗?
是的,我愿意。我复制了您直接发送的代码,仅将两次出现的“List以上是关于在 StreamBuilder 中使用 AnimatedList的主要内容,如果未能解决你的问题,请参考以下文章
在 StreamBuilder 中使用 SnackBar 的奇特方式是啥?
在 Streambuilder 中的集合上使用 where() 不断返回 null
在 Flutter 中使用 streamBuilder 时条件不起作用