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
- ListView set
@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…