Implement a pinned message list effect with Flutter

Rely on
flutter_slidable: ^ 0.5.5 # wrap the ListView Item to implement the extended operation of sliding left and right
Copy the code
The result is shown in figure

Modify integralDTMessageScreenlayout
  • Use 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(),
              ],
          ),
      )
    );
  }
Copy the code
Implement a DTMessageListView message list
  • Listview. builder to build a ListView
  • The view displays different items based on the data model
  • Create the DTMessageModel data model
  • Add extensions to third Party Slidable (delete, untop)
    • ListView set
      • ShrinkWrap: true applies to parent containers
      • Physics: NeverScrollableScrollPhysics () and banned the ListView itself scroll effect, use the parent component of rolling, because the father is SingleChildScrollView components
@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: 'Cancel top',
              color: Colors.black45,
              icon: Icons.more_horiz,
              onTap: () => _showSnackBar('More'),
            ),
            IconSlideAction(
              caption: 'delete',
              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(),
    );
  }
Copy the code
Building a data model
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});

  /// Chat Id
  String chatId;

  /// The user name
  String userName;

  /// The user Id
  String userId;

  /// The name of the chat
  String chatName;

  /// The message body
  String message;

  /// message type
  DTMessageType messageType;

  /// time
  int time;

  /// Did not read the number
  int unReadCount;

  /// Single chat
  bool isSingle;
  String avatar;

  /// Group chat message
  bool isGroup;
  List<String> groupAvatars;

  /// Message Do not Disturb
  bool isDisturbing;

  /// Whether it is the top
  bool isStick;

  /// Pay special attention to
  bool isSpecialAttention;

  /// Whether @ you
  bool isAtYou;

  /// Whether @ all
  bool isAtAll;
}
Copy the code
Create the DTMessageItem view
  • Display according to data model data

    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 ? "[@ you]" : "",
                              style:
                              TextStyle(color: kColorF1A33A, fontSize: kSize28),
                            ),
                            TextSpan(
                              text: messageModel.isSpecialAttention ? "[Special concern]" : "",
                              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]) ?? ""; }}Copy the code
  • Build image

    • Distinguish between personal and group avatars
    • The group avatar is displayed using the NineGridView component
    class DTMessageAvatar extends StatelessWidget {
      final DTMessageModel messageModel;
    
      DTMessageAvatar({this.messageModel});
    
      @override
      Widget build(BuildContext context) {
        return messageModel.isSingle ? _buildSingleAvatar() : _buildGroupAvatar();
      }
    
      Widget _buildSingleAvatar() {
        returnContainer( 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), ), ), ); }); }}Copy the code

Code address: gitee.com/shizidada/f…

List Mock data: gitee.com/shizidada/f…