chat_page.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import 'package:flutter/material.dart';
  2. import '../model/msg.dart';
  3. import '../util/ToastUtil.dart';
  4. import '../util/clipboard_util.dart';
  5. import '../widget/bubble/bubble_widget.dart';
  6. /// 聊天页面
  7. class ChatPage extends StatefulWidget {
  8. const ChatPage({super.key});
  9. @override
  10. State<StatefulWidget> createState() {
  11. return ChatPageState();
  12. }
  13. }
  14. class ChatPageState extends State<ChatPage> {
  15. /// TextField操作控制器
  16. final TextEditingController _editingController = TextEditingController();
  17. /// ListView的滚动控制器
  18. final ScrollController _scrollController = ScrollController();
  19. /// 输入框焦点
  20. final _inputFocusNode = FocusNode();
  21. /// 消息列表
  22. final List<Msg> _msgList = [];
  23. @override
  24. void initState() {
  25. super.initState();
  26. _inputFocusNode.addListener(_handleTextFieldFocusChange);
  27. // 进入页面,就获取输入框焦点
  28. _inputFocusNode.requestFocus();
  29. }
  30. @override
  31. void dispose() {
  32. // 取消监听
  33. _inputFocusNode.removeListener(_handleTextFieldFocusChange);
  34. _inputFocusNode.dispose();
  35. _editingController.dispose();
  36. _scrollController.dispose();
  37. super.dispose();
  38. }
  39. /// 处理输入框的焦点变化
  40. void _handleTextFieldFocusChange() {
  41. if (_inputFocusNode.hasFocus) {
  42. // 输入框获取焦点,滚动列表到底部
  43. _scrollToBottom();
  44. }
  45. }
  46. /// 添加消息到消息列表中
  47. void _addMsg2List(String msg, bool isMe) {
  48. setState(() {
  49. _msgList.add(
  50. Msg(isMe: isMe, msg: msg, createTime: DateTime.now().millisecond),
  51. );
  52. });
  53. _scrollToBottom();
  54. }
  55. /// 滚动到列表底部
  56. void _scrollToBottom() {
  57. //延迟300毫秒,再滚动到列表底部
  58. Future.delayed(const Duration(milliseconds: 300), () {
  59. _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
  60. });
  61. }
  62. /// 发送消息
  63. void _sendMsg(String msg) {
  64. if (msg.isEmpty) {
  65. ToastUtil.showToast("请输入要发送的消息内容");
  66. return;
  67. }
  68. //添加消息到列表中
  69. _addMsg2List(msg, true);
  70. // 延迟生成对方的回复消息
  71. Future.delayed(const Duration(milliseconds: 150), () {
  72. //添加消息到列表中
  73. _addMsg2List(replyMessage2Client(msg), false);
  74. });
  75. //清除输入框的内容
  76. _editingController.clear();
  77. }
  78. /// 生成回复消息
  79. String replyMessage2Client(String clientMsg) {
  80. return clientMsg
  81. .replaceAll("我", "你")
  82. .replaceAll("吗", "")
  83. .replaceAll("?", "!")
  84. .replaceAll("?", "!");
  85. }
  86. /// 构建聊天气泡
  87. Widget _buildMsgBubble(Msg msg) {
  88. // 设置气泡的外边距,让气泡不易过长
  89. double marginValue = 50;
  90. EdgeInsets marginEdgeInsets;
  91. if (msg.isMe) {
  92. marginEdgeInsets = EdgeInsets.only(left: marginValue);
  93. } else {
  94. marginEdgeInsets = EdgeInsets.only(right: marginValue);
  95. }
  96. // Flexible,文本超过一行时,自动换行,并且不超过最大宽度,不超过一行时,则自动包裹内容
  97. return Flexible(
  98. child: BubbleWidget(
  99. // 箭头方向
  100. arrowDirection: msg.isMe ? AxisDirection.right : AxisDirection.left,
  101. arrowOffset: 22,
  102. arrowLength: 8,
  103. arrowRadius: 4,
  104. arrowWidth: 14,
  105. padding: const EdgeInsets.all(12),
  106. borderRadius: BorderRadius.circular(8),
  107. margin: marginEdgeInsets,
  108. backgroundColor:
  109. msg.isMe
  110. ? const Color.fromARGB(255, 164, 208, 238)
  111. : const Color.fromARGB(255, 153, 231, 169),
  112. contentBuilder: (context) {
  113. return SelectableText(msg.msg);
  114. },
  115. ),
  116. );
  117. }
  118. /// 构建聊天消息列表项
  119. Widget _buildMsgItem(Msg msg) {
  120. Widget content;
  121. // 自己发的
  122. if (msg.isMe) {
  123. content = Row(
  124. // 如果是自己发的,则在右边
  125. mainAxisAlignment: MainAxisAlignment.end,
  126. // 顶部对齐
  127. crossAxisAlignment: CrossAxisAlignment.start,
  128. children: [
  129. // 聊天气泡
  130. _buildMsgBubble(msg),
  131. // 头像
  132. _buildAvatar(msg),
  133. ],
  134. );
  135. } else {
  136. // 对方发的
  137. content = Row(
  138. // 如果是自己发的,则在右边
  139. mainAxisAlignment: MainAxisAlignment.start,
  140. // 顶部对齐
  141. crossAxisAlignment: CrossAxisAlignment.start,
  142. children: [
  143. // 头像
  144. _buildAvatar(msg),
  145. // 聊天气泡
  146. _buildMsgBubble(msg),
  147. ],
  148. );
  149. }
  150. // return GestureDetector(
  151. // onLongPress: () {
  152. // // 长按消息,复制文本到剪切板
  153. // ClipboardUtil.copyToClipboard(msg.msg);
  154. // ToastUtil.showToast("已复制到剪切板");
  155. // },
  156. // child: Container(padding: const EdgeInsets.all(8.0), child: content),
  157. // );
  158. return Container(padding: const EdgeInsets.all(8.0), child: content);
  159. }
  160. /// 构建头像
  161. Widget _buildAvatar(Msg msg) {
  162. return Container(
  163. margin: const EdgeInsets.symmetric(horizontal: 8.0),
  164. child: CircleAvatar(
  165. radius: 20,
  166. backgroundColor: Colors.grey,
  167. child: Text(
  168. msg.isMe ? "我" : "对方",
  169. style: const TextStyle(color: Colors.white),
  170. ),
  171. ),
  172. );
  173. }
  174. @override
  175. Widget build(BuildContext context) {
  176. return Scaffold(
  177. appBar: AppBar(
  178. leading: IconButton(
  179. onPressed: () {
  180. Navigator.pop(context, null);
  181. },
  182. icon: const Icon(Icons.arrow_back),
  183. ),
  184. title: const Text("聊天页"),
  185. ),
  186. body: Column(
  187. children: [
  188. Expanded(
  189. flex: 1,
  190. child: ListView.builder(
  191. controller: _scrollController,
  192. itemCount: _msgList.length,
  193. itemBuilder: (BuildContext context, int index) {
  194. return _buildMsgItem(_msgList[index]);
  195. },
  196. ),
  197. ),
  198. Center(
  199. child: Column(
  200. children: [
  201. Container(height: 0.3, color: Colors.grey),
  202. Padding(
  203. padding: const EdgeInsets.symmetric(
  204. vertical: 15.0,
  205. horizontal: 18.0,
  206. ),
  207. child: Row(
  208. mainAxisAlignment: MainAxisAlignment.start,
  209. children: [
  210. Expanded(
  211. flex: 1,
  212. child: TextField(
  213. // 设置按钮显示为发送
  214. textInputAction: TextInputAction.send,
  215. onSubmitted: (value) {
  216. // 用户点击软键盘的发送按钮
  217. var msg = _editingController.text;
  218. _sendMsg(msg);
  219. },
  220. // 输入框焦点
  221. focusNode: _inputFocusNode,
  222. // 点击外部区域,关闭软键盘
  223. onTapUpOutside: (event) {
  224. _inputFocusNode.unfocus();
  225. },
  226. controller: _editingController,
  227. decoration: const InputDecoration(
  228. //提示文字
  229. hintText: "请输入要发送的消息",
  230. //边框
  231. border: OutlineInputBorder(),
  232. ),
  233. ),
  234. ),
  235. Container(
  236. margin: const EdgeInsets.only(left: 15.0),
  237. child: ElevatedButton(
  238. child: const Text("发送"),
  239. onPressed: () {
  240. var msg = _editingController.text;
  241. _sendMsg(msg);
  242. },
  243. ),
  244. ),
  245. ],
  246. ),
  247. ),
  248. ],
  249. ),
  250. ),
  251. ],
  252. ),
  253. );
  254. }
  255. }