在 StreamBuilder 中使用 AnimatedList
Use AnimatedList inside a StreamBuilder
我正在使用 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,
)
],
),
);
}
}```
您可以将小部件的 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
class 更新为以下代码:
// 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 包来避免样板。
我正在使用 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,
)
],
),
);
}
}```
您可以将小部件的 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
class 更新为以下代码:
// 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 包来避免样板。