Flutter chat interface - custom emoticon keyboard implementation

Flutter chat interface-custom emoticon keyboard to realize
flutter is an open source mobile application development framework launched by Google, which focuses on cross-platform, high-fidelity, and high-performance. Developers can develop apps through the Dart language, and a set of codes runs on both iOS and Android platforms.

Flutter develops the basic Tencent IM chat application, using the tencent_im_sdk_plugin plug-in. Using a custom emoticon.

insert image description here

1. The expressions used

1.1. Custom emoticons

Use custom emoticons here, the list of emoticons is as follows

const emojiUrl = 'https://web.sdk.qcloud.com/im/assets/emoji/';

const emojiMap = {
    
    
  '[NO]': '[email protected]',
  '[OK]': '[email protected]',
  '[下雨]': '[email protected]',
  '[么么哒]': '[email protected]',
  '[乒乓]': '[email protected]',
  '[便便]': '[email protected]',
  '[信封]': '[email protected]',
  '[偷笑]': '[email protected]',
  '[傲慢]': '[email protected]',
  '[再见]': '[email protected]',
  '[冷汗]': '[email protected]',
  '[凋谢]': '[email protected]',
  '[刀]': '[email protected]',
  '[删除]': '[email protected]',
  '[勾引]': '[email protected]',
  '[发呆]': '[email protected]',
  '[发抖]': '[email protected]',
  '[可怜]': '[email protected]',
  '[可爱]': '[email protected]',
  '[右哼哼]': '[email protected]',
  '[右太极]': '[email protected]',
  '[右车头]': '[email protected]',
  '[吐]': '[email protected]',
  '[吓]': '[email protected]',
  '[咒骂]': '[email protected]',
  '[咖啡]': '[email protected]',
  '[啤酒]': '[email protected]',
  '[嘘]': '[email protected]',
  '[回头]': '[email protected]',
  '[困]': '[email protected]',
  '[坏笑]': '[email protected]',
  '[多云]': '[email protected]',
  '[大兵]': '[email protected]',
  '[大哭]': '[email protected]',
  '[太阳]': '[email protected]',
  '[奋斗]': '[email protected]',
  '[奶瓶]': '[email protected]',
  '[委屈]': '[email protected]',
  '[害羞]': '[email protected]',
  '[尴尬]': '[email protected]',
  '[左哼哼]': '[email protected]',
  '[左太极]': '[email protected]',
  '[左车头]': '[email protected]',
  '[差劲]': '[email protected]',
  '[弱]': '[email protected]',
  '[强]': '[email protected]',
  '[彩带]': '[email protected]',
  '[彩球]': '[email protected]',
  '[得意]': '[email protected]',
  '[微笑]': '[email protected]',
  '[心碎了]': '[email protected]',
  '[快哭了]': '[email protected]',
  '[怄火]': '[email protected]',
  '[怒]': '[email protected]',
  '[惊恐]': '[email protected]',
  '[惊讶]': '[email protected]',
  '[憨笑]': '[email protected]',
  '[手枪]': '[email protected]',
  '[打哈欠]': '[email protected]',
  '[抓狂]': '[email protected]',
  '[折磨]': '[email protected]',
  '[抠鼻]': '[email protected]',
  '[抱抱]': '[email protected]',
  '[抱拳]': '[email protected]',
  '[拳头]': '[email protected]',
  '[挥手]': '[email protected]',
  '[握手]': '[email protected]',
  '[撇嘴]': '[email protected]',
  '[擦汗]': '[email protected]',
  '[敲打]': '[email protected]',
  '[晕]': '[email protected]',
  '[月亮]': '[email protected]',
  '[棒棒糖]': '[email protected]',
  '[汽车]': '[email protected]',
  '[沙发]': '[email protected]',
  '[流汗]': '[email protected]',
  '[流泪]': '[email protected]',
  '[激动]': '[email protected]',
  '[灯泡]': '[email protected]',
  '[炸弹]': '[email protected]',
  '[熊猫]': '[email protected]',
  '[爆筋]': '[email protected]',
  '[爱你]': '[email protected]',
  '[爱心]': '[email protected]',
  '[爱情]': '[email protected]',
  '[猪头]': '[email protected]',
  '[猫咪]': '[email protected]',
  '[献吻]': '[email protected]',
  '[玫瑰]': '[email protected]',
  '[瓢虫]': '[email protected]',
  '[疑问]': '[email protected]',
  '[白眼]': '[email protected]',
  '[皮球]': '[email protected]',
  '[睡觉]': '[email protected]',
  '[磕头]': '[email protected]',
  '[示爱]': '[email protected]',
  '[礼品袋]': '[email protected]',
  '[礼物]': '[email protected]',
  '[篮球]': '[email protected]',
  '[米饭]': '[email protected]',
  '[糗大了]': '[email protected]',
  '[红双喜]': '[email protected]',
  '[红灯笼]': '[email protected]',
  '[纸巾]': '[email protected]',
  '[胜利]': '[email protected]',
  '[色]': '[email protected]',
  '[药]': '[email protected]',
  '[菜刀]': '[email protected]',
  '[蛋糕]': '[email protected]',
  '[蜡烛]': '[email protected]',
  '[街舞]': '[email protected]',
  '[衰]': '[email protected]',
  '[西瓜]': '[email protected]',
  '[调皮]': '[email protected]',
  '[象棋]': '[email protected]',
  '[跳绳]': '[email protected]',
  '[跳跳]': '[email protected]',
  '[车厢]': '[email protected]',
  '[转圈]': '[email protected]',
  '[鄙视]': '[email protected]',
  '[酷]': '[email protected]',
  '[钞票]': '[email protected]',
  '[钻戒]': '[email protected]',
  '[闪电]': '[email protected]',
  '[闭嘴]': '[email protected]',
  '[闹钟]': '[email protected]',
  '[阴险]': '[email protected]',
  '[难过]': '[email protected]',
  '[雨伞]': '[email protected]',
  '[青蛙]': '[email protected]',
  '[面条]': '[email protected]',
  '[鞭炮]': '[email protected]',
  '[风车]': '[email protected]',
  '[飞吻]': '[email protected]',
  '[飞机]': '[email protected]',
  '[饥饿]': '[email protected]',
  '[香蕉]': '[email protected]',
  '[骷髅]': '[email protected]',
  '[麦克风]': '[email protected]',
  '[麻将]': '[email protected]',
  '[鼓掌]': '[email protected]',
  '[龇牙]': '[email protected]',
};

const emojiName = [
  '[龇牙]',
  '[调皮]',
  '[流汗]',
  '[偷笑]',
  '[再见]',
  '[敲打]',
  '[擦汗]',
  '[猪头]',
  '[玫瑰]',
  '[流泪]',
  '[大哭]',
  '[嘘]',
  '[酷]',
  '[抓狂]',
  '[委屈]',
  '[便便]',
  '[炸弹]',
  '[菜刀]',
  '[可爱]',
  '[色]',
  '[害羞]',
  '[得意]',
  '[吐]',
  '[微笑]',
  '[怒]',
  '[尴尬]',
  '[惊恐]',
  '[冷汗]',
  '[爱心]',
  '[示爱]',
  '[白眼]',
  '[傲慢]',
  '[难过]',
  '[惊讶]',
  '[疑问]',
  '[困]',
  '[么么哒]',
  '[憨笑]',
  '[爱情]',
  '[衰]',
  '[撇嘴]',
  '[阴险]',
  '[奋斗]',
  '[发呆]',
  '[右哼哼]',
  '[抱抱]',
  '[坏笑]',
  '[飞吻]',
  '[鄙视]',
  '[晕]',
  '[大兵]',
  '[可怜]',
  '[强]',
  '[弱]',
  '[握手]',
  '[胜利]',
  '[抱拳]',
  '[凋谢]',
  '[米饭]',
  '[蛋糕]',
  '[西瓜]',
  '[啤酒]',
  '[瓢虫]',
  '[勾引]',
  '[OK]',
  '[爱你]',
  '[咖啡]',
  '[月亮]',
  '[刀]',
  '[发抖]',
  '[差劲]',
  '[拳头]',
  '[心碎了]',
  '[太阳]',
  '[礼物]',
  '[皮球]',
  '[骷髅]',
  '[挥手]',
  '[闪电]',
  '[饥饿]',
  '[困]',
  '[咒骂]',
  '[折磨]',
  '[抠鼻]',
  '[鼓掌]',
  '[糗大了]',
  '[左哼哼]',
  '[打哈欠]',
  '[快哭了]',
  '[吓]',
  '[篮球]',
  '[乒乓]',
  '[NO]',
  '[跳跳]',
  '[怄火]',
  '[转圈]',
  '[磕头]',
  '[回头]',
  '[跳绳]',
  '[激动]',
  '[街舞]',
  '[献吻]',
  '[左太极]',
  '[右太极]',
  '[闭嘴]',
  '[猫咪]',
  '[红双喜]',
  '[鞭炮]',
  '[红灯笼]',
  '[麻将]',
  '[麦克风]',
  '[礼品袋]',
  '[信封]',
  '[象棋]',
  '[彩带]',
  '[蜡烛]',
  '[爆筋]',
  '[棒棒糖]',
  '[奶瓶]',
  '[面条]',
  '[香蕉]',
  '[飞机]',
  '[左车头]',
  '[车厢]',
  '[右车头]',
  '[多云]',
  '[下雨]',
  '[钞票]',
  '[熊猫]',
  '[灯泡]',
  '[风车]',
  '[闹钟]',
  '[雨伞]',
  '[彩球]',
  '[钻戒]',
  '[沙发]',
  '[纸巾]',
  '[手枪]',
  '[青蛙]',
];

1.2. Define a custom expression data class

Define the data class CommonChatEmoji for custom expressions here

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. Chat Emoji Keyboard

2.1. Realize expression arrangement Panel keyboard

Arrange emoticons, use GridView.builder, GridView grid layout is a common layout type, GridView component is the component that implements grid layout, SliverGridDelegate is
an abstract class that defines GridView Layout related interfaces, subclasses need Implement specific layout algorithms by implementing them. Flutter provides two subclasses of SliverGridDelegate, SliverGridDelegateWithFixedCrossAxisCount and 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,
              ),
            ),

The arrangement effect is shown in the figure
insert image description here

The complete code of the layout of the emoticon Panel in the chat interface

// 表情输入
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. The layout code of the emoticon 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,
          ),
        ],
      ),
    );
  }
}

Delete button to delete entered emoji

// 表情键盘底部发送及删除按钮
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. Long press to preview emoticons

When we use common chat tools, emoticons basically have a preview function, here is a long press preview emoticon function.
The preview expression effect is as follows
insert image description here

specific code

/// 表情长按预览功能
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(),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

3. Summary

Flutter chat interface - custom emoticon keyboard implementation, mainly to implement GridView layout emoticons, custom preview function, use GestureDetector long press function to get LongPressStartDetails details get long press position, display emoticon preview, emoticon pictures and text rich text display-Text. rich(TextSpan(children: textSapns));.

Learning records, keep improving every day.

Guess you like

Origin blog.csdn.net/gloryFlow/article/details/131592703