flutter聊天界面-自定义表情键盘实现
flutter聊天界面-自定义表情键盘实现
flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。
flutter开发基础腾讯IM的聊天应用,使用的是tencent_im_sdk_plugin插件。使用的是自定义表情。
一、使用的表情
1.1、自定义表情
这里使用自定义表情,表情列表如下
const emojiUrl = 'https://web.sdk.qcloud.com/im/assets/emoji/';const emojiMap = { '[NO]': 'emoji_0@2x.png', '[OK]': 'emoji_1@2x.png', '[下雨]': 'emoji_2@2x.png', '[么么哒]': 'emoji_3@2x.png', '[乒乓]': 'emoji_4@2x.png', '[便便]': 'emoji_5@2x.png', '[信封]': 'emoji_6@2x.png', '[偷笑]': 'emoji_7@2x.png', '[傲慢]': 'emoji_8@2x.png', '[再见]': 'emoji_9@2x.png', '[冷汗]': 'emoji_10@2x.png', '[凋谢]': 'emoji_11@2x.png', '[刀]': 'emoji_12@2x.png', '[删除]': 'emoji_13@2x.png', '[勾引]': 'emoji_14@2x.png', '[发呆]': 'emoji_15@2x.png', '[发抖]': 'emoji_16@2x.png', '[可怜]': 'emoji_17@2x.png', '[可爱]': 'emoji_18@2x.png', '[右哼哼]': 'emoji_19@2x.png', '[右太极]': 'emoji_20@2x.png', '[右车头]': 'emoji_21@2x.png', '[吐]': 'emoji_22@2x.png', '[吓]': 'emoji_23@2x.png', '[咒骂]': 'emoji_24@2x.png', '[咖啡]': 'emoji_25@2x.png', '[啤酒]': 'emoji_26@2x.png', '[嘘]': 'emoji_27@2x.png', '[回头]': 'emoji_28@2x.png', '[困]': 'emoji_29@2x.png', '[坏笑]': 'emoji_30@2x.png', '[多云]': 'emoji_31@2x.png', '[大兵]': 'emoji_32@2x.png', '[大哭]': 'emoji_33@2x.png', '[太阳]': 'emoji_34@2x.png', '[奋斗]': 'emoji_35@2x.png', '[奶瓶]': 'emoji_36@2x.png', '[委屈]': 'emoji_37@2x.png', '[害羞]': 'emoji_38@2x.png', '[尴尬]': 'emoji_39@2x.png', '[左哼哼]': 'emoji_40@2x.png', '[左太极]': 'emoji_41@2x.png', '[左车头]': 'emoji_42@2x.png', '[差劲]': 'emoji_43@2x.png', '[弱]': 'emoji_44@2x.png', '[强]': 'emoji_45@2x.png', '[彩带]': 'emoji_46@2x.png', '[彩球]': 'emoji_47@2x.png', '[得意]': 'emoji_48@2x.png', '[微笑]': 'emoji_49@2x.png', '[心碎了]': 'emoji_50@2x.png', '[快哭了]': 'emoji_51@2x.png', '[怄火]': 'emoji_52@2x.png', '[怒]': 'emoji_53@2x.png', '[惊恐]': 'emoji_54@2x.png', '[惊讶]': 'emoji_55@2x.png', '[憨笑]': 'emoji_56@2x.png', '[手枪]': 'emoji_57@2x.png', '[打哈欠]': 'emoji_58@2x.png', '[抓狂]': 'emoji_59@2x.png', '[折磨]': 'emoji_60@2x.png', '[抠鼻]': 'emoji_61@2x.png', '[抱抱]': 'emoji_62@2x.png', '[抱拳]': 'emoji_63@2x.png', '[拳头]': 'emoji_64@2x.png', '[挥手]': 'emoji_65@2x.png', '[握手]': 'emoji_66@2x.png', '[撇嘴]': 'emoji_67@2x.png', '[擦汗]': 'emoji_68@2x.png', '[敲打]': 'emoji_69@2x.png', '[晕]': 'emoji_70@2x.png', '[月亮]': 'emoji_71@2x.png', '[棒棒糖]': 'emoji_72@2x.png', '[汽车]': 'emoji_73@2x.png', '[沙发]': 'emoji_74@2x.png', '[流汗]': 'emoji_75@2x.png', '[流泪]': 'emoji_76@2x.png', '[激动]': 'emoji_77@2x.png', '[灯泡]': 'emoji_78@2x.png', '[炸弹]': 'emoji_79@2x.png', '[熊猫]': 'emoji_80@2x.png', '[爆筋]': 'emoji_81@2x.png', '[爱你]': 'emoji_82@2x.png', '[爱心]': 'emoji_83@2x.png', '[爱情]': 'emoji_84@2x.png', '[猪头]': 'emoji_85@2x.png', '[猫咪]': 'emoji_86@2x.png', '[献吻]': 'emoji_87@2x.png', '[玫瑰]': 'emoji_88@2x.png', '[瓢虫]': 'emoji_89@2x.png', '[疑问]': 'emoji_90@2x.png', '[白眼]': 'emoji_91@2x.png', '[皮球]': 'emoji_92@2x.png', '[睡觉]': 'emoji_93@2x.png', '[磕头]': 'emoji_94@2x.png', '[示爱]': 'emoji_95@2x.png', '[礼品袋]': 'emoji_96@2x.png', '[礼物]': 'emoji_97@2x.png', '[篮球]': 'emoji_98@2x.png', '[米饭]': 'emoji_99@2x.png', '[糗大了]': 'emoji_100@2x.png', '[红双喜]': 'emoji_101@2x.png', '[红灯笼]': 'emoji_102@2x.png', '[纸巾]': 'emoji_103@2x.png', '[胜利]': 'emoji_104@2x.png', '[色]': 'emoji_105@2x.png', '[药]': 'emoji_106@2x.png', '[菜刀]': 'emoji_107@2x.png', '[蛋糕]': 'emoji_108@2x.png', '[蜡烛]': 'emoji_109@2x.png', '[街舞]': 'emoji_110@2x.png', '[衰]': 'emoji_111@2x.png', '[西瓜]': 'emoji_112@2x.png', '[调皮]': 'emoji_113@2x.png', '[象棋]': 'emoji_114@2x.png', '[跳绳]': 'emoji_115@2x.png', '[跳跳]': 'emoji_116@2x.png', '[车厢]': 'emoji_117@2x.png', '[转圈]': 'emoji_118@2x.png', '[鄙视]': 'emoji_119@2x.png', '[酷]': 'emoji_120@2x.png', '[钞票]': 'emoji_121@2x.png', '[钻戒]': 'emoji_122@2x.png', '[闪电]': 'emoji_123@2x.png', '[闭嘴]': 'emoji_124@2x.png', '[闹钟]': 'emoji_125@2x.png', '[阴险]': 'emoji_126@2x.png', '[难过]': 'emoji_127@2x.png', '[雨伞]': 'emoji_128@2x.png', '[青蛙]': 'emoji_129@2x.png', '[面条]': 'emoji_130@2x.png', '[鞭炮]': 'emoji_131@2x.png', '[风车]': 'emoji_132@2x.png', '[飞吻]': 'emoji_133@2x.png', '[飞机]': 'emoji_134@2x.png', '[饥饿]': 'emoji_135@2x.png', '[香蕉]': 'emoji_136@2x.png', '[骷髅]': 'emoji_137@2x.png', '[麦克风]': 'emoji_138@2x.png', '[麻将]': 'emoji_139@2x.png', '[鼓掌]': 'emoji_140@2x.png', '[龇牙]': 'emoji_141@2x.png',};const emojiName = [ '[龇牙]', '[调皮]', '[流汗]', '[偷笑]', '[再见]', '[敲打]', '[擦汗]', '[猪头]', '[玫瑰]', '[流泪]', '[大哭]', '[嘘]', '[酷]', '[抓狂]', '[委屈]', '[便便]', '[炸弹]', '[菜刀]', '[可爱]', '[色]', '[害羞]', '[得意]', '[吐]', '[微笑]', '[怒]', '[尴尬]', '[惊恐]', '[冷汗]', '[爱心]', '[示爱]', '[白眼]', '[傲慢]', '[难过]', '[惊讶]', '[疑问]', '[困]', '[么么哒]', '[憨笑]', '[爱情]', '[衰]', '[撇嘴]', '[阴险]', '[奋斗]', '[发呆]', '[右哼哼]', '[抱抱]', '[坏笑]', '[飞吻]', '[鄙视]', '[晕]', '[大兵]', '[可怜]', '[强]', '[弱]', '[握手]', '[胜利]', '[抱拳]', '[凋谢]', '[米饭]', '[蛋糕]', '[西瓜]', '[啤酒]', '[瓢虫]', '[勾引]', '[OK]', '[爱你]', '[咖啡]', '[月亮]', '[刀]', '[发抖]', '[差劲]', '[拳头]', '[心碎了]', '[太阳]', '[礼物]', '[皮球]', '[骷髅]', '[挥手]', '[闪电]', '[饥饿]', '[困]', '[咒骂]', '[折磨]', '[抠鼻]', '[鼓掌]', '[糗大了]', '[左哼哼]', '[打哈欠]', '[快哭了]', '[吓]', '[篮球]', '[乒乓]', '[NO]', '[跳跳]', '[怄火]', '[转圈]', '[磕头]', '[回头]', '[跳绳]', '[激动]', '[街舞]', '[献吻]', '[左太极]', '[右太极]', '[闭嘴]', '[猫咪]', '[红双喜]', '[鞭炮]', '[红灯笼]', '[麻将]', '[麦克风]', '[礼品袋]', '[信封]', '[象棋]', '[彩带]', '[蜡烛]', '[爆筋]', '[棒棒糖]', '[奶瓶]', '[面条]', '[香蕉]', '[飞机]', '[左车头]', '[车厢]', '[右车头]', '[多云]', '[下雨]', '[钞票]', '[熊猫]', '[灯泡]', '[风车]', '[闹钟]', '[雨伞]', '[彩球]', '[钻戒]', '[沙发]', '[纸巾]', '[手枪]', '[青蛙]',];
1.2、定义自定义表情数据类
这里定义自定义表情的数据类 CommonChatEmoji
class CommonChatEmojiItem { String? emojiName; String? url; CommonChatEmojiItem({required this.emojiName, required this.url});}class CommonChatEmoji { static List<CommonChatEmojiItem> emojiUrlList() { return emojiName .map((item) => CommonChatEmojiItem( emojiName: item, url: emojiUrl + emojiMap[item]!)) .toList(); } static bool emojiIsContain(String emojiName) { bool isContain = false; CommonChatEmojiItem? emojiItem = CommonChatEmoji.findEmojiItem(emojiName); if (emojiName.contains(emojiName) && emojiItem != null) { isContain = true; } return isContain; } static CommonChatEmojiItem? findEmojiItem(String emojiName) { List<CommonChatEmojiItem> emojiItemList = CommonChatEmoji.emojiUrlList(); CommonChatEmojiItem? emojiItem; for(CommonChatEmojiItem item in emojiItemList) { if (emojiName == item.emojiName) { emojiItem = item; break; } } return emojiItem; }}
二、聊天表情键盘
2.1、实现表情排列Panel键盘
排列表情,使用的是GridView.builder,GridView网格布局是一种常见的布局类型,GridView 组件正是实现了网格布局的组件,
SliverGridDelegate是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法。Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,
GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, //每行三列 childAspectRatio: 1.0, //显示区域宽高相等 ), itemCount: CommonChatEmoji.emojiUrlList().length, itemBuilder: (context, index) { CommonChatEmojiItem emojiItem = CommonChatEmoji.emojiUrlList()[index]; return ChatInputEmojiButton( emojiItem: emojiItem, size: itemSize, onEmojiLongPressed: widget.onEmojiLongPressed, onEmojiTapPressed: widget.onEmojiTapPressed, ); }, padding: EdgeInsets.only( bottom: deleteBarHeight, ), ),
排列效果如图所示
聊天界面的表情Panel的布局完整代码
// 表情输入class ChatInputEmojiPanel extends StatefulWidget { const ChatInputEmojiPanel({ Key? key, required this.emojiPanelHeight, required this.chatInputBarController, required this.onTextFieldDelete, required this.onEmojiTapPressed, required this.onEmojiLongPressed, required this.onTextFieldSend, }) : super(key: key); final double emojiPanelHeight; final ChatInputBarController chatInputBarController; final Function onTextFieldDelete; final Function onTextFieldSend; final Function(CommonChatEmojiItem emojiItem) onEmojiTapPressed; final Function(CommonChatEmojiItem emojiItem, Offset globalPosition) onEmojiLongPressed; State<ChatInputEmojiPanel> createState() => _ChatInputEmojiPanelState();}class _ChatInputEmojiPanelState extends State<ChatInputEmojiPanel> { void initState() { // TODO: implement initState super.initState(); } void dispose() { // TODO: implement dispose super.dispose(); } Widget build(BuildContext context) { Size screenSize = MediaQuery.of(context).size; int crossAxisCount = 7; double itemSize = screenSize.width / crossAxisCount; EdgeInsets viewPadding = MediaQuery.of(context).viewPadding; double emojiCateBarHeight = 50.0 + viewPadding.bottom; double deleteBarHeight = 50.0; return Container( width: screenSize.width, height: widget.emojiPanelHeight, decoration: BoxDecoration( color: ColorUtil.hexColor(0xf7f7f7), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Stack( children: [ GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, //每行三列 childAspectRatio: 1.0, //显示区域宽高相等 ), itemCount: CommonChatEmoji.emojiUrlList().length, itemBuilder: (context, index) { CommonChatEmojiItem emojiItem = CommonChatEmoji.emojiUrlList()[index]; return ChatInputEmojiButton( emojiItem: emojiItem, size: itemSize, onEmojiLongPressed: widget.onEmojiLongPressed, onEmojiTapPressed: widget.onEmojiTapPressed, ); }, padding: EdgeInsets.only( bottom: deleteBarHeight, ), ), Positioned( bottom: 0.0, right: 0.0, child: ChatInputEmojiDeleteBar( height: deleteBarHeight, onTextFieldDelete: widget.onTextFieldDelete, ), ), ], ), ), ChatInputEmojiCateBar( height: emojiCateBarHeight, onTextFieldSend: widget.onTextFieldSend, ), ], ), ); }}// 显示表情Emoji图片class ChatInputEmojiButton extends StatelessWidget { const ChatInputEmojiButton({ Key? key, required this.emojiItem, required this.size, required this.onEmojiTapPressed, required this.onEmojiLongPressed, }) : super(key: key); final CommonChatEmojiItem emojiItem; final double size; final Function(CommonChatEmojiItem emojiItem) onEmojiTapPressed; final Function(CommonChatEmojiItem emojiItem, Offset globalPosition) onEmojiLongPressed; Widget build(BuildContext context) { double iconSize = size; if (iconSize > 36.0) { iconSize = 36.0; } return ButtonWidget( width: size, height: size, onLongPressStart: (LongPressStartDetails details) { onEmojiLongPressed(emojiItem, details.globalPosition); }, onPressed: () { onEmojiTapPressed(emojiItem); }, child: ImageHelper.imageNetwork( imageUrl: "${emojiItem.url}", fit: BoxFit.cover, width: iconSize, height: iconSize, ), ); }}
2.2、表情Panel的布局代码。
// 底部表情切换bar与发送按钮class ChatInputEmojiCateBar extends StatefulWidget { const ChatInputEmojiCateBar({ Key? key, required this.height, required this.onTextFieldSend, }) : super(key: key); final double height; final Function onTextFieldSend; State<ChatInputEmojiCateBar> createState() => _ChatInputEmojiCateBarState();}class _ChatInputEmojiCateBarState extends State<ChatInputEmojiCateBar> { Widget build(BuildContext context) { EdgeInsets viewPadding = MediaQuery.of(context).viewPadding; Size screenSize = MediaQuery.of(context).size; print("ChatInputEmojiCateBar viewPadding bottom:${viewPadding.bottom}"); return Container( width: screenSize.width, height: widget.height, decoration: BoxDecoration( color: ColorUtil.hexColor(0xf7f7f7), border: Border( bottom: BorderSide(width: 0.0, color: ColorUtil.hexColor(0xffffff)), left: BorderSide(width: 0.0, color: ColorUtil.hexColor(0xffffff)), right: BorderSide(width: 0.0, color: ColorUtil.hexColor(0xffffff)), top: BorderSide(width: 1.0, color: ColorUtil.hexColor(0xf0f0f0)), ), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ ButtonWidget( margin: EdgeInsets.only(left: 10.0), width: 36.0, onPressed: () {}, child: ImageHelper.wrapAssetAtImages( "icons/ic_custom_emoji_cate.png", fit: BoxFit.cover, width: 32.0, height: 32.0, ), ), Expanded( child: Container(), ), ButtonWidget( margin: const EdgeInsets.only(left: 10.0), width: 70.0, bgColor: ColorUtil.hexColor(0xf7f7f7), bgHighlightedColor: ColorUtil.hexColor(0x3b93ff, alpha: 0.35), onPressed: () { widget.onTextFieldSend(); }, child: Text( "发送", textAlign: TextAlign.center, maxLines: 1000, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, color: ColorUtil.hexColor(0x3b93ff), decoration: TextDecoration.none, ), ), ), ], ), ), SizedBox( height: viewPadding.bottom, ), ], ), ); }}
删除输入的表情的删除按钮
// 表情键盘底部发送及删除按钮class ChatInputEmojiDeleteBar extends StatefulWidget { const ChatInputEmojiDeleteBar({ Key? key, required this.height, required this.onTextFieldDelete, }) : super(key: key); final double height; final Function onTextFieldDelete; State<ChatInputEmojiDeleteBar> createState() => _ChatInputEmojiDeleteBarState();}class _ChatInputEmojiDeleteBarState extends State<ChatInputEmojiDeleteBar> { Widget build(BuildContext context) { return Container( padding: EdgeInsets.only(right: 10.0), color: Colors.transparent, height: widget.height, child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ ButtonWidget( onPressed: () { widget.onTextFieldDelete(); }, child: ImageHelper.wrapAssetAtImages( "icons/ic_backspace.png", fit: BoxFit.cover, width: 42.0, height: 42.0, ), ), ], ), ); }}
2.3、长按预览表情
当我们使用常见的聊天工具的时候,表情基本上都有预览功能,这里实现长按预览表情功能。
预览表情效果如下
具体代码
/// 表情长按预览功能class ChatInputEmojiPreview extends StatefulWidget { const ChatInputEmojiPreview({ Key? key, required this.emojiItem, required this.width, required this.height, }) : super(key: key); final CommonChatEmojiItem emojiItem; final double width; final double height; State<ChatInputEmojiPreview> createState() => _ChatInputEmojiPreviewState();}class _ChatInputEmojiPreviewState extends State<ChatInputEmojiPreview> { Widget build(BuildContext context) { return Container( child: ChatInputEmojiShowEmoji( emojiItem: widget.emojiItem, width: widget.width, height: widget.height, ), ); }}// 显示预览的内容class ChatInputEmojiShowEmoji extends StatelessWidget { const ChatInputEmojiShowEmoji({ Key? key, required this.emojiItem, required this.width, required this.height, }) : super(key: key); final CommonChatEmojiItem emojiItem; final double width; final double height; Widget build(BuildContext context) { return Container( width: width, height: height, child: Stack( children: [ ImageHelper.wrapAssetAtImages( "icons/bg_emoji-preview.png", width: width, height: height, ), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( height: 25.0, ), ImageHelper.imageNetwork( imageUrl: "${emojiItem.url}", fit: BoxFit.cover, width: 60, height: 60, ), SizedBox( height: 3.0, ), Text( "${emojiItem.emojiName}", textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, color: ColorUtil.hexColor(0x555555), decoration: TextDecoration.none, ), ), Expanded( child: Container(), ), ], ), ], ), ); }}
三、小结
flutter聊天界面-自定义表情键盘实现,主要实现GridView布局表情,自定义预览功能,使用GestureDetector长按功能得到LongPressStartDetails details获得长按的位置,展示表情预览、表情的图片和文本富文本展示-Text.rich(TextSpan(children: textSapns));。
学习记录,每天不停进步。
来源地址:https://blog.csdn.net/gloryFlow/article/details/131592703
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341