网上找了找 零零碎碎有一些文章 没找到一个整体的 自己做完记录一下 防止忘了
大体就是这样
聊天气泡用的是 https://blog.csdn.net/oterminator12/article/details/105790961 这个文章看到的
然后表情用的是 https://blog.csdn.net/qq_36676433/article/details/104756685 这个文章看到的
整体结构及底部输入/表情选择部分
body下的结构主要为最外层Column,然后聊天部分用Flexible组件套住 为了保证聊天框部分可以保持在底部
Flexible(
child: Container(
color: Color(0xfff5f5f7),
width: ScreenUtil().setWidth(750),
height: ScreenUtil().setHeight(1334),
child: EasyRefresh(
controller: _controller,
child: _comment(),
footer: comIsNone ? noneFooterStyle : footerStyle,
onRefresh: (){
},
),
),
)
//_comment() 是自定义的一个widget 其实就是一个listview 用来承载聊天内容列表
//EasyRefresh是下拉加载 上拉刷新功能的插件 可用可不用
我的聊天输入部分是这么做的 与Flexible同级
Container(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(15),top: ScreenUtil().setHeight(10),bottom: ScreenUtil().setHeight(10),right:ScreenUtil().setWidth(15) ),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Color(0xFFF3F3F3),
offset: Offset(0.0, -5.0), //阴影xy轴偏移量
blurRadius: 15.0, //阴影模糊程度
spreadRadius: 0.4, //阴影扩散程度
),
],
),
child:Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: ScreenUtil().setHeight(500),
minHeight: ScreenUtil().setHeight(50),
),
child: Container(
width: ScreenUtil().setWidth(565),
color: Color(0xfff5f5f7),
child:ConstrainedBox(
constraints: BoxConstraints(
maxHeight: ScreenUtil().setHeight(500),
minHeight: ScreenUtil().setHeight(50),
),
child:TextField(
textInputAction: TextInputAction.send,
controller: _titleController,
focusNode: focusNode,
maxLines: null,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
isCollapsed:true,
hintStyle: TextStyle(fontSize: ScreenUtil().setSp(27)),
contentPadding:EdgeInsets.only(left: ScreenUtil().setWidth(10), right: ScreenUtil().setWidth(10),top: ScreenUtil().setWidth(10),bottom: ScreenUtil().setWidth(10)),
border: InputBorder.none
),
onSubmitted: (text){
if( text.trim() == '' ){
toast('请输入内容');
}else{
_titleController.clear();
widgetLists.add(
BubbleShow(
contentType:1,
senderType:2,
headPortrait:minePhoto,
text:text,
photo:'',
),
);
setState(() {});
}
},
),
),
),
),
//表情按钮
Container(
alignment: Alignment.center,
child: InkWell(
onTap: () {
focusNode.unfocus();
if( expShow ){
setState(() {
expShow = false;
});
}else{
setState(() {
expShow = true;
});
}
},
child: Icon(
IconData(0xe62b, fontFamily: 'MyIcons'),
color: Color(0xff666666),
size: ScreenUtil().setSp(50),
)
),
),
//图片
Container(
alignment: Alignment.center,
child: InkWell(
onTap: () {
focusNode.unfocus();
if( functionShow ){
setState(() {
functionShow = false;
});
}else{
setState(() {
functionShow = true;
});
}
},
child: Icon(
IconData(0xe7e1, fontFamily: 'MyIcons'),
color: Color(0xff666666),
size: ScreenUtil().setSp(50),
)
),
),
],
),
),
然后表情或者图片的下部扩展部分用Offstage组件嵌套上 他是靠offstage属性控制是否展示 true为隐藏 依旧是与flexible同级 图片功能也是类似写法
Container(
child: Offstage(
offstage: expShow,
child: Container(
height: ScreenUtil().setWidth(300),
child: WeChatExpression((Expression expression){
_selectText = _titleController.text;
_selectText += '[${expression.name}]';
_titleController.text = _selectText;
_titleController.selection = TextSelection(
baseOffset: _selectText.length,
extentOffset: _selectText.length,
);
setState(() {});
}),
),
),
)
body的最外层应该先放一个GestureDetector组件 点击事件里写好失去焦点 隐藏表情框等方法
聊天列表展示
聊天主要是两种 一种是照片的呈现 比较简单 就是image组件即可
另一种是文本与表情的配合展示,表情都是图片,他那个文章里有说到 也可以下载他的代码
我主要是将聊天气泡与那个文本表情结合在一起 重新写了个类
遇到的问题就是合成在一起内容不换行的问题 之后用ConstrainedBox嵌套住设置了最大宽度解决了 下面如果有不存在的类 需要到前面提到的两个文章 把他们的代码拿过来用下
//聊天展示类
class BubbleShow extends StatelessWidget {
//contentType 内容类型 文字/表情-1 图片-2
//senderType 发送人类型 对方-1 自己-2
//headPortrait 头像url
//text 聊天内容文本
//photo 图片路径
final contentType;
final senderType;
final headPortrait;
final text;
final photo;
const BubbleShow({Key key, this.contentType,this.senderType,this.headPortrait,this.text,this.photo}):super(key: key);
@override
Widget build(BuildContext context) {
return senderType==1?Container(
margin: EdgeInsets.only(top:ScreenUtil().setWidth(20)),
alignment: Alignment.topLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children:<Widget>[
Container(
child: ClipOval(
child: SizedBox(
width: ScreenUtil().setWidth(70),
height: ScreenUtil().setWidth(70),
child:
Image.network(headPortrait, fit: BoxFit.cover),
),
),
margin: EdgeInsets.only(right: ScreenUtil().setWidth(15)),
width: ScreenUtil().setWidth(70),
height: ScreenUtil().setWidth(70),
),
contentType==1?ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ScreenUtil().setWidth(550),
),
child:Bubble(
direction:BubbleDirection.left,
color: Colors.white,
child:ExpressionText(text,null)
),
):InkWell(
child:ConstrainedBox(
constraints: BoxConstraints(
maxHeight: ScreenUtil().setWidth(500),
),
child:Image.file(
File(photo),
width: ScreenUtil().setWidth(150),
fit: BoxFit.fitWidth,
),
),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ArticleImage(src:photo)),
);
},
),
]
),
):Container(
margin: EdgeInsets.only(top:ScreenUtil().setWidth(20)),
alignment: Alignment.topRight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children:<Widget>[
contentType==1?ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ScreenUtil().setWidth(550),
),
child:Bubble(
direction:BubbleDirection.right,
color: Colors.lightBlue[100],
child:ExpressionText(text,null)
),
):InkWell(
child:ConstrainedBox(
constraints: BoxConstraints(
maxHeight: ScreenUtil().setWidth(500),
),
child:Image.file(
File(photo),
width: ScreenUtil().setWidth(150),
fit: BoxFit.fitWidth,
),
),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ArticleImage(src:photo)),
);
},
),
Container(
child: ClipOval(
child: SizedBox(
width: ScreenUtil().setWidth(70),
height: ScreenUtil().setWidth(70),
child:
Image.network(headPortrait, fit: BoxFit.cover),
),
),
margin: EdgeInsets.only(left: ScreenUtil().setWidth(15)),
width: ScreenUtil().setWidth(70),
height: ScreenUtil().setWidth(70),
),
]
),
);
}
}
然后再说一下聊天展示部分,就是上面提到的_comment()
一开始为了做到聊天内容始终看到最新的 想到用了listview的reverse属性,但是如果不够一屏聊天记录,会将他们显示在下方而不是从上往下展示,之后靠测试查询发现是EasyRefresh导致的,去他的github上可以查到相关问题,官方说了解决方案,但是他说体验也不是很好,我没尝试那个,后来看到有人说可以用ScrollController,我就选用了这种方法对listview进行手动移动
ScrollController scrollController = ScrollController();
@override
void initState() {
super.initState();
//build执行完后会执行
var widgetsBinding=WidgetsBinding.instance;
widgetsBinding.addPostFrameCallback((callback){
time=Timer.periodic(
Duration(milliseconds: 3000),
(t){
if(scrollController.hasClients) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
t.cancel();
}
);
});
}
有一个很蠢的事,build完成可能不代表listview构建完成吧.. 如果直接执行jumpto操作,会报错scrollController没有用在widget上.. 然后我先暂时改成了等个三秒,在判断是否作用在了widget上,实际上可以有更好的写法,这样写也挺鸡肋的