Flutter聊天界面(静态)

功能有,链接,发送文字,发送表情

本篇文章存静态效果、没用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(),
                          ),
                        )))
                  ])))
        ]));
  }
}
相关推荐
续亮~1 小时前
6、Redis系统-数据结构-05-整数
java·前端·数据结构·redis·算法
顶顶年华正版软件官方2 小时前
剪辑抽帧技巧有哪些 剪辑抽帧怎么做视频 剪辑抽帧补帧怎么操作 剪辑抽帧有什么用 视频剪辑哪个软件好用在哪里学
前端·音视频·视频·会声会影·视频剪辑软件·视频剪辑教程·剪辑抽帧技巧
MarkHD3 小时前
javascript 常见设计模式
开发语言·javascript·设计模式
托尼沙滩裤3 小时前
【js面试题】js的数据结构
前端·javascript·数据结构
不熬夜的臭宝3 小时前
每天10个vue面试题(一)
前端·vue.js·面试
朝阳394 小时前
vue3【实战】来回拖拽放置图片
javascript·vue.js
不如喫茶去4 小时前
VUE自定义新增、复制、删除dom元素
前端·javascript·vue.js
长而不宰4 小时前
vue3+electron项目搭建,遇到的坑
前端·vue.js·electron
阿垚啊4 小时前
vue事件参数
前端·javascript·vue.js
加仑小铁4 小时前
【区分vue2和vue3下的element UI Dialog 对话框组件,分别详细介绍属性,事件,方法如何使用,并举例】
javascript·vue.js·ui