Flutter聊天界面(静态)

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

本篇文章存静态效果、没用socket连接的实现

效果图

 代码如下:

//在线聊天
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(),
                          ),
                        )))
                  ])))
        ]));
  }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以使用Flutter的`Stack`和`AnimatedContainer`来实现底部键盘和表情列表的切换,并保持高度不变。以下是一个简单的示例代码: ```dart import 'package:flutter/material.dart'; class ChatScreen extends StatefulWidget { @override _ChatScreenState createState() => _ChatScreenState(); } class _ChatScreenState extends State<ChatScreen> { bool _isKeyboardVisible = false; double _keyboardHeight = 0.0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Chat'), ), body: Stack( children: [ ListView.builder( // Chat messages itemBuilder: (context, index) => ListTile( title: Text('Message $index'), ), ), Positioned( left: 0, right: 0, bottom: 0, child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, height: _isKeyboardVisible ? _keyboardHeight : 80.0, // Set your desired height child: Column( children: [ Expanded( child: Container( // Your chat input field color: Colors.grey[200], child: TextField( decoration: InputDecoration( hintText: 'Type a message...', contentPadding: EdgeInsets.symmetric(horizontal: 16.0), ), ), ), ), Container( // Emoji picker or any other bottom panel color: Colors.grey[300], child: _isKeyboardVisible ? Text('Emoji Picker') : Text('Keyboard'), ), ], ), ), ), ], ), ); } @override void initState() { super.initState(); // Listen for keyboard visibility changes KeyboardVisibility.onChange.listen((bool visible) { setState(() { _isKeyboardVisible = visible; _keyboardHeight = KeyboardVisibility.keyboardHeight ?? 0.0; }); }); } } ``` 在上述代码中,我们使用`Stack`将聊天消息列表和底部输入框叠加在一起。然后,我们使用`AnimatedContainer`来实现高度的动画变化,根据键盘的可见性来决定高度值。您可以将您的聊天输入字段放在`Expanded`小部件中,并将表情选择器或任何其他底部面板放在下面。 为了监听键盘的可见性和高度变化,我们使用了`keyboard_visibility`库中的`KeyboardVisibility`类。请确保在您的`pubspec.yaml`文件中添加`keyboard_visibility`依赖项。 这只是一个基本的示例,您可以根据自己的需求进行修改和扩展。希望对您有所帮助!如果有任何问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值