[英]Use AnimatedList inside a StreamBuilder
I am building a chat app with firebase and I am currently storing each message as a document inside a collection in firebase. I use a StreamBuilder to get the latest messages and display them.我正在使用 firebase 构建聊天应用程序,目前我将每条消息作为文档存储在 firebase 中的集合中。我使用 StreamBuilder 获取最新消息并显示它们。 I want to add an animation when a new message is received and sent.我想在收到和发送新消息时添加一个 animation。 I have tried using an Animatedlist, however, I don't get how to make it work with a StreamBuilder.我尝试过使用 Animatedlist,但是,我不知道如何让它与 StreamBuilder 一起工作。 As far as I understand I would have to call the insertItem function each time a new message is added.据我了解,每次添加新消息时我都必须调用insertItem function。 Is there a smarter way to do it?有更聪明的方法吗? Or how would this be implemented?或者这将如何实施?
This is what I have so far:这是我到目前为止所拥有的:
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,
)
],
),
);
}
}```
You can update your widget's State
to this below:您可以将小部件的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,
)
],
),
);
}
}
Also, update your Message
class to the code below:此外,将您的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];
}
Explanation:解释:
The State
code above does the following:上面的State
代码执行以下操作:
currentMessageList
outside the build method它将当前消息存储在 build 方法之外的列表currentMessageList
中currentMessageList
.它侦听 stream 以获取新消息,并将新列表与currentMessageList
中的前一个列表进行比较。AnimatedList
widget at the specific index updateIndex
.它通过在特定索引updateIndex
处更新AnimatedList
小部件来获取列表和循环之间的差异。 The Message
code above does the following:上面的Message
代码执行以下操作:
==
operator and the object hashcode
to allow the check in this line: List<Message> updateList = messageList.where((e) =>.currentMessageList.contains(e));toList();
它覆盖==
运算符和 object hashcode
码以允许在此行中进行检查: List<Message> updateList = messageList.where((e) =>.currentMessageList.contains(e));toList();
work as intended.按预期工作。 [Without overriding these getters, the check would fail as two different Message
objects with the same values would not be equivalent]. [如果不覆盖这些 getter,检查将失败,因为具有相同值的两个不同Message
对象将不等价]。I wrote an abstraction that I am using to animate a list of tasks - it uses SliverAnimatedList but you can adapt that for a regular animated list:我写了一个抽象,我用它来制作任务列表的动画 - 它使用 SliverAnimatedList 但你可以将它改编为常规动画列表:
// if you had a class for your list data like
class MyItem {
const MyItem({ required this.id, required this.name });
final String id;
final String name;
}
// then your build method could be like this
Widget build(BuildContext context) {
return StreamSliverAnimatedListBuilder<MyItem>(
stream: _loadData(),
build: (context, item, animation) {
return FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
child: ListTile(title: Text(item.name)),
),
);
},
compare: (p0, p1) => p0.id == p1.id
);
}
Widget code小部件代码
import 'package:flutter/widgets.dart';
class StreamSliverAnimatedListBuilder<T> extends StatefulWidget {
const StreamSliverAnimatedListBuilder(
{super.key,
required this.stream,
required this.build,
required this.compare,
this.fallback});
final Stream<List<T>> stream;
final Widget Function(
BuildContext context, T item, Animation<double> animation) build;
final bool Function(T, T) compare;
final Widget? fallback;
@override
State<StatefulWidget> createState() {
return _StreamSliverAnimatedListBuilderState<T>();
}
}
class _StreamSliverAnimatedListBuilderState<T>
extends State<StreamSliverAnimatedListBuilder<T>> {
late final GlobalObjectKey<SliverAnimatedListState> _listKey =
GlobalObjectKey<SliverAnimatedListState>(this);
List<T> _currentList = [];
bool _hasData = false;
@override
void initState() {
super.initState();
widget.stream.listen((event) {
final List<T> newList = event;
if (_hasData && _listKey.currentState != null) {
List<T> addedItems = newList
.where((a) => !_currentList.any((b) => widget.compare(a, b)))
.toList();
for (var update in addedItems) {
final int updateIndex = newList.indexOf(update);
_listKey.currentState!.insertItem(updateIndex);
}
List<T> removedItems = _currentList
.where((a) => !newList.any((b) => widget.compare(a, b)))
.toList();
for (var update in removedItems) {
final int updateIndex = _currentList.indexOf(update);
_listKey.currentState!.removeItem(updateIndex, (context, animation) {
return widget.build(context, update, animation);
});
}
}
_currentList = newList;
if (!_hasData) {
setState(() {
_hasData = true;
});
}
});
}
@override
Widget build(BuildContext context) {
if (!_hasData) {
return SliverToBoxAdapter(child: widget.fallback);
}
return SliverAnimatedList(
key: _listKey,
initialItemCount: _currentList.length,
itemBuilder: (context, index, animation) {
if (_currentList.length <= index) {
return Text('what. $index');
}
final item = _currentList[index];
return widget.build(context, item, animation);
},
);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.