上一篇: Flutter 实现钉钉消息列表效果(二)
依赖
flutter_slidable: ^0.5.5 # 包裹 ListView Item 实现可以左右滑动的扩展操作
实现效果如图

修改整体的 DTMessageScreen 布局
- 使用 SingleChildScrollView + Column
@overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: Colors.white,appBar: buildAppBar(context),body: SingleChildScrollView(controller: _scrollController,child: Column(children: <Widget>[DTMessageSearchDecoration(),DTMessageTopQuick(),DTMessageTopMask(),DTMessageListView(),],),));}
实现一个 DTMessageListView 消息列表
- ListView.builder 来构建出 ListView
- 视图显示根据数据模型来显示不同的 Item
- 创建 DTMessageModel 数据模型
第三方 Slidable 添加扩展操作 (删除、取消置顶)
ListView 设置
- shrinkWrap: true 适应父容器
- physics: NeverScrollableScrollPhysics() ,禁止 ListView 自身的滚动效果,使用父组件的滚动,因为父组件是 SingleChildScrollView
@overrideWidget build(BuildContext context) {return ListView.builder(itemCount: _messageList.length,itemBuilder: (BuildContext context, int index) {DTMessageModel model = _messageList[index];return Slidable(actionPane: SlidableDrawerActionPane(),actionExtentRatio: 0.25,secondaryActions: <Widget>[IconSlideAction(caption: '取消置顶',color: Colors.black45,icon: Icons.more_horiz,onTap: () => _showSnackBar('More'),),IconSlideAction(caption: '删除',color: Colors.redAccent,icon: Icons.delete,onTap: () => _showSnackBar('Delete'),),],child: Padding(padding:EdgeInsets.only(left: kSize36, bottom: kSize20, top: kSize20),child: Row(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[Expanded(child: DTMessageItem(messageModel: model)),model.isStick? Container(width: kSize48,height: kSize48,margin: EdgeInsets.only(top: kSize10, right: kSize10),child:SvgPicture.asset('assets/icons/icon_triangle.svg'),): Padding(padding: EdgeInsets.symmetric(horizontal: kSize24),)],),),);},shrinkWrap: true,physics: NeverScrollableScrollPhysics(),);}
构建数据模型
enum DTMessageType { text, image, file, location, video, voice }class DTMessageModel {DTMessageModel({@required this.chatId,@required this.userId,@required this.userName,@required this.chatName,@required this.message,this.messageType = DTMessageType.text,this.time,this.unReadCount = 0,this.isSingle = false,this.avatar,this.isGroup = false,this.groupAvatars,this.isDisturbing = false,this.isSpecialAttention = false,this.isAtYou = false,this.isAtAll = false,this.isStick = false});/// 聊天 IdString chatId;/// 用户名称String userName;/// 用户IdString userId;/// 聊天名称String chatName;/// 消息体String message;/// message typeDTMessageType messageType;/// 时间int time;/// 未读数量int unReadCount;/// 单聊bool isSingle;String avatar;/// 群聊信息bool isGroup;List<String> groupAvatars;/// 消息免打扰bool isDisturbing;/// 是否为置顶bool isStick;/// 特别关注bool isSpecialAttention;/// 是否 @ 你bool isAtYou;/// 是否 @ 全部bool isAtAll;}
创建 DTMessageItem 视图
根据数据模型数据来显示
class DTMessageItem extends StatelessWidget {final DTMessageModel messageModel;DTMessageItem({@required this.messageModel});@overrideWidget build(BuildContext context) {return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[DTMessageAvatar(messageModel: messageModel),Expanded(child: Column(children: <Widget>[Padding(padding: EdgeInsets.symmetric(vertical: kSize8),child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[Expanded(child: Text(messageModel?.chatName ?? "",style: TextStyle(color: kColor33, fontSize: kSize34),overflow: TextOverflow.ellipsis,),),Text(_formatDate(),style: TextStyle(color: kColor99, fontSize: kSize26),overflow: TextOverflow.ellipsis,),],),),Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[Expanded(child: RichText(text: TextSpan(children: [TextSpan(text: messageModel.isAtYou ? "[@你]" : "",style:TextStyle(color: kColorF1A33A, fontSize: kSize28),),TextSpan(text: messageModel.isSpecialAttention ? "[特别关注]" : "",style:TextStyle(color: kColorF1A33A, fontSize: kSize28),),TextSpan(text: messageModel.isAtAll ? "[@所有人]" : "",style:TextStyle(color: kColorF1A33A, fontSize: kSize28),),TextSpan(text: messageModel?.message ?? "",style: TextStyle(color: kColor99, fontSize: kSize28),)]),overflow: TextOverflow.ellipsis,),),(messageModel.unReadCount != null &&messageModel.unReadCount > 0 &&!messageModel.isDisturbing)? Container(width: kSize32,height: kSize32,alignment: Alignment.center,decoration: BoxDecoration(color: kColorE86A3E,borderRadius: BorderRadius.circular(kSize20)),child: Text(messageModel.unReadCount.toString(),style: TextStyle(color: Colors.white, fontSize: kSize26),)): Container(),messageModel.isDisturbing? Row(children: <Widget>[SvgPicture.asset('assets/icons/icon_disturbing.svg',width: kSize36,color: kColorECEDED,),messageModel.unReadCount != null &&messageModel.unReadCount > 0? SvgPicture.asset('assets/icons/icon_red_dot.svg',width: kSize36,): Container()],): Container()],)],),)],);}String _formatDate() {DateTime dateTime =DateTime.fromMillisecondsSinceEpoch(messageModel?.time ?? 0);return formatDate(dateTime, [HH, ':', nn]) ?? "";}}
构建头像
- 区分个人头像还是群组头像
- 群组头像使用 NineGridView 组件来显示 ```dart class DTMessageAvatar extends StatelessWidget { final DTMessageModel messageModel;
DTMessageAvatar({this.messageModel});
@override Widget build(BuildContext context) { return messageModel.isSingle ? _buildSingleAvatar() : _buildGroupAvatar(); }
Widget _buildSingleAvatar() { return Container(
margin: EdgeInsets.only(right: kSize24),child: (messageModel.isSingle && messageModel.chatName != null)? CircleAvatar(radius: kSize50,child: Text(_getChatName(),style: TextStyle(color: Colors.white, fontSize: kSize32),),backgroundColor: kPrimaryColor,): Image.asset('assets/images/track_icon_attend.png',fit: BoxFit.cover),
); }
String _getChatName() { String chatName = messageModel.chatName; if (chatName == null || chatName.isEmpty) {
return "(^_^)";
}
chatName = chatName.replaceAll(‘\(’, “”).replaceAll(‘\)’, “”);
/// RegExp regExp = RegExp(r’\(|\)’); /// chatName = chatName.replaceAll(regExp, “”); /// print(chatName);
if (chatName.length == 2) {
return chatName;
}
if (chatName.length > 2) {
return chatName.substring(chatName.length - 2, chatName.length);
} return “”; }
Widget _buildGroupAvatar() { List
groupAvatars = messageModel.groupAvatars; if (groupAvatars.length >= 4) { groupAvatars = groupAvatars.sublist(0, 4);
} return NineGridView(
width: kSize100,height: kSize100,type: NineGridType.dingTalkGp,itemCount: groupAvatars.length,space: kSizeDot5,margin: EdgeInsets.only(right: kSize24),itemBuilder: (BuildContext context, int index) {String userName = groupAvatars[index];return Container(color: kPrimaryColor,/// TODO: position ???alignment: Alignment.center,child: Container(margin: EdgeInsets.symmetric(horizontal: kSize6),child: Text(userName.length >= 2 ? userName.substring(1, 2) : userName,style: TextStyle(color: Colors.white),),),);},
); } } ```
代码地址: https://gitee.com/shizidada/flutter_dingtalk/blob/master/lib/ui/screens/message/message_screen.dart
列表 Mock 数据: https://gitee.com/shizidada/flutter_dingtalk/blob/master/lib/mock/mock_message.dart
