一、依赖与预览

  • get: 4.1.4
  • video_player: ^2.1.1 /// 播放视频
  • emoji_picker_flutter: ^1.0.5 /// 表情库
  • flutter_keyboard_visibility: ^5.0.3 /// 监听键盘弹起

有关IM相关的请看《Flutter融云接入部分》

预览

二、使用

  • _messageText:TextEditingController()类型

1. 表情部分展示

import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';///选中表情
_onEmojiSelected(Emoji emoji) {_messageText..text += emoji.emoji..selection = TextSelection.fromPosition(TextPosition(offset: _messageText.text.length));
}///表情删除按钮
_onBackspacePressed() {_messageText..text = _messageText.text.characters.skipLast(1).toString()..selection = TextSelection.fromPosition(TextPosition(offset: _messageText.text.length));
}EmojiPicker(onEmojiSelected: (Category category, Emoji emoji) {/// 当表情被选择后自定义方法操作_onEmojiSelected(emoji);},onBackspacePressed: _onBackspacePressed,config: const Config(columns: 7,emojiSizeMax: 25.0,verticalSpacing: 0,horizontalSpacing: 0,initCategory: Category.RECENT,bgColor: Color(0xFFF2F2F2),indicatorColor: Color(0xff65DAC5),iconColor: Colors.orange,iconColorSelected: Color(0xff65DAC5),progressIndicatorColor: Color(0xff65DAC5),backspaceColor: Color(0xff65DAC5),showRecentsTab: true,recentsLimit: 28,noRecentsText: 'No Recents',noRecentsStyle:TextStyle(fontSize: 20, color: Colors.black26),categoryIcons: CategoryIcons(),buttonMode: ButtonMode.MATERIAL)
)

2. 初始化背景视频

import 'package:video_player/video_player.dart';late VideoPlayerController _controller; //背景视频播放控制器@override
void initState() {///初始化视频播放_controller = VideoPlayerController.network('https://gugu-1300042725.cos.ap-shanghai.myqcloud.com/0_szDjEDn.mp4');_controller.addListener(() {setState(() {});});_controller.setVolume(0);_controller.setLooping(true);_controller.initialize().then((_) => setState(() {}));_controller.play();super.initState();
}@override
void dispose() {_controller.dispose(); //移除监听super.dispose();
}@override
Widget build(BuildContext context) {return AnnotatedRegion<SystemUiOverlayStyle>(value: SystemUiOverlayStyle.light,child: child: Stack(fit: StackFit.expand,children: [_controller.value.isInitialized /// 判断是否已经初始化,如果初始化,则加载,否则显示备用图片? Transform.scale(scale: _controller.value.aspectRatio /MediaQuery.of(context).size.aspectRatio,child: Center(child: AspectRatio(aspectRatio: _controller.value.aspectRatio,child: VideoPlayer(_controller),),),): Image.asset(R.imagesHealingIconBackground),],),);
}

3. 直接贴源码

bubble.dart

import 'package:flutter/material.dart';const _ArrowWidth = 7.0;    //箭头宽度
const _ArrowHeight = 10.0;  //箭头高度
const _MinHeight = 32.0;    //内容最小高度
const _MinWidth = 50.0;     //内容最小宽度class Bubble extends StatelessWidget {final BubbleDirection direction;final Radius? borderRadius;final Widget? child;final BoxDecoration? decoration;final Color? color;final _left;final _right;final EdgeInsetsGeometry? padding;final EdgeInsetsGeometry? margin;final BoxConstraints? constraints;final double? width;final double? height;final Alignment? alignment;const Bubble({Key? key,this.direction = BubbleDirection.left,this.borderRadius,this.child, this.decoration, this.color, this.padding, this.margin, this.constraints, this.width, this.height, this.alignment}): _left = direction == BubbleDirection.left ? _ArrowWidth : 0.0,_right = direction == BubbleDirection.right ? _ArrowWidth : 0.0,super(key: key);@overrideWidget build(BuildContext context) {return ClipPath(clipper:_BubbleClipper(direction, this.borderRadius ?? Radius.circular(5.0)),child: Container(alignment: this.alignment,width: this.width,height: this.height,constraints: (this.constraints??BoxConstraints()).copyWith(minHeight: _MinHeight,minWidth: _MinWidth),margin: this.margin,decoration: this.decoration,color: this.color,padding: EdgeInsets.fromLTRB(this._left, 0.0, this._right, 0.0).add(this.padding??EdgeInsets.fromLTRB(7.0, 5.0, 7.0, 5.0)),child: this.child,),);}
}///方向
enum BubbleDirection { left, right }class _BubbleClipper extends CustomClipper<Path> {final BubbleDirection direction;final Radius radius;_BubbleClipper(this.direction, this.radius);@overridePath getClip(Size size) {final path = Path();final path2 = Path();final centerPoint = (size.height / 2).clamp(_MinHeight/2, _MinHeight/2);print(centerPoint);if (this.direction == BubbleDirection.left) {//绘制左边三角形path.moveTo(0, centerPoint);path.lineTo(_ArrowWidth, centerPoint - _ArrowHeight/2);path.lineTo(_ArrowWidth, centerPoint + _ArrowHeight/2);path.close();//绘制矩形path2.addRRect(RRect.fromRectAndRadius(Rect.fromLTWH(_ArrowWidth, 0, (size.width - _ArrowWidth), size.height), this.radius));//合并path.addPath(path2, Offset(0, 0));} else {//绘制右边三角形path.moveTo(size.width, centerPoint);path.lineTo(size.width - _ArrowWidth, centerPoint - _ArrowHeight/2);path.lineTo(size.width - _ArrowWidth, centerPoint + _ArrowHeight/2);path.close();//绘制矩形path2.addRRect(RRect.fromRectAndRadius(Rect.fromLTWH(0, 0, (size.width - _ArrowWidth), size.height), this.radius));//合并path.addPath(path2, Offset(0, 0));}return path;}@overridebool shouldReclip(CustomClipper<Path> oldClipper) {return false;}
}

test.dart

/// 原生
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';/// 第三方
import 'package:get/get.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';///本地
import '../../../theme/utils/export.dart'; /// 主要是十六进制色制、自适应大小、静态图片
import '../../../utils/widget/bubble.dart'; /// 聊天气泡//气球聊天详情页
// ignore: must_be_immutable
class TestChatPage extends StatefulWidget {TestChatPage();@override_TestChatPageState createState() => _TestChatPageState();
}class _TestChatPageState extends State<TestChatPage> {/// 用户头像Widget userAvatar(img, size){return Padding(padding: EdgeInsets.all(10.dp),child: (img == null || img == "")? Image.asset(R.imagesMineIconAvatar,height: size,): CircleAvatar(radius: size/2,backgroundImage: NetworkImage(img),),);}/// 通用简单text格式singleTextWeight(text, color, fontSize){return Text(text,style: TextStyle(color: color,fontSize: fontSize),overflow: TextOverflow.ellipsis,);}/// 通用获取安全顶部距离Widget safePadding(BuildContext context, color){return Container(height: MediaQuery.of(context).padding.top,color: color,);}/// 隐藏键盘void hideKeyboard(BuildContext context){FocusScopeNode currentFocus = FocusScope.of(context);if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {FocusManager.instance.primaryFocus!.unfocus();}}ScrollController scrollController = ScrollController();/// 消息列表var _messageList = Rx<List<Map<dynamic, dynamic>>>([Map()]);/// 判断是否首次进入页面var firstCome = true.obs;/// 输入框焦点FocusNode focusNode = new FocusNode();late VideoPlayerController _controller; //背景视频播放控制器var _visZhifeiji = true.obs; //发送按钮隐藏和显示bool _isText = true; //true文本输入  false语言输入final TextEditingController _messageText = new TextEditingController(); //需要初始化的时候赋值用bool emojiShowing = false; /// 是否显示表情bool keyboardShowing = false; /// 是否显示键盘/// 获取用户历史消息getHistoryMessages() async {_messageList.value = [{"messageDirection": 1, "content": "还好"},{"messageDirection": 2, "content": "最近还好吗?"},{"messageDirection": 1, "content": "是啊"},];}@overridevoid initState() {///初始化视频播放_controller = VideoPlayerController.network('https://gugu-1300042725.cos.ap-shanghai.myqcloud.com/0_szDjEDn.mp4');_controller.addListener(() {setState(() {});});_controller.setVolume(0);_controller.setLooping(true);_controller.initialize().then((_) => setState(() {}));_controller.play();getHistoryMessages();focusNode.addListener(() {if(focusNode.hasFocus){keyboardShowing = true;if(emojiShowing){setState(() {emojiShowing = !emojiShowing;});}} else {keyboardShowing = false;}});super.initState();}///选中表情_onEmojiSelected(Emoji emoji) {_visZhifeiji.value = false;_messageText..text += emoji.emoji..selection = TextSelection.fromPosition(TextPosition(offset: _messageText.text.length));}///表情删除按钮_onBackspacePressed() {_messageText..text = _messageText.text.characters.skipLast(1).toString()..selection = TextSelection.fromPosition(TextPosition(offset: _messageText.text.length));if(_messageText.text.length == 0){_visZhifeiji.value = true;}}@overridevoid dispose() {_controller.dispose(); //移除监听scrollController.dispose();super.dispose();}/// 头部 BannerWidget _buildHeader(context) {return Container(color: Colors.transparent,width: double.infinity,height: 30.dp,child: Padding(padding: EdgeInsets.only(left: 10.dp, right: 10.dp),child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [Container(width: MediaQuery.of(context).size.width/4,child: Row(children: [GestureDetector(child: Image.asset(R.imagesMineIconBack,width: 18.dp,height: 18.dp,color: c_FF,),onTap: ()=>Get.back(),),Expanded(child: Text("")),],),),Container(width: MediaQuery.of(context).size.width/4,child: Center(child: singleTextWeight("Jaycee", c_FF, 16.dp),),),Container(width: MediaQuery.of(context).size.width/4,child: Text(""),),],),),);}/// 渲染聊天内容next(_messageRealList, index){return Row(children: [_messageRealList[index]['messageDirection'] == 1 ? Expanded(child: Text("")) : userAvatar("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202005%2F06%2F20200506110929_iajqi.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631409536&t=03cad8232b224d6a7ff11f58ff2be920", 58.dp),GestureDetector(onTap: ()=>{},child: Container(constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width - 128.dp),child: Bubble(direction: _messageRealList[index]['messageDirection'] == 1 ? BubbleDirection.right : BubbleDirection.left,color: c_FF95B5,child: Text("${_messageRealList[index]['content']}",style: TextStyle(color: c_00,fontSize: 18.dp),)),),),_messageRealList[index]['messageDirection'] != 1 ? Expanded(child: Text("")) : userAvatar("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Fc8%2Fdd%2Fb9%2Fc8ddb934a69d90216f1b406cf3975475.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631409536&t=17150dcec9e325456525160928d384f7", 58.dp),],);}/// 渲染聊天部分Widget _chatList(BuildContext context){List<Map<dynamic, dynamic>> _messageRealList = _messageList.value.reversed.toList();if(scrollController.hasClients && firstCome.value && scrollController.position.maxScrollExtent != 0.0){scrollController.jumpTo(scrollController.position.maxScrollExtent);firstCome.value = false;}return Column(children: [safePadding(context, Colors.transparent),_buildHeader(context),Container(height: 80.dp,width: MediaQuery.of(context).size.width - 40.dp,decoration: BoxDecoration(color: c_FF.withOpacity(0.6),borderRadius: BorderRadius.circular(20.dp)),child: Center(child: singleTextWeight("好久不见", c_FF, 16.dp),),),Expanded(child: _messageList.value.length > 0 ? EasyRefresh.custom(scrollController: scrollController,header: ClassicalHeader(),onRefresh: () async {/// 加载更多消息方法},slivers: [SliverGrid(delegate: SliverChildBuilderDelegate((context, index) => next(_messageRealList, index),childCount: _messageList.value.length,),gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1,mainAxisSpacing: 10.dp,crossAxisSpacing: 10.dp,childAspectRatio: 312.dp / 60.dp,),)]) : Text(""),),///输入键盘Container(color: Color(0x0dffffff),margin: EdgeInsets.fromLTRB(0, 5, 0, 10),height: 60.dp,child: Row(crossAxisAlignment: CrossAxisAlignment.center,children: [InkWell(child: Container(margin: EdgeInsets.fromLTRB(8, 0, 13, 0),width: 34.dp,height: 34.dp,child: _isText? Image.asset(R.imagesChatButtonVoice): Image.asset(R.imagesChatButtonKeyboard),),// onTap: () {//   if (this._isText) {//     this._isText = false;//     this.emojiShowing = false;//     this._visZhifeiji = true;//   } else {//     this._isText = true;//   }// },),Expanded(flex: 1,child: _isText? TextFormField(focusNode: focusNode,controller: _messageText,style: const TextStyle(fontSize: 18, color: Colors.white),decoration: InputDecoration(hintText: '请输入',hintStyle: TextStyle(fontSize: 18, color: Color(0x80ffffff))),onChanged: (value) {if (value.isEmpty) {_visZhifeiji.value = true;} else {_visZhifeiji.value = false;}},) : Text("data"),),InkWell(child: Container(margin: EdgeInsets.fromLTRB(8, 0, 6, 0),width: 30.dp,height: 30.dp,child: Image.asset(R.imagesChatButtonEmoji),),onTap: () {hideKeyboard(context);Future.delayed(Duration(milliseconds: 10), (){setState(() {emojiShowing = !emojiShowing;});if(emojiShowing){scrollController.jumpTo(scrollController.position.maxScrollExtent+250.dp);}});},),InkWell(child: Container(margin: EdgeInsets.fromLTRB(6, 0, 15, 0),width: 34.dp,height: 34.dp,child: Image.asset(R.imagesChatButtonAdd),),// onTap:(){// _onImageButtonPressed(ImageSource.camera, context: context);//打开相机// }),Obx(()=>Offstage(offstage: _visZhifeiji.value,child: Container(margin: EdgeInsets.fromLTRB(0, 0, 15, 0),width: 32.dp,height: 32.dp,child: InkWell(child: Image.asset(R.imagesChatButtonPaperPlane),onTap: () {_visZhifeiji.value = true;this.getHistoryMessages();_messageText.text = "";},),),)),],),),///表情Offstage(offstage: !emojiShowing,child: SizedBox(height: 250.dp,width: 1000.dp,child: EmojiPicker(onEmojiSelected: (Category category, Emoji emoji) {_onEmojiSelected(emoji);},onBackspacePressed: _onBackspacePressed,config: const Config(columns: 7,emojiSizeMax: 25.0,verticalSpacing: 0,horizontalSpacing: 0,initCategory: Category.RECENT,bgColor: Color(0xFFF2F2F2),indicatorColor: Color(0xff65DAC5),iconColor: Colors.orange,iconColorSelected: Color(0xff65DAC5),progressIndicatorColor: Color(0xff65DAC5),backspaceColor: Color(0xff65DAC5),showRecentsTab: true,recentsLimit: 28,noRecentsText: 'No Recents',noRecentsStyle:TextStyle(fontSize: 20, color: Colors.black26),categoryIcons: CategoryIcons(),buttonMode: ButtonMode.MATERIAL)),),)],);}@overrideWidget build(BuildContext context) {return AnnotatedRegion<SystemUiOverlayStyle>(value: SystemUiOverlayStyle.light,child: KeyboardVisibilityBuilder( /// 检测键盘是否弹出builder: (context, isKeyboardVisible){if(isKeyboardVisible){ /// 当键盘弹出时自动跳转到最底部scrollController.jumpTo(scrollController.position.maxScrollExtent);}return GestureDetector(onTap: () => {hideKeyboard(context), /// 隐藏键盘emojiShowing = false},child: Container(decoration: BoxDecoration(image: DecorationImage(image: AssetImage(R.imagesHealingIconBackground,),fit: BoxFit.fill)),child: Stack(fit: StackFit.expand,children: [_controller.value.isInitialized? Transform.scale(scale: _controller.value.aspectRatio /MediaQuery.of(context).size.aspectRatio,child: Center(child: AspectRatio(aspectRatio: _controller.value.aspectRatio,child: VideoPlayer(_controller),),),): Image.asset(R.imagesHealingIconBackground),Scaffold(backgroundColor: Colors.transparent,body: Container(height: double.infinity,width: double.infinity,child: _chatList(context),))],),),);},));}
}

Flutter聊天页面相关推荐

  1. flutter图片聊天泡泡_flutter即时聊天IM仿微信|flutter聊天界面

    鉴于Flutter最近比较火,今天给大家分享的是基于flutter+dart全家桶技术开发的仿微信界面聊天FlutterChat项目实例.实现了消息+表情.图片预览.红包.长按菜单.视频/仿朋友圈等功 ...

  2. 12、Flutter - 项目实战 - 仿微信(六)聊天页面

    Flutter - 项目实战 - 仿微信(六)聊天页面 接上篇:11.Flutter - 项目实战 - 仿微信(五)通讯录 详细代码参见Demo Demo地址 -> wechat_demo 其他 ...

  3. Flutter实战一Flutter聊天应用(汇总)

    纸聊 这个应用程序使用Google的Flutter移动框架开发,是一个实时聊天应用程序,为了能专注于APP设计,应用程序的服务端使用Googler的Firebase平台.程序程序的名称为纸聊,意为像传 ...

  4. Flutter实战一Flutter聊天应用(二十)

    在上一篇文章<Flutter实战一Flutter聊天应用(十九)>中,我们完成了删除用户的逻辑,就是将会话的有效性设置为false就可以了.那么当会话的有效性为false时,用户再次添加该 ...

  5. Flutter实战一Flutter聊天应用(十六)

    在上一篇文章<Flutter实战一Flutter聊天应用(十五)>中,我们完成了登陆屏幕.在用户登陆成功后,会在本地创建一个LandingInformation文件,以使应用程序在启动时可 ...

  6. Flutter实战一Flutter聊天应用(十五)

    在上一篇文章<Flutter实战一Flutter聊天应用(十四)>中,我们完成了注册屏幕.为了保持应用程序入口只有一个,即登陆屏幕,用户注册完成之后会返回手机号码.密码到登陆屏幕,让用户点 ...

  7. html怎么实现聊天界面设计,纯css制作仿微信聊天页面

    纯css制作仿微信聊天页面 *{ margin: 0; padding: 0; } body{ font-size: 14px; } .triangle{ margin: 100px auto ; w ...

  8. web实现置顶、置底功能、聊天页面、锚点、滚动条、vue、scrollTop、scrollIntoView、scrollHeight

    目录 前言 1.代码实现 3.scrollTop实现置顶置底(方式一) 4.scrollIntoView实现置顶置底(方式二) 5.总结 前言 在项目中遇到聊天功能,功能涉及置顶和置底知识,由此记录一 ...

  9. PyQt模拟微信聊天页面开发

    一.引述 网上关于Qt模拟微信.QQ的页面开发的多如牛毛,但C++本身来说逻辑上难上手,对只会Python的小伙伴来说太痛苦了. 今天就为大家开个场(下节更精彩),众所周知,要想模拟微信.QQ等这种聊 ...

最新文章

  1. 在Mac中安装opencv-python
  2. 不要将时间浪费到编写完美代码上
  3. P1972 [SDOI2009]HH的项链
  4. 应用配置管理ACM 使用场景快速入门
  5. 360浏览器一打开就是瑞星安全网址怎么办
  6. CentOS 7上安装 MongoDB数据库 4.0.0最新版
  7. 依赖注入[2]: 基于IoC的设计模式
  8. 男子欲刺杀游戏公司创始人被抓,还是游戏里“兔女郎”惹的祸?
  9. Android PopupWindow的简单说明
  10. 基于Matlab的自适应低通滤波器设计,课程设计-低通滤波器设计(含matlab程序)
  11. 计算机网络第二章学习通题目及答案
  12. 【IoT】成功十大因素,命、运、风水 、、贵人、养生,哪个最重要?
  13. Git命令提交项目代码
  14. BootStrap 4种图片形式 image形式 img-rounded img-circle img-thumbnail img-responsive
  15. IIS报错:HTTP 错误 404.0 - Not Found 您要找的资源已被删除、已更名或暂时不可用。最可能的原因: 指定的目录或文件在 Web 服务器上不存在。 URL 拼写错误。。。。
  16. configure命令的使用
  17. CSS——移动端适配方案
  18. 微信小程序定义和调用全局变量globalData
  19. 谷歌语音对接-GoogleAssistant-smart-home
  20. 卷积神经网络(CNN)在语音识别中的应用

热门文章

  1. PCI Express 基础规范修订版6.0【编辑中】
  2. 诗人、录音师......海南旅途中的“神仙岗位”,等你来体验
  3. 猪的一生应当这样度过
  4. JS实现购物车功能(JS案例)
  5. 机器人社社长事迹_【青春榜样】佳木斯大学十佳科技创新创新标兵事迹——黄鹏...
  6. docker运行yyets_Docker 后台进程参数-------更改Docker运行根目录的方法
  7. 在dos模拟文字游戏“英雄联盟“
  8. webdriver模仿ie_使IE8模仿IE7
  9. 解决2G内存处理8G数据的问题
  10. 用php建站_利用PHP建站心得