功能有,链接,发送文字,发送表情
本篇文章存静态效果、没用socket连接的实现
效果图
代码如下:
Dart
//在线聊天
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../home/house_detail_page.dart';
import '../library/network/network.dart';
class Message {
final String type;
final String sender;
final String? text;
final Map? cardInfo;
Message({required this.sender, this.text, required this.type, this.cardInfo});
}
//文字信息==============================================================================
class Bubble extends StatelessWidget {
final Message message;
final bool isMe;
Bubble({required this.message, required this.isMe});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Visibility(
visible: !isMe,
child: const Icon(
Icons.paid,
size: 30,
),
),
Container(
margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: isMe ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(12.0),
),
child: Text(
message.text ?? '',
style: TextStyle(color: isMe ? Colors.white : Colors.black),
),
),
Visibility(
visible: isMe,
child: const Icon(
Icons.pages,
size: 30,
),
)
],
);
}
}
//卡片================================================================================
class Card extends StatelessWidget {
final Message message;
final bool isMe;
Card({required this.message, required this.isMe});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Visibility(
visible: !isMe,
child: const Icon(
Icons.paid,
size: 30,
),
),
SizedBox(child: _CardPage(cardInfo: message.cardInfo ?? {})),
Visibility(
visible: isMe,
child: const Icon(
Icons.pages,
size: 30,
),
)
],
);
}
}
class _CardPage extends StatelessWidget {
late Map cardInfo;
_CardPage({required this.cardInfo});
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.8,
margin: EdgeInsets.only(top: 5),
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(12.0)),
child: Row(
children: [
GestureDetector(
onTap: () {
// Add your click event handling code here
// 去详情页
Navigator.push(
context,
MaterialPageRoute(
// fullscreenDialog: true,
builder: (context) => MyHomeDetailPage(
houseId: cardInfo['id'], type: cardInfo['type']),
),
);
},
child: Container(
width: 100,
height: 84,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
kFileRootUrl + (cardInfo['styleImgPath'] ?? '')),
fit: BoxFit.fill,
repeat: ImageRepeat.noRepeat,
),
borderRadius: BorderRadius.circular(10),
),
)),
GestureDetector(
onTap: () {
// Add your click event handling code here
// 去详情页
Navigator.push(
context,
MaterialPageRoute(
// fullscreenDialog: true,
builder: (context) => MyHomeDetailPage(
houseId: cardInfo['id'], type: cardInfo['type']),
),
);
},
child: Container(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
cardInfo['name'],
style: const TextStyle(fontSize: 18),
),
],
),
Row(
children: [
Text(cardInfo['zoneName'] ?? ''),
const Text(' | '),
Text('${"mianji".tr} '),
Text(cardInfo['area']),
],
),
Container(
alignment: Alignment.centerLeft,
child: Text(
'${cardInfo['price'] ?? ''}/㎡',
style: const TextStyle(
color: Colors.orange, fontSize: 16),
),
),
],
),
)), //小标题
],
));
}
}
//主页
class CommunicatePage extends StatefulWidget {
const CommunicatePage({super.key});
@override
State<CommunicatePage> createState() => _CommunicatePageState();
}
class _CommunicatePageState extends State<CommunicatePage> {
//变量 start==========================================================
final TextEditingController _ContentController =
TextEditingController(text: '');
/// 输入框焦点
FocusNode focusNode = FocusNode();
final List<Message> messages = [
Message(
sender: "ta",
cardInfo: {
"id": "4",
"code": "fxhsud",
"title": "test1",
"name": "test1",
"zoneName": null,
"area": "90",
"roomType": "2室1厅1卫",
"directions": ["2"],
"price": "200.00",
"type": 2,
"status": 2,
"seeCount": null,
"floorNum": "24/30",
"styleImgPath":
"/upload/upload/2024/03/26/IMG_20230303_183318(1)_20240326170733A001.jpg",
"time": "2022-03-26"
},
type: "card"),
Message(sender: "me", text: "hi!", type: "text"),
Message(sender: "me", text: "你是?!", type: "text"),
Message(sender: "ta", text: "hello!", type: "text")
];
var isEmojiShow = false;
final List unicodeArr = [
'\u{1F600}',
'\u{1F601}',
'\u{1F602}',
'\u{1F603}',
'\u{1F604}',
'\u{1F60A}',
'\u{1F60B}',
'\u{1F60C}',
'\u{1F60D}',
'\u{2764}',
'\u{1F44A}',
'\u{1F44B}',
'\u{1F44C}',
'\u{1F44D}'
];
//变量 end==========================================================
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
_ContentController.dispose();
focusNode.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Color(0xFFebebeb),
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text('张三'),
),
body: Stack(alignment: Alignment.bottomCenter, children: [
ListView.builder(
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
return messages[index].type == 'text'
? Bubble(
message: messages[index],
isMe: messages[index].sender == 'me',
)
: Card(
message: messages[index],
isMe: messages[index].sender == 'me',
);
},
),
Positioned(
bottom: 0,
child: SingleChildScrollView(
reverse: true, // 反向滚动以确保 Positioned 在键盘上方
child: Column(children: [
Container(
width: MediaQuery.of(context).size.width,
height: 50,
decoration: const BoxDecoration(
color: Color.fromRGBO(240, 240, 240, 1)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const Icon(
Icons.contactless_outlined,
size: 35,
),
SizedBox(
width: MediaQuery.of(context).size.width *
0.6, // 添加固定宽度
child: TextField(
textAlignVertical: TextAlignVertical.center,
controller: _ContentController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.all(5),
isCollapsed: true,
filled: true,
fillColor: Colors.white,
// 设置背景色
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)), // 设置圆角半径
borderSide: BorderSide.none, // 去掉边框
),
),
focusNode: focusNode,
onTap: ()=>{setState(() {
isEmojiShow =false;
})},
onTapOutside: (e) => {focusNode.unfocus()},
onEditingComplete: () {
FocusScope.of(context)
.requestFocus(focusNode);
},
)),
GestureDetector(
onTap: () => {
setState(() {
isEmojiShow =
!isEmojiShow; // 数据加载完毕,重置标志位
})
},
child: const Icon(
Icons.sentiment_satisfied_alt_outlined,
size: 35,
)),
const Icon(
Icons.add_circle_outline,
size: 35,
),
],
),
),
Visibility(
visible: isEmojiShow,
child: Container(
width: MediaQuery.of(context).size.width,
height: 200,
decoration: const BoxDecoration(color: Colors.white),
child:
SingleChildScrollView(
scrollDirection: Axis.vertical,
child:
Wrap(
children: unicodeArr.map((emoji) {
return Container(
padding: const EdgeInsets.all(8.0),
width: MediaQuery.of(context).size.width / 4, // 设置每个子项的宽度为屏幕宽度的三分之一
height: 60,
child: GestureDetector(
onTap: () {
setState(() {
messages.add(Message(
sender: 'me',
text: emoji,
type: 'text',
));
});
},
child: Text(
emoji,
style: TextStyle(fontSize: 30),
),
),
);
}).toList(),
),
)))
])))
]));
}
}