Flutter 实现钉钉消息列表效果(三)

505 阅读3分钟

上一篇: Flutter 实现钉钉消息列表效果(二)

依赖
flutter_slidable: ^0.5.5 # 包裹 ListView Item 实现可以左右滑动的扩展操作
实现效果如图

修改整体的 DTMessageScreen 布局
  • 使用 SingleChildScrollView + Column
@override
  Widget 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
@override
  Widget 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});

  /// 聊天 Id
  String chatId;

  /// 用户名称
  String userName;

  /// 用户Id
  String userId;

  /// 聊天名称
  String chatName;

  /// 消息体
  String message;

  /// message type
  DTMessageType 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});
    
      @override
      Widget 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 组件来显示
    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<String> 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),
                ),
              ),
            );
          },
        );
      }
    }
    

代码地址: gitee.com/shizidada/f…

列表 Mock 数据: gitee.com/shizidada/f…