Flutter仿QQ实现UI、消息列表&信息详情列表

仓库地址

https://gitee.com/zhangNull/flutter_qq
https://github.com/nullErrorzy/flutter_qq

效果图:


ezgif-2-9e81adcda4.gif

ezgif-2-77818d24f5.gif

消息列表头部组件:


image.png

一、

Scaffold(
      backgroundColor: const Color.fromARGB(255, 255, 255, 255),
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        toolbarHeight: 0,
        backgroundColor: Colors.transparent,
        systemOverlayStyle: SystemUiOverlayStyle.dark,
        leading: const SizedBox(),
      ));

二、实现组件

Padding(
        padding: EdgeInsets.only(top: top + 10, left: 15, right: 15),
        child: Column(
          children: [
            Row(
              children: [
                ClipOval(
                    child: Image.asset(
                      "assets/images/bit7.jpg",
                      alignment: Alignment.topCenter,
                      fit: BoxFit.cover,
                      width: 40,
                      height: 40,                   
                  ),
                ),
                const SizedBox(width: 10),
                Expanded(
                    child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      "7_bit",
                      style: TextStyle(fontSize: 18),
                    ),
                    Row(
                      children: [
                        Container(
                          width: 10,
                          height: 10,
                          decoration: BoxDecoration(boxShadow: const [
                            BoxShadow(color: Color.fromARGB(255, 20, 250, 27)),
                            BoxShadow(color: Color.fromARGB(255, 40, 248, 47))
                          ], borderRadius: BorderRadius.circular(5)),
                        ),
                        const SizedBox(width: 5),
                        const Text(
                          "在线-WIFI",
                          style: TextStyle(fontSize: 11),
                        )
                      ],
                    ),
                  ],
                )),
                SvgPicture.asset(
                  "assets/svg/cloud.svg",
                  color: Colors.black,
                ),
                const SizedBox(width: 10),              
              ],
            ),
            const SizedBox(height: 10),
            Expanded(
                child: ListView(
              padding: EdgeInsets.zero,
              shrinkWrap: true,
              physics: const AlwaysScrollableScrollPhysics(
                parent: BouncingScrollPhysics(),
              ),
              children: [
                GestureDetector(
                  onTap: () {
                    Get.to(const SearchMessageListPage());
                  },
                  child: Hero(
                    tag: "Search",
                    child: Container(
                      height: 35,
                      decoration: BoxDecoration(
                          color: const Color.fromARGB(255, 245, 245, 245),
                          borderRadius: BorderRadius.circular(5)),
                      child: const Row(
                        mainAxisSize: MainAxisSize.min,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(Icons.search, color: Colors.grey),
                          SizedBox(width: 3),
                          Text("搜索",
                              style:
                                  TextStyle(fontSize: 16, color: Colors.grey))
                        ],
                      ),
                    ),
                  ),
                ),
                const SizedBox(height: 10),
                GestureDetector(
                  onTap: () {
                    Get.to(const UserDevicePage(),
                        transition: Transition.rightToLeft);
                  },
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      const SizedBox(width: 10),
                      SvgPicture.asset("assets/svg/ Monitor.svg",
                          color: Colors.grey),
                      const SizedBox(width: 20),
                      const Expanded(
                        child: SizedBox(
                          child: Text(
                            "已登录 Windows",
                            style: TextStyle(
                                color: Colors.grey,
                                fontSize: 16,
                                fontWeight: FontWeight.w500),
                          ),
                        ),
                      ),
                      SvgPicture.asset("assets/svg/chevronright.svg",
                          height: 30, color: Colors.grey),
                    ],
                  ),
                ),
                const Divider(
                  thickness: .2,
                ),
               )
              ],
            ))
          ],
        ),
      ),
    );
  }

消息列表实现思路:


image.png

一、创建实体类

class MsgModel {
  late String imageurl; //头像url
  late String name;  //名称
  late String msg;  //发送的消息
  late String time;  //发送时间
  late String? count;  //未读数
  MsgModel({
    required this.imageurl,
    required this.name,
    required this.msg,
    required this.time,
    this.count,
  });
}

二、实现单个消息组件

extended_image: ^8.1.0                         #图片缓存
shimmer: ^3.0.0                                #加载动画

Widget msgItem1(MsgModel model) {
  //加载动画组件
    Widget loading = Shimmer.fromColors(
        baseColor: Colors.white,
        highlightColor: const Color.fromARGB(255, 240, 240, 240),
        child: Container(
          height: 50,
          width: double.infinity,
          color: Colors.white,
        ));

    return Container(
      color: Colors.transparent,
      child: Row(
        children: [
          ClipOval(
              child: ExtendedImage.network(
            model.imageurl,
            cache: true,
            width: 50,
            height: 50,
            fit: BoxFit.cover,
            loadStateChanged: (state) {
              if (state.extendedImageLoadState == LoadState.loading) {
                return loading;
              } else if (state.extendedImageLoadState == LoadState.failed) {
                return loading;
              }
              return null;
            },
          )),
          const SizedBox(width: 10),
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      Text(
                        model.name,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(
                            fontSize: 20, fontWeight: FontWeight.w500),
                      ),
                      const SizedBox(height: 3),
                      Text(
                        model.msg,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style:
                            const TextStyle(color: Colors.grey, fontSize: 15),
                      ),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    Text(
                      model.time,
                      style: const TextStyle(color: Colors.grey),
                    ),
                    const SizedBox(height: 5),
                    Opacity(
                      opacity: model.count != null ? 1 : 0,
                      child: Container(
                        padding: const EdgeInsets.symmetric(horizontal: 2),
                        constraints:
                            const BoxConstraints(minWidth: 17, minHeight: 15),
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                            color: Colors.red,
                            borderRadius: BorderRadius.circular(15)),
                        child: Text(
                          model.count ?? "0",
                          style: const TextStyle(color: Colors.white),
                        ),
                      ),
                    )
                  ],
                )
              ],
            ),
          ),
        ],
      ),
    );
  }

信息详情头部组件-高斯模糊appbar


image.png

高斯模糊组件

//高斯模糊组件
Widget getFilterWidget({
  Widget? child,
  double sigmaX = 20,
  double sigmaY = 20,
}) {
  return ClipRect(
    //背景模糊化
    child: BackdropFilter(
      filter: ImageFilter.blur(
        sigmaX: sigmaX,
        sigmaY: sigmaY,
      ),
      child: child,
    ),
  );
}

实现自定义appbar

PreferredSize(
        preferredSize: const Size(0, 60),
        child: 
        getFilterWidget(
          child: Container(
            color: const Color.fromARGB(100, 255, 255, 255),
            child: AppBar(
              backgroundColor: Colors.transparent,
              leading: const BackButton(
                style: ButtonStyle(
                    overlayColor: MaterialStatePropertyAll(Colors.transparent)),
              ),
              leadingWidth: 70,
              title: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    widget.msgModel.name,
                    style: const TextStyle(fontSize: 18),
                  ),
                  const SizedBox(height: 3),
                  Row(
                    children: [
                      Container(
                        width: 8,
                        height: 8,
                        decoration: BoxDecoration(boxShadow: const [
                          BoxShadow(color: Color.fromARGB(255, 20, 250, 27)),
                          BoxShadow(color: Color.fromARGB(255, 40, 248, 47))
                        ], borderRadius: BorderRadius.circular(5)),
                      ),
                      const SizedBox(width: 5),
                      const Text("手机在线 - 4G", style: TextStyle(fontSize: 10))
                    ],
                  )
                ],
              ),
              actions: [
                IconButton(
                  padding: EdgeInsets.zero,
                  onPressed: () {},
                  highlightColor: Colors.white.withOpacity(.1),
                  icon: Container(
                    constraints: BoxConstraints.tight(const Size(30, 30)),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(100),
                      color: Colors.black.withOpacity(0),
                    ),
                    child: const Icon(
                      Icons.qr_code,
                      color: Colors.black,
                      size: 22,
                    ),
                  ),
                  style: const ButtonStyle(
                      padding: MaterialStatePropertyAll(EdgeInsets.zero)),
                ),
                IconButton(
                  padding: EdgeInsets.zero,
                  onPressed: () {},
                  highlightColor: Colors.black.withOpacity(.1),
                  icon: Container(
                    constraints: BoxConstraints.tight(const Size(30, 30)),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(100),
                      color: Colors.black.withOpacity(0),
                    ),
                    child: const Icon(
                      Icons.settings,
                      color: Colors.black,
                      size: 22,
                    ),
                  ),
                  style: const ButtonStyle(
                      padding: MaterialStatePropertyAll(EdgeInsets.zero)),
                )
              ],
            ),
          ),
        ),
      ),

对话列表


image.png

一、创建实体类

class UserSendUserMsgModel {
  late String imageurl; //头像
  late String msg;  //消息
  late String? type;  //消息类型
  late String? role;  //角色
  UserSendUserMsgModel(
      {required this.imageurl,
      required this.msg,
      this.type = "text", //text 、 image
      this.role = "i"}); // i 、you
}

二、单个组件

Widget item(UserSendUserMsgModel userMsgModel) {
    double width = MediaQuery.sizeOf(Get.context!).width * .7;
    Widget loading = Shimmer.fromColors(
        baseColor: Colors.white,
        highlightColor: const Color.fromARGB(255, 240, 240, 240),
        child: Container(
          height: 50,
          width: double.infinity,
          color: Colors.white,
        ));
    return userMsgModel.role == "i"
        ? Row(
            mainAxisAlignment: MainAxisAlignment.end,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              userMsgModel.type == "text"
                  ? Container(
                      constraints: BoxConstraints(maxWidth: width),
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                          color: Colors.blue,
                          borderRadius: BorderRadius.circular(8)),
                      child: Text(
                        userMsgModel.msg,
                        style: const TextStyle(color: Colors.white),
                      ))
                  : GestureDetector(
                      onTap: () {
                        showDialog(
                            builder: (BuildContext context) {
                              return DetailPage(userMsgModel.msg);
                            },
                            context: context);
                      },
                      child: Container(
                        constraints: BoxConstraints(maxWidth: width),
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(8),
                          child: ExtendedImage.network(
                            userMsgModel.msg,
                            cache: true,
                            loadStateChanged: (state) {
                              if (state.extendedImageLoadState ==
                                  LoadState.loading) {
                                return loading;
                              } else if (state.extendedImageLoadState ==
                                  LoadState.failed) {
                                return loading;
                              }
                              return null;
                            },
                          ),
                        ),
                      ),
                    ),
              const SizedBox(width: 10),
              ClipOval(
                  child: ExtendedImage.network(
                userMsgModel.imageurl,
                cache: true,
                width: 35,
                height: 35,
                fit: BoxFit.cover,
                loadStateChanged: (state) {
                  if (state.extendedImageLoadState == LoadState.loading) {
                    return loading;
                  } else if (state.extendedImageLoadState == LoadState.failed) {
                    return loading;
                  }
                  return null;
                },
              )),
            ],
          )
        : Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              ClipOval(
                  child: ExtendedImage.network(
                userMsgModel.imageurl,
                cache: true,
                width: 35,
                height: 35,
                fit: BoxFit.cover,
                loadStateChanged: (state) {
                  if (state.extendedImageLoadState == LoadState.loading) {
                    return loading;
                  } else if (state.extendedImageLoadState == LoadState.failed) {
                    return loading;
                  }
                  return null;
                },
              )),
              const SizedBox(width: 10),
              userMsgModel.type == "text"
                  ? Container(
                      constraints: BoxConstraints(maxWidth: width),
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(8)),
                      child: Text(
                        userMsgModel.msg,
                        style: const TextStyle(color: Colors.black),
                      ))
                  : GestureDetector(
                      onTap: () {
                        showDialog(
                            builder: (BuildContext context) {
                              return DetailPage(userMsgModel.msg);
                            },
                            context: context);
                      },
                      child: Container(
                        constraints: BoxConstraints(maxWidth: width),
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(8),
                          child: ExtendedImage.network(
                            userMsgModel.msg,
                            cache: true,
                            loadStateChanged: (state) {
                              if (state.extendedImageLoadState ==
                                  LoadState.loading) {
                                return loading;
                              } else if (state.extendedImageLoadState ==
                                  LoadState.failed) {
                                return loading;
                              }
                              return null;
                            },
                          ),
                        ),
                      ),
                    )
            ],
          );
  }
}

底部输入框


image.png
            Container(
              color: const Color.fromARGB(255, 247, 247, 247),
              padding: const EdgeInsets.all(8.0),
              child: Row(
                children: [
                  const Icon(Icons.mic_none_outlined),
                  const SizedBox(width: 5),
                  Expanded(
                    child: BLMSerachField(
                      "",
                      "colse",
                      textEditingController,
                      autofocus: false,
                      textInputType: TextInputType.text,
                      backgrund: Colors.white,
                    ),
                  ),
                  const SizedBox(width: 10),
                  SvgPicture.asset(
                    "assets/svg/Star-Struck.svg",
                    width: 24,
                    height: 24,
                  ),
                  const SizedBox(width: 10),
                  SizedBox(
                          width: 60,
                          height: 30,
                          child: ElevatedButton(
                            onPressed: () {},
                            style: ButtonStyle(
                                padding: const MaterialStatePropertyAll(
                                    EdgeInsets.zero),
                                shape: MaterialStatePropertyAll(
                                  RoundedRectangleBorder(
                                    borderRadius: BorderRadius.circular(3),
                                  ),
                                ),
                                backgroundColor: MaterialStatePropertyAll(
                                    Colors.blue.shade400)),
                            child: const Text(
                              "发送",
                              style:
                                  TextStyle(color: Colors.white, fontSize: 16),
                            ),
                          ),
                        ),
                  const SizedBox(width: 5)
                ],
              ),
            ),

弹出选项列表

ezgif-3-ca6c706a8c.gif

实现组件 点击后修改高度

AnimatedContainer(
      height: height,
      color: const Color.fromARGB(255, 247, 247, 247),
      padding: const EdgeInsets.only(top: 20),
      duration: const Duration(milliseconds: 500),
      curve: Curves.fastLinearToSlowEaseIn,
      child: StaggeredGrid.count(
      crossAxisCount: 3,
      mainAxisSpacing: 50,
      crossAxisSpacing: 4,
        children: [
           .......
          ],
    ))

单个选项组件


image.png
Widget myDisplayItem({required String svg, required String name}) {
  return SizedBox(
    child: Column(children: [
      SvgPicture.asset(
        "assets/svg/$svg.svg",
        width: 25,
      ),
      Text(name, style: const TextStyle(fontWeight: FontWeight.w300))
    ]),
  );
}

最后上全部代码

// ignore_for_file: deprecated_member_use

import 'dart:ui';

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_qq/QQ/model/msg_model.dart';
import 'package:flutter_qq/QQ/model/user_send_user_msg_model.dart';
import 'package:flutter_qq/QQ/widget/serach_field.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:shimmer/shimmer.dart';

class MessageDetailsPage extends StatefulWidget {
  const MessageDetailsPage({super.key, required this.msgModel});
  final MsgModel msgModel;

  @override
  State<MessageDetailsPage> createState() => _MessageDetailsPageState();
}

class _MessageDetailsPageState extends State<MessageDetailsPage> {
  late TextEditingController textEditingController = TextEditingController();
  late bool displaySend = false;
  late String me =
      "https://foruda.gitee.com/avatar/1677180609201628769/9580418_zhangnull_1639032531.png";
  late List<UserSendUserMsgModel> msgs = [];

  void addData() {
    msgs.add(UserSendUserMsgModel(
      imageurl: widget.msgModel.imageurl,
      role: "you",
      msg: '可以把',
    ));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl,
        msg: 'https://w.wallhaven.cc/full/zy/wallhaven-zyvg1j.png',
        role: "you",
        type: "image"));

    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl,
        msg: 'https://w.wallhaven.cc/full/d6/wallhaven-d6vp7m.jpg',
        role: "you",
        type: "image"));
    msgs.add(UserSendUserMsgModel(
      imageurl: me,
      msg: '来点摄图',
    ));
    msgs.add(UserSendUserMsgModel(
      imageurl: me,
      msg: '你配个机霸',
    ));
    msgs.add(UserSendUserMsgModel(
        imageurl: me,
        msg: 'https://w.wallhaven.cc/full/7p/wallhaven-7pxx99.png',
        type: "image"));

    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl,
        msg: '感觉我这种臭鱼烂虾进去要坐大牢',
        role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '这里的invoke没有啊', role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl,
        msg:
            '我的识别码:364819064使用向日葵即可对我发起远程协助向日葵下载地址:http://url.oray.com/tGJdas/',
        role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '谢谢你,大屌侠', role: "you"));

    msgs.add(UserSendUserMsgModel(
      imageurl: me,
      msg: '所以说,点不出来就等于没有吗',
    ));
    msgs.add(UserSendUserMsgModel(
      imageurl: me,
      msg: '眼睛不要可以捐了',
    ));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '唉,我是傻逼!', role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '我感觉这些东西我不太配', role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl,
        msg: '我真的他妈的开始找的时候有点心虚了',
        role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '我感觉我不配', role: "you"));
    msgs.add(UserSendUserMsgModel(
      imageurl: me,
      msg: '你行个机霸',
    ));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '我得问问啥情况', role: "you"));
    msgs.add(UserSendUserMsgModel(
        imageurl: widget.msgModel.imageurl, msg: '我感觉我真不太行', role: "you"));
  }

  late bool isdisplay = false;
  late double height = 0;
  late ScrollController scrollController = ScrollController();
  //实例化
  FocusNode focusNode = FocusNode();
  @override
  void initState() {
    super.initState();
    addData();
    //监听输入框的焦点,是否弹出选项列表
    focusNode.addListener(() {
      if (focusNode.hasFocus) {
        displaySend = true;
        if (isdisplay) {
          height = 0;
        }
        setState(() {});
      }
    });
    //监听滚动到底后,列表拉到指定位置输入框获取焦点
    scrollController.addListener(() {
      if (scrollController.offset < -130) {
        FocusScope.of(context).requestFocus(focusNode);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color.fromARGB(255, 245, 245, 245),
      appBar: PreferredSize(
        preferredSize: const Size(0, 60),
        child: getFilterWidget(
          child: Container(
            color: const Color.fromARGB(100, 255, 255, 255),
            child: AppBar(
              backgroundColor: Colors.transparent,
              leading: const BackButton(
                style: ButtonStyle(
                    overlayColor: MaterialStatePropertyAll(Colors.transparent)),
              ),
              leadingWidth: 70,
              title: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    widget.msgModel.name,
                    style: const TextStyle(fontSize: 18),
                  ),
                  const SizedBox(height: 3),
                  Row(
                    children: [
                      Container(
                        width: 8,
                        height: 8,
                        decoration: BoxDecoration(boxShadow: const [
                          BoxShadow(color: Color.fromARGB(255, 20, 250, 27)),
                          BoxShadow(color: Color.fromARGB(255, 40, 248, 47))
                        ], borderRadius: BorderRadius.circular(5)),
                      ),
                      const SizedBox(width: 5),
                      const Text("手机在线 - 4G", style: TextStyle(fontSize: 10))
                    ],
                  )
                ],
              ),
              actions: [
                IconButton(
                  padding: EdgeInsets.zero,
                  onPressed: () {},
                  highlightColor: Colors.white.withOpacity(.1),
                  icon: Container(
                    constraints: BoxConstraints.tight(const Size(30, 30)),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(100),
                      color: Colors.black.withOpacity(0),
                    ),
                    child: const Icon(
                      Icons.qr_code,
                      color: Colors.black,
                      size: 22,
                    ),
                  ),
                  style: const ButtonStyle(
                      padding: MaterialStatePropertyAll(EdgeInsets.zero)),
                ),
                IconButton(
                  padding: EdgeInsets.zero,
                  onPressed: () {},
                  highlightColor: Colors.black.withOpacity(.1),
                  icon: Container(
                    constraints: BoxConstraints.tight(const Size(30, 30)),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(100),
                      color: Colors.black.withOpacity(0),
                    ),
                    child: const Icon(
                      Icons.settings,
                      color: Colors.black,
                      size: 22,
                    ),
                  ),
                  style: const ButtonStyle(
                      padding: MaterialStatePropertyAll(EdgeInsets.zero)),
                )
              ],
            ),
          ),
        ),
      ),
      extendBodyBehindAppBar: true,
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: GestureDetector(
                onTap: () {
                  FocusScope.of(context).requestFocus(FocusNode());
                  if (isdisplay) {
                    isdisplay = !isdisplay;
                    height = 0;
                  }
                  displaySend = textEditingController.text.isNotEmpty;
                  if (mounted) setState(() {});
                  setState(() {});
                },
                child: ListView.separated(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                  reverse: true,
                  shrinkWrap: true,
                  controller: scrollController,
                  physics: const AlwaysScrollableScrollPhysics(
                    parent: BouncingScrollPhysics(),
                  ),
                  itemCount: msgs.length,
                  itemBuilder: (context, index) {
                    return DefaultTextStyle(
                        style:
                            const TextStyle(fontSize: 16, color: Colors.black),
                        child: item(msgs[index]));
                  },
                  separatorBuilder: (BuildContext context, int index) {
                    return const SizedBox(height: 20);
                  },
                ),
              ),
            ),
            Container(
              color: const Color.fromARGB(255, 247, 247, 247),
              padding: const EdgeInsets.all(8.0),
              child: Row(
                children: [
                  const Icon(Icons.mic_none_outlined),
                  const SizedBox(width: 5),
                  Expanded(
                    child: BLMSerachField(
                      "",
                      "colse",
                      textEditingController,
                      autofocus: false,
                      textInputType: TextInputType.text,
                      focusNode: focusNode,
                      onSubmitted: (p0) {
                        if (p0.isEmpty) return;
                        msgs.insert(
                            0, UserSendUserMsgModel(imageurl: me, msg: p0));
                        textEditingController.clear();
                        //displaySend = false;
                        Future.delayed(const Duration(seconds: 2), () {
                          msgs.insert(
                              0,
                              UserSendUserMsgModel(
                                  imageurl: widget.msgModel.imageurl,
                                  role: "you",
                                  msg: "${widget.msgModel.name}:$p0"));
                          setState(() {});
                        });
                        setState(() {});
                      },
                      onchange: (text) {
                        displaySend = textEditingController.text.isNotEmpty;
                        if (mounted) setState(() {});
                      },
                      backgrund: Colors.white,
                    ),
                  ),
                  const SizedBox(width: 10),
                  SvgPicture.asset(
                    "assets/svg/Star-Struck.svg",
                    width: 24,
                    height: 24,
                  ),
                  const SizedBox(width: 10),
                  !displaySend
                      ? GestureDetector(
                          onTap: () {
                            isdisplay = !isdisplay;
                            if (isdisplay) {
                              height = 300;
                              scrollController.animateTo(-20,
                                  curve: Curves.ease,
                                  duration: const Duration(milliseconds: 500));
                            } else {
                              height = 0;
                            }
                            //FocusScope.of(context).requestFocus(FocusNode());

                            setState(() {});
                          },
                          child: SvgPicture.asset(
                            "assets/svg/Add_Plus_Circle.svg",
                            color: !isdisplay ? Colors.black : Colors.blue,
                          ),
                        )
                      : SizedBox(
                          width: 60,
                          height: 30,
                          child: ElevatedButton(
                            onPressed: () {
                              if (textEditingController.text.isEmpty) return;
                              msgs.insert(
                                  0,
                                  UserSendUserMsgModel(
                                      imageurl: me,
                                      msg: textEditingController.text));
                              String youtext = textEditingController.text;
                              textEditingController.clear();
                              //displaySend = false;

                              scrollController.animateTo(0,
                                  curve: Curves.ease,
                                  duration: const Duration(milliseconds: 300));
                              Future.delayed(const Duration(seconds: 2), () {
                                msgs.insert(
                                    0,
                                    UserSendUserMsgModel(
                                        imageurl: widget.msgModel.imageurl,
                                        role: "you",
                                        msg:
                                            "${widget.msgModel.name}:$youtext"));
                                setState(() {});
                              });

                              setState(() {});
                            },
                            style: ButtonStyle(
                                padding: const MaterialStatePropertyAll(
                                    EdgeInsets.zero),
                                shape: MaterialStatePropertyAll(
                                  RoundedRectangleBorder(
                                    borderRadius: BorderRadius.circular(3),
                                  ),
                                ),
                                backgroundColor: MaterialStatePropertyAll(
                                    Colors.blue.shade400)),
                            child: const Text(
                              "发送",
                              style:
                                  TextStyle(color: Colors.white, fontSize: 16),
                            ),
                          ),
                        ),
                  const SizedBox(width: 5)
                ],
              ),
            ),
            AnimatedContainer(
                height: height,
                color: const Color.fromARGB(255, 247, 247, 247),
                padding: const EdgeInsets.only(top: 20),
                duration: const Duration(milliseconds: 500),
                curve: Curves.fastLinearToSlowEaseIn,
                child: StaggeredGrid.count(
                  crossAxisCount: 3,
                  mainAxisSpacing: 50,
                  crossAxisSpacing: 4,
                  children: [
                    myDisplayItem(svg: 'folder', name: '文件'),
                    myDisplayItem(svg: 'upload 1', name: '微云'),
                    myDisplayItem(svg: 'picture', name: '相册'),
                    myDisplayItem(svg: 'bookmark', name: '收藏'),
                    myDisplayItem(svg: 'tethering 1', name: '王卡'),
                    myDisplayItem(svg: 'desktop 1', name: '我的电脑'),
                    myDisplayItem(svg: 'controller 1', name: '游戏中心'),
                    myDisplayItem(svg: 'QQ', name: 'QQ')
                  ],
                ))
          ],
        ),
      ),
    );
  }

  Widget item(UserSendUserMsgModel userMsgModel) {
    double width = MediaQuery.sizeOf(Get.context!).width * .7;
    Widget loading = Shimmer.fromColors(
        baseColor: Colors.white,
        highlightColor: const Color.fromARGB(255, 240, 240, 240),
        child: Container(
          height: 50,
          width: double.infinity,
          color: Colors.white,
        ));
    return userMsgModel.role == "i"
        ? Row(
            mainAxisAlignment: MainAxisAlignment.end,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              userMsgModel.type == "text"
                  ? Container(
                      constraints: BoxConstraints(maxWidth: width),
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                          color: Colors.blue,
                          borderRadius: BorderRadius.circular(8)),
                      child: Text(
                        userMsgModel.msg,
                        style: const TextStyle(color: Colors.white),
                      ))
                  : GestureDetector(
                      onTap: () {
                        showDialog(
                            builder: (BuildContext context) {
                              return DetailPage(userMsgModel.msg);
                            },
                            context: context);
                      },
                      child: Container(
                        constraints: BoxConstraints(maxWidth: width),
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(8),
                          child: ExtendedImage.network(
                            userMsgModel.msg,
                            cache: true,
                            loadStateChanged: (state) {
                              if (state.extendedImageLoadState ==
                                  LoadState.loading) {
                                return loading;
                              } else if (state.extendedImageLoadState ==
                                  LoadState.failed) {
                                return loading;
                              }
                              return null;
                            },
                          ),
                        ),
                      ),
                    ),
              const SizedBox(width: 10),
              ClipOval(
                  child: ExtendedImage.network(
                userMsgModel.imageurl,
                cache: true,
                width: 35,
                height: 35,
                fit: BoxFit.cover,
                loadStateChanged: (state) {
                  if (state.extendedImageLoadState == LoadState.loading) {
                    return loading;
                  } else if (state.extendedImageLoadState == LoadState.failed) {
                    return loading;
                  }
                  return null;
                },
              )),
            ],
          )
        : Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              ClipOval(
                  child: ExtendedImage.network(
                userMsgModel.imageurl,
                cache: true,
                width: 35,
                height: 35,
                fit: BoxFit.cover,
                loadStateChanged: (state) {
                  if (state.extendedImageLoadState == LoadState.loading) {
                    return loading;
                  } else if (state.extendedImageLoadState == LoadState.failed) {
                    return loading;
                  }
                  return null;
                },
              )),
              const SizedBox(width: 10),
              userMsgModel.type == "text"
                  ? Container(
                      constraints: BoxConstraints(maxWidth: width),
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(8)),
                      child: Text(
                        userMsgModel.msg,
                        style: const TextStyle(color: Colors.black),
                      ))
                  : GestureDetector(
                      onTap: () {
                        showDialog(
                            builder: (BuildContext context) {
                              return DetailPage(userMsgModel.msg);
                            },
                            context: context);
                      },
                      child: Container(
                        constraints: BoxConstraints(maxWidth: width),
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(8),
                          child: ExtendedImage.network(
                            userMsgModel.msg,
                            cache: true,
                            loadStateChanged: (state) {
                              if (state.extendedImageLoadState ==
                                  LoadState.loading) {
                                return loading;
                              } else if (state.extendedImageLoadState ==
                                  LoadState.failed) {
                                return loading;
                              }
                              return null;
                            },
                          ),
                        ),
                      ),
                    )
            ],
          );
  }
}

Widget myDisplayItem({required String svg, required String name}) {
  return SizedBox(
    child: Column(children: [
      SvgPicture.asset(
        "assets/svg/$svg.svg",
        width: 25,
      ),
      Text(name, style: const TextStyle(fontWeight: FontWeight.w300))
    ]),
  );
}

Widget getFilterWidget({
  Widget? child,
  double sigmaX = 20,
  double sigmaY = 20,
}) {
  return ClipRect(
    //背景模糊化
    child: BackdropFilter(
      filter: ImageFilter.blur(
        sigmaX: sigmaX,
        sigmaY: sigmaY,
      ),
      child: child,
    ),
  );
}

class DetailPage extends StatefulWidget {
  const DetailPage(this.url, {super.key});
  final String url;
  @override
  State<DetailPage> createState() => _NewDetailPageState();
}

class _NewDetailPageState extends State<DetailPage>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return GestureDetector(
      onTap: () {
        Navigator.pop(context);
      },
      child: ExtendedImageSlidePage(
        slideAxis: SlideAxis.both,
        slideType: SlideType.onlyImage,
        slidePageBackgroundHandler: (offset, pageSize) {
          return Colors.transparent;
        },
        resetPageDuration: const Duration(milliseconds: 200),
        child: ExtendedImageGesturePageView(
          children: [
            ExtendedImage.network(
              widget.url,
              fit: BoxFit.contain,
              enableSlideOutPage: true,
              heroBuilderForSlidingPage: (image) => Hero(
                tag: widget.url,
                flightShuttleBuilder: (
                  flightContext,
                  animation,
                  flightDirection,
                  fromHeroContext,
                  toHeroContext,
                ) {
                  final hero = (flightDirection == HeroFlightDirection.pop
                      ? fromHeroContext.widget
                      : toHeroContext.widget) as Hero;
                  return hero.child;
                },
                child: image,
              ),
              mode: ExtendedImageMode.gesture,
              initGestureConfigHandler: (s) {
                return GestureConfig(
                    minScale: 0.9,
                    animationMinScale: 0.7,
                    maxScale: 5.0,
                    animationMaxScale: 5.5,
                    speed: 1.0,
                    inertialSpeed: 100.0,
                    initialScale: 1.0,
                    inPageView: false);
              },
              loadStateChanged: (state) {
                return null;
              },
            ),
          ],
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}
class UserSendUserMsgModel {
  late String imageurl;
  late String msg;
  late String? type;
  late String? role;
  UserSendUserMsgModel(
      {required this.imageurl,
      required this.msg,
      this.type = "text", //text 、 image
      this.role = "i"}); // i 、you
}


class MsgModel {
  late String imageurl;
  late String name;
  late String msg;
  late String time;
  late String? count;
  MsgModel({
    required this.imageurl,
    required this.name,
    required this.msg,
    required this.time,
    this.count,
  });
}
// ignore_for_file: must_be_immutable

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get_connect/http/src/utils/utils.dart';

class BLMSerachField extends StatefulWidget {
  final String hint;
  final int? maxLength;
  late String type;
  late TextInputType? textInputType;
  final TextEditingController controller;
  late int codetype;
  late Widget? icon;
  late bool autofocus;
  late void Function(String)? onchange;
  late Color? backgrund;
  late FocusNode? focusNode;
  late void Function(String)? onSubmitted;
  BLMSerachField(this.hint, this.type, this.controller,
      {super.key,
      this.maxLength,
      this.textInputType,
      this.codetype = 1,
      this.icon,
      this.autofocus = false,
      this.onchange,
      this.backgrund,
      this.focusNode,
      this.onSubmitted});
  @override
  State<BLMSerachField> createState() => _SerachFieldState();
}

class _SerachFieldState extends State<BLMSerachField> {
  bool displayClean = false;
  //late var stringText = "".obs;
  late bool obscurebool = true;
  //验证码是否发送
  late bool isFinish = false;
  //倒计时60秒
  int seconds = 60;

  Timer? time;

  late bool phoneFormat = F;

  @override
  void initState() {
    super.initState();
    obscurebool = widget.type == "colse" ||
            widget.type == "userName" ||
            widget.type == "recolse"
        ? false
        : true;
    // debounce(stringText, (callback) {
    // });
    //触发任意焦点或事件监听
    widget.controller.addListener(() {
      checkDisplayClean();
    });
  }

  @override
  void dispose() {
    super.dispose();
    //销毁计时器
    time?.cancel();
  }

  //倒计时
  showTimer() {
    isFinish = true;
    time = Timer.periodic(const Duration(milliseconds: 1000), (timer) {
      seconds--;
      if (seconds == 0) {
        time?.cancel(); //清除定时器
        isFinish = false;
        seconds = 60;
      }
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    // ignore: unused_local_variable
    late Widget getsuffix = const SizedBox();

    return Container(
      height: 35,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      decoration: BoxDecoration(
        color: widget.backgrund ?? const Color.fromARGB(255, 245, 245, 245),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        children: [
          widget.icon ?? const SizedBox(),
          const SizedBox(width: 6),
          Expanded(
            child: TextField(
              onSubmitted: widget.onSubmitted,
              autofocus: widget.autofocus,
              keyboardType: widget.textInputType,
              maxLength: widget.maxLength,
              focusNode: widget.focusNode,
              maxLines: 1,
              onChanged: widget.onchange,
              controller: widget.controller,
              textInputAction: TextInputAction.send,
              onEditingComplete: () {},
              obscureText: obscurebool,
              decoration: InputDecoration(
                isCollapsed: true,
                counterText: "",
                border: InputBorder.none,
                hintText: widget.hint,
                hintStyle: const TextStyle(fontSize: 15, color: Colors.grey),
                contentPadding: const EdgeInsets.symmetric(vertical: 10),
              ),
            ),
          ),
          const SizedBox(width: 6),
          Row(
            children: [
              SizedBox(
                width: 30,
                child: Visibility(
                  visible: displayClean,
                  child: IconButton(
                    iconSize: 20,
                    icon: const Icon(Icons.close),
                    onPressed: () {
                      widget.controller.text = "";

                      phoneFormat = F;
                      setState(() {});
                    },
                  ),
                ),
              ),
              getsuffix
            ],
          ),
        ],
      ),
    );
  }

  Widget yesorNo() {
    return widget.controller.text.isNotEmpty && widget.type == "userName" ||
            widget.type == "recolse"
        ? !phoneFormat
            ? SvgPicture.asset("assets/svg/cloud.svg")
            : SvgPicture.asset("assets/svg/edit.svg")
        : const SizedBox();
  }

  void checkDisplayClean() {
    setState(() {
      displayClean = widget.controller.text.isNotEmpty;
    });
  }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容