在上一篇文章中,我们完成了基本的添加聊天功能,但是还没有在聊天列表显示添加的新聊天,在这篇文章中我们将实现这个功能——在聊天列表中展示所有的聊天。
首先,我们在/lib
目录下新建一个group_chat_list_body.dart
文件。在group_chat_list_body.dart
中编写一个有状态控件GroupChatListBody
,传递两个参数,phone
(手机号码)和myName
(姓名),手机号码将用于在Firebase实时数据库中访问“/chats/$phone”,以读取当前用户的聊天列表。
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_database/ui/firebase_animated_list.dart';
import 'chat_screen.dart';
import 'prompt_wait.dart';
class GroupChatListBody extends StatefulWidget {
GroupChatListBody({
this.phone,
this.myName,
Key key,
})
: super(key: key);
final String phone;
final String myName;
@override
_GroupChatListBodyState createState() => new _GroupChatListBodyState(phone);
}
class _GroupChatListBodyState extends State<GroupChatListBody> {
_GroupChatListBodyState(this._phone);
final String _phone;
@override
Widget build(BuildContext context) {
return new Text("聊天列表");
}
}
回到group_chat_list.dart
文件中,在_GroupChatListState
中修改一下build
方法,使用我们在前面自定义的有状态控件GroupChatListBody
。如果参数phone
为空,说明还没有读取到用户的手机号码,则暂时显示空白,直到参数phone
不为空才显示GroupChatListBody
控件。
class _GroupChatListState extends State<GroupChatList> {
//...
Widget build(BuildContext context) {
//...
return new Scaffold(
appBar: new AppBar(
title: new Text("纸聊"),
centerTitle: true,
elevation: 0.0,
),
drawer: drawer,
body: new Center(
child: phone == "null"
? null
: new GroupChatListBody(phone: phone, myName: name),
),
//...
);
}
}
再回到group_chat_list_body.dart
文件中,我们新创建一个GroupChatListBodyItem
类,这是一个无状态控件,用于展示具体的一个聊天。这个控件所需要的参数有点多,name
(对方的名称)、lastMessage
(最新的消息)、timestamp
(时间戳)、messages
(“/messages/$messages”)、myName
(名称)、myPphone
(手机号码)和shePphone
(对方的手机号码)。
GroupChatListBodyItem
的顶层是一个GestureDetector
控件,其onTap
属性是一个匿名方法,内容是导航到一个新屏幕,而新屏幕的内容是ChatScreen
控件。ChatScreen
控件是应用程序的第一个屏幕,我们只需要修改一下里面的内容就可以直接调用,具体的修改会在下一篇文章中讲解,或者可以在GitHub上直接查看。
class GroupChatListBodyItem extends StatelessWidget {
GroupChatListBodyItem(
{this.name,
this.lastMessage,
this.timestamp,
this.messages,
this.myName,
this.myPphone,
this.shePphone});
final String name;
final String lastMessage;
final String timestamp;
final String messages;
final String myName;
final String myPphone;
final String shePphone;
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
Navigator.of(context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return new ChatScreen(
messages: messages,
myName: myName,
sheName: name,
myPphone: myPphone,
shePphone: shePphone);
},
));
},
child: new Container(
decoration: new BoxDecoration(),
padding: new EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: new Row(
children: <Widget>[
new CircleAvatar(
child: new Text(name[0]),
backgroundColor: Theme.of(context).buttonColor),
new Flexible(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(" " + name, textScaleFactor: 1.2),
new Text(ReadableTime(timestamp),
textAlign: TextAlign.right,
style: new TextStyle(
color: Theme.of(context).hintColor)),
]),
new Container(
padding: new EdgeInsets.only(top: 2.0),
child: new Text(" " + lastMessage,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
color: Theme.of(context).hintColor))),
],
))
],
)));
}
}
前面的GroupChatListBodyItem
中有一个timestamp
参数,在Firebase实时数据库是以“2017-08-14 17:16:11.65”的时间戳格式保存的,并非直观的可读格式,因此我们需要进行格式转换。在prompt_wait.dart
文件中写一个通用方法ReadableTime
,将给定的时间戳转换成直观的可读形式,在prompt_wait.dart
中添加以下代码。
String ReadableTime(String timestamp) {
List<String> timeList = timestamp.split(" ");
List<String> times = timeList[1].split(":");
String time;
if (new DateTime.now().toString().split(" ")[0] == timeList[0]) {
if (int.parse(times[0]) < 6) {
time = "凌晨${times[0]}:${times[1]}";
} else if (int.parse(times[0]) < 12) {
time = "上午${times[0]}:${times[1]}";
} else if (int.parse(times[0]) == 12) {
time = "中午${times[0]}:${times[1]}";
} else {
time =
"下午${(int.parse(times[0])- 12).toString().padLeft(2,'0')}:${times[1]}";
}
} else {
time = timeList[0];
}
return time;
}
在_GroupChatListBodyState
中覆盖initState
方法,使控件在初始化时访问Firebase实时数据库中的“chats/$_phone”。将setPersistenceEnabled
设置为true
,开启数据库的持久性设置,同时调用keepSynced(true)
,使数据将自动下载并保持同步。
class _GroupChatListBodyState extends State<GroupChatListBody> {
//...
DatabaseReference _chatsReference;
@override
void initState() {
super.initState();
_chatsReference = FirebaseDatabase.instance.reference().child('chats/$_phone');
FirebaseDatabase.instance.setPersistenceEnabled(true);
_chatsReference.keepSynced(true);
}
//...
}
最后修改_GroupChatListBodyState
中的build
方法,使用FirebaseAnimatedList
控件读取Firebase实时数据库。参数query
设置查询位置,sort
设置排序,这里设置成按时间戳排序,defaultChild
设置读取时的等待动画。itemBuilder
会遍历一次查询到的数据,将这些数据分别创建一个GroupChatListBodyItem
控件。
class _GroupChatListBodyState extends State<GroupChatListBody> {
//...
@override
Widget build(BuildContext context) {
return new FirebaseAnimatedList(
query: _chatsReference,
sort: (DataSnapshot a, DataSnapshot b) =>
b.value["timestamp"].compareTo(a.value["timestamp"]),
defaultChild: new CircularProgressIndicator(),
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation<double> animation) {
return new SizeTransition(
sizeFactor: animation,
child: new GroupChatListBodyItem(
name: snapshot.value["name"],
lastMessage: snapshot.value["lastMessage"],
timestamp: snapshot.value["timestamp"],
messages: snapshot.value["messages"],
myName: widget.myName,
myPphone: _phone,
shePphone: snapshot.value["phone"],
),
);
},
);
}
}
大家可以在GitHub上直接查看group_chat_list_body.dart文件、group_chat_list.dart文件和chat_screen.dart文件的代码。