Flutter高仿微信-第29篇-单聊

news2024/12/22 3:27:31

Flutter高仿微信系列共59篇,从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。

详情请查看

效果图:

实现代码:

单聊包含:文本、表情、语音、图片、小视频、红包、转账、视频通话、语音通话功能,有4个widget:

home_chat_page.dart、chat_add_view.dart、chat_content_view.dart、chat_voice_view.dart

 home_chat_page.dart实现:

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/8/24 14:48
 * Description : 单聊页面
 */
class HomeChatPage extends StatefulWidget {

  String toChatId;
  String account = SpUtils.getString(CommonUtils.LOGIN_ACCOUNT);

  HomeChatPage({required this.toChatId});

  @override
  _HomeChatPageState createState() => _HomeChatPageState(toChatId);
}

class _HomeChatPageState extends State<HomeChatPage> with TickerProviderStateMixin {

  String _toChatId;
  _HomeChatPageState(this._toChatId);
  //好友账户
  UserBean? _otherUserBean;
  //我的账户
  UserBean? _meUserBean;

  List<String> addTimeList = [];
  List<ChatBean> items = [];
  ScrollController _controller = ScrollController();
  var chatEvent;

  //每页13条
  static const PAGE_SIZE = 13;
  //当前页
  var PAGE_NUM = 1;
  //从那一条开始(为保证最新的先显示, 先查询最后的,并且不能用desc查询)
  var startNum = 0;
  //总共多少条
  var CHAT_TOTAL = 0;

  @override
  void initState() {
    super.initState();
    AppManager.getInstance().toChatId = _toChatId;
    _checkAvailable();
    _updateChatStatus();

    chatEvent = eventBus.on<ChatBean>((chatBean) {
      if(mounted){
        setState(() {
          chatBean as ChatBean;
          items.add(chatBean);
        });
      }
    });

    chatEvent = eventBus.on<RedPacketBean>((redPacketBean) {
      setState(() {
        _updateRedpacketBalance(redPacketBean);
      });
    });


    loadUserBean();
    loadAllChat();
    jumpToBottom(400);
    // 监听滚动事件
    _controller.addListener((){
      if(_controller.position.pixels>_controller.position.maxScrollExtent-40){
      }
    });

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _initScreenUtil(context);
    });

  }

  //更新红包金额
  void _updateRedpacketBalance(RedPacketBean redPacketBean){
    LogUtils.d("home_chat_page 的金额:${redPacketBean?.position}");
    ChatBean chatBean = items[redPacketBean.position??0];
    String messageId = chatBean.messageId??"";
    int isClick = 1;
    chatBean.isClick = isClick;
    ChatRepository.getInstance().updateChatRedPacketStatus(messageId, isClick);
    setState(() {
    });
    Navigator.push(context, MaterialPageRoute(builder: (context) => ReceiveRedpacketSuccess(fromUser: widget.account, toUser: widget.toChatId, balance: "${chatBean.content}", addTime: chatBean.addTime??"",)));
  }

  @override
  void dispose() {
    super.dispose();
    eventBus.off(chatEvent);
    AppManager.getInstance().toChatId = "";
  }

  final controller = TextEditingController();
  final FocusNode _chatContentFocus = FocusNode();

  @override
  Widget build(BuildContext context) {
    if(!isLoadMore){
      //每次发送消息滚动到底部0.1秒
      jumpToBottom(100);
    }

    return Scaffold(

      appBar: WnAppBar.getAppBar(context, Text("${_otherUserBean?.nickName??""}")),
      bottomNavigationBar: Text(""),//占用底部位置
      body: GestureDetector(
        onTap: (){
          _processClickBlank();
        },
        child: Container(
          child: Column(
            children: <Widget>[
              Expanded(
                  child: RefreshIndicator(
                    displacement: 2,
                      onRefresh: _onRefresh,
                      child: ListView.builder(
                        controller: _controller,
                        itemBuilder: (BuildContext context, int index) {
                          return ChatContentView(items: items,account: widget.account, chatBean: items[index], index: index, meUserBean: _meUserBean,addTimeList: addTimeList,
                            otherUserBean: _otherUserBean, deleteCallback: (data){
                              setState(() {
                                items.remove(items[index]);
                              });
                            },clickVoiceCallback: (data){
                                //点击播放录音,暂停后再播放
                                for(int i = 0; i < items.length; i++){
                                  if(i == index){
                                    items[i].isPlayVoice = true;
                                  } else {
                                    items[i].isPlayVoice = false;
                                  }
                                }
                                setState(() {

                                });
                            }, refreshTransfer: (position){
                              LogUtils.d("回调刷新:${position}");
                              _refreshTransfer(position);
                            },);
                        },
                        itemCount: items.length,

                      )
                  ),
              ),
              Divider(height: 12.0, color: Color(0xFFF7F8F8),),
              Container(
                padding: EdgeInsets.only(top: 5.0, bottom: 5.0, right: 2.0, left: 2.0),
                color: Color(0xFFF3F3F3),
                width: double.infinity,
                child: Row(

                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.symmetric(horizontal: 2.0),
                      child: IconButton(
                        //按下语音说活
                          icon: isPressVoice ? Image.asset("assets/chat/button_keyboard.png"):Image.asset("assets/chat/button_voice.png"),
                          onPressed: () =>{
                            _processPressVoice()
                          }
                      ), //触发发送消息事件执行的函数_handleSubmitted
                    ),
                    Expanded(
                      child: Stack(
                        children: [
                          Offstage(
                            offstage: !isPressVoice,
                            child: ChatVoiceView(
                              refreshMediaCallback: (type, mediaURL, thumbnailFileName, second, messageId){
                                _refreshMedia(type, mediaURL, thumbnailFileName, mediaSecond: second, messageId: messageId);
                              },
                              sendMedialCallback: (type, mediaURL, second, messageId){
                                _sendMedia(type, mediaURL, mediaSecond: second, messageId: messageId);
                              },
                              stopPlayVoiceCallback: (){
                                hidePlayVoiceList();
                              },
                            ),
                          ),
                          Offstage(
                            offstage: isPressVoice,
                            child: Container(
                              padding: EdgeInsets.only(top: 8.0, bottom: 8.0, left: 8.0),
                              decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Colors.white),
                              child: TextField(
                                controller: controller,
                                focusNode: _chatContentFocus,
                                decoration: InputDecoration.collapsed(hintText: null),
                                autocorrect: true,
                                //是否自动更正
                                autofocus: false,
                                maxLines: 5,
                                minLines: 1,
                                textAlign: TextAlign.start,
                                style: TextStyle(color: Colors.black, fontSize: 20),
                                cursorColor: Colors.green,
                                onTap: (){
                                  //点击编辑框
                                  jumpToBottom(400);
                                  hideEmoji = true;
                                  hideAdd = true;
                                  setState(() {
                                  });
                                },
                                onChanged: (value){
                                  //录入文字
                                  setState(() {
                                    if(value.length>0){
                                      hideSend = false;
                                      hideAddIcon = true;
                                    } else {
                                      hideSend = true;
                                      hideAddIcon = false;
                                    }
                                  });
                                },
                                onSubmitted: _handleSubmitted,
                                enabled: true, //是否禁用
                              ),
                            ),
                          ),
                        ],
                      ),

                    ),

                    Container(
                      child: IconButton(
                          icon: Image.asset("assets/chat/button_emoji.png"),
                          onPressed: () => _processEmoji()),
                    ),


                    Offstage(
                      offstage: hideAddIcon,
                      child: Container(
                        //margin: EdgeInsets.only(right: 4.0),
                        child: IconButton(
                          //添加按钮
                            icon: Image.asset("assets/chat/button_add.png"),
                            onPressed: () => {
                              _processAdd()
                            }
                        ),
                      ),
                    ),

                    Offstage(
                      offstage: hideSend,
                      child: Container(
                        margin: EdgeInsets.symmetric(horizontal: 4.0),
                        child: IconButton(
                          //发送按钮
                            icon: new Icon(Icons.send), //发送按钮图标
                            onPressed: () => _handleSubmitted(
                                controller.text)), //触发发送消息事件执行的函数_handleSubmitted
                      ),
                    ),
                  ],
                ),
              ),

              Offstage(
                offstage: hideAdd,
                child: ChatAddView(
                  viewType: CommonUtils.VIEW_TYPE_SINGLE_CHAT,
                  toChatId: widget.toChatId,
                  refreshMediaCallback: (type, mediaURL, thumbnailFileName, second, messageId){
                    _refreshMedia(type, mediaURL, thumbnailFileName, mediaSecond: second , messageId: messageId);
                  },
                  sendMedialCallback: (type, mediaURL, second, messageId){
                    _sendMedia(type, mediaURL, mediaSecond: second, messageId: messageId);
                  },
                  refreshRedpacketAndTransfer: (type, text){
                    _refreshRedpacketAndTransfer(type, text);
                  },
                ),
              ),

              Offstage(
                offstage: hideEmoji,
                child: getEmojiWidget(),
              ),

            ],
          ),
        ),
      ),
    );
  }

  //进入聊天页面,把聊天状态更新为已读
  void _updateChatStatus() async{
    int newMessageCount = await ChatRepository.getInstance().getAllChatUnReadByAccount(_toChatId)??0;
    if(newMessageCount >= 0){
      await ChatRepository.getInstance().updateChatReadByAccount(_toChatId);
      Map<String, Object> result = HashMap<String, Object>();
      result["from_account"] = _toChatId;
      eventBus.emit(BaseEvent(BaseEvent.TYPE_UPDATE_CHAT_STATUS, result: result));
    }

  }

  // 下拉刷新
  Future<void> _onRefresh() async{
    //延迟0.02秒
    await Future.delayed(Duration(milliseconds:20),(){
      if(startNum >= PAGE_SIZE){
        startNum = CHAT_TOTAL - PAGE_SIZE * PAGE_NUM;
        _loadMoreData(widget.account, widget.toChatId, startNum, PAGE_SIZE);
      } else if(startNum > 0 && startNum < PAGE_SIZE){
        //不够1页数据,查询全部,然后就不能下一页
        _loadMoreData(widget.account, widget.toChatId, 0, startNum);
        startNum = 0;
      }

    });
  }

  bool isLoadMore = false;
  //上拉加载更多数据
  void _loadMoreData(String fromAccount, String toAccount, int sNum , int pageSize){
    isLoadMore = true;
    ChatRepository.getInstance().findAllChatByAccountPage(fromAccount, toAccount, sNum, pageSize).then((chatList) {
      if(startNum > 0){
        PAGE_NUM++;
      }
      Timer(Duration(milliseconds: 100),() => _controller.jumpTo(AppManager.getInstance().getHeight(context)/3));
      setState(() {
        items.insertAll(0, chatList??[]);
      });

      Timer(Duration(milliseconds: 100),() => isLoadMore = false);

    });

  }

  //检查状态, 如果不可以,先登录
  void _checkAvailable() async{
    var isAvailable = await XmppManager.getInstance().isAvailable();
    if(!isAvailable){
      String account = SpUtils.getString(CommonUtils.LOGIN_ACCOUNT);
      String password = SpUtils.getString(CommonUtils.LOGIN_PASSWORD);
      XmppManager.getInstance().connect(account, password);
    }
  }


  //加载聊天信息
  void loadAllChat() async {
    CHAT_TOTAL = await ChatRepository.getInstance().getChatCountByAccount(widget.account, widget.toChatId)??0;
    startNum = CHAT_TOTAL - PAGE_SIZE * PAGE_NUM;
    ChatRepository.getInstance().findAllChatByAccountPage(widget.account, widget.toChatId, startNum, CHAT_TOTAL).then((chatList) {
      if(startNum > 0){
        PAGE_NUM++;
      }
      setState(() {
        items = chatList??[];
      });
    });
  }

  //加载我的、好友信息
  void loadUserBean() async {
    _otherUserBean = await UserRepository.getInstance().findUserByAccount(_toChatId);
    _meUserBean = await UserRepository.getInstance().findUserByAccount(widget.account);
  }

  //发送消息
  _sendMessage(var message){
    int id = DateTime.now().millisecondsSinceEpoch;
    String account = SpUtils.getString(CommonUtils.LOGIN_ACCOUNT);
    String toJid = "${widget.toChatId}@wangning";
    XmppManager.getInstance().sendMessageWithType(toJid, message, "$account", id);
    Map<String, Object> result = HashMap<String, Object>();
    eventBus.emit(BaseEvent(BaseEvent.TYPE_NEW_MESSAGE, result: result));
  }

  //默认滚动到底部
  void jumpToBottom(int milliseconds){
    if (items.length > 0) {
      Timer(Duration(milliseconds: milliseconds),
              () => _controller.jumpTo(_controller.position.maxScrollExtent));
    }
  }

  //隐藏播放列表,停止播放录音
  hidePlayVoiceList(){
    for(int i = 0; i < items.length;i++){
      items[i].isPlayVoice = false;
    }
    AudioPlayer.getInstance().stop();
    setState(() {
    });
  }

  //刷新多媒体(图片、语音、小视频) (先刷新本地,然后小视频压缩完成再慢慢发送)
  void _refreshMedia(int type, String mediaURL, String thumbnailFileName, {int mediaSecond=0, String messageId = "" }) async {

    bool isNetwork = await CommonNetwork.isNetwork();
    if(!isNetwork) {
      CommonUtils.showNetworkError(context);
      return;
    }

    bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);
    if(deleteContacts){
      WnBaseDialog.showAddFriendsDialog(context, widget.toChatId);
      return;
    }

    String addTime = WnDateUtils.getCurrentTime();

    //先刷新本地聊天
    ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId,addTime:addTime,messageId: messageId,isRead: 1);


    chatBean.contentType = type;
    if(type == CommonUtils.CHAT_CONTENT_TYPE_VOICE){
      chatBean.voiceLocal = mediaURL;
      chatBean.second = mediaSecond;
      //状态变更,向聊天记录中插入新记录
      setState(() {
        items.add(chatBean);
      });
      ChatRepository.getInstance().insertChat(chatBean);
    } else if(type == CommonUtils.CHAT_CONTENT_TYPE_IMG){
      chatBean.imgPathLocal = mediaURL;
      //状态变更,向聊天记录中插入新记录
      setState(() {
        items.add(chatBean);
      });
      ChatRepository.getInstance().insertChat(chatBean);
    } else if(type == CommonUtils.CHAT_CONTENT_TYPE_VIDEO){
      //小视频会刷新本地2次。但messageId都是一样的
      chatBean.videoLocal = mediaURL;
      chatBean.imgPathLocal = thumbnailFileName;
      chatBean.second = mediaSecond;

      ChatBean? localChatBean = await ChatRepository.getInstance().findChatByMessageId(messageId);
      //状态变更,向聊天记录中插入新记录
      if(localChatBean == null){
        items.add(chatBean);
        ChatRepository.getInstance().insertChat(chatBean);
      } else {
        chatBean.id = localChatBean.id;
        ChatRepository.getInstance().updateChat(chatBean);
        //如果已经存在,先删除在添加
        for(int i = 0; i < items.length; i++){
          ChatBean item = items[i];
          if(item.messageId == messageId){
            items.remove(item);
            break;
          }
        }
        items.add(chatBean);
      }

      setState(() {

      });

    }


    //LogUtils.d("滚动到底部3");
    jumpToBottom(100);
  }

  //发送多媒体(图片、语音、小视频)
  void _sendMedia(int type, String mediaURL, {int mediaSecond = 0, String messageId = ""}) async {

    bool isNetwork = await CommonNetwork.isNetwork();
    if(!isNetwork) {
      return;
    }

    bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);
    if(deleteContacts){
      return;
    }

    //上传文件
    ChatBean serverChatBean;
    String message = "";
    ChatSendBean chatSendBean = ChatSendBean();
    chatSendBean.contentType = type;
    chatSendBean.messageId = messageId;
    chatSendBean.addTime = WnDateUtils.getCurrentTime();
    if(type == CommonUtils.CHAT_CONTENT_TYPE_IMG){
      //图片
      serverChatBean = await UploadUtils.getInstance().uploadChatImage(widget.account, widget.toChatId, mediaURL);
      chatSendBean.content = serverChatBean.imgPath??"";
    } else if(type == CommonUtils.CHAT_CONTENT_TYPE_VOICE){
      //语音
      serverChatBean = await UploadUtils.getInstance().uploadChatVoice(widget.account, widget.toChatId, mediaURL);
      chatSendBean.content = serverChatBean.voice??"";
      chatSendBean.second = mediaSecond;
    } else if(type == CommonUtils.CHAT_CONTENT_TYPE_VIDEO){
      //小视频
      serverChatBean = await UploadUtils.getInstance().uploadChatVideo(widget.account, widget.toChatId, mediaURL);
      message = "${type}${CommonUtils.CHAT_MESSAGE_SPILE}${serverChatBean.video}";
      chatSendBean.content = serverChatBean.video??"";
      chatSendBean.second = mediaSecond;
    } else {
      return ;
    }
    message = jsonEncode(chatSendBean);
    _sendMessage(message);
  }

  //是否隐藏文件
  bool hideAdd = true;
  //是否隐藏emoji表情
  bool hideEmoji = true;
  //是否隐藏发送按钮
  bool hideSend = true;
  //是否隐藏添加按钮
  bool hideAddIcon = false;
  //是否按下语音说话
  bool isPressVoice = false;

  //点击空白地方,隐藏文件、emoji
  void _processClickBlank(){
    setState(() {
      hideAdd = true;
      hideEmoji = true;
      _chatContentFocus.unfocus();    // 失去焦点
    });
  }

  //按下录音
  void _processPressVoice(){
    setState(() {
      isPressVoice = !isPressVoice;
      hideEmoji = true;
      hideAdd = true;
      _processFocus();
    });
  }

  //点击emoji表情
  void _processEmoji(){
    setState(() {
      hideEmoji = !hideEmoji;
      isPressVoice = false;
      hideAdd = true;
      _processFocus();
    });
  }

  //点击+按钮
  void _processAdd(){
    setState(() {
      hideAdd = !hideAdd;
      isPressVoice = false;
      hideEmoji = true;
      _processFocus();
    });
  }

  //处理焦点
  void _processFocus(){
    if(!hideAdd || !hideEmoji || isPressVoice){
      _chatContentFocus.unfocus();    // 失去焦点
    } else {
      FocusScope.of(context).requestFocus(_chatContentFocus);     // 获取焦点
    }
  }

  emoticonClick(String name){
    controller.text = name;
  }

  ///选中表情
  _onEmojiSelected(Emoji emoji) {
    controller
      ..text += emoji.emoji
      ..selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
    hideAddIcon = true;
    hideSend = false;
    setState(() {
    });
  }

  ///表情删除按钮
  _onBackspacePressed() {
    controller
      ..text = controller.text.characters.skipLast(1).toString()
      ..selection = TextSelection.fromPosition(
          TextPosition(offset: controller.text.length));
    if (controller.text.isNotEmpty) {
      setState(() {
      });
    }
  }

  //是否已经删除联系人
  Future<bool> isDeleteContacts(String fromAccount, String toAccount) async {
    bool delete = false;
    ContactsBean? contactsBean = await ContactsRepository.getInstance().findContactByFromOrToAccount(fromAccount, toAccount);
    if(contactsBean != null){
      delete = (contactsBean.type == ContactsBean.typeDelete);
    }
    return Future.value(delete);
  }

  //定义发送文本事件的处理函数
  void _handleSubmitted(String text) async {
    if (text.length > 0) {

      bool isNetwork = await CommonNetwork.isNetwork();
      if(!isNetwork) {
        CommonUtils.showNetworkError(context);
        return;
      }

      bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);
      if(deleteContacts){
        WnBaseDialog.showAddFriendsDialog(context, widget.toChatId);
        return;
      }

      int contentType = CommonUtils.CHAT_CONTENT_TYPE_TEXT;
      String addTime = WnDateUtils.getCurrentTime();
      String messageId = UUID.getUUID();
      ChatSendBean chatSendBean = ChatSendBean();
      chatSendBean.contentType = contentType;
      chatSendBean.content = text;
      chatSendBean.addTime = addTime;
      chatSendBean.second = 0;
      chatSendBean.messageId = messageId;
      String message = jsonEncode(chatSendBean);

      _sendMessage(message);
      controller.clear(); //清空输入框
      ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId, content: text,contentType: contentType, addTime: addTime, isRead: 1, messageId: messageId);
      LogUtils.d("插入数据:${chatBean.toJson()}");
      //状态变更,向聊天记录中插入新记录
      setState(() {
        hideAddIcon = false;
        hideSend = true;
        items.add(chatBean);
      });
      await ChatRepository.getInstance().insertChat(chatBean);
      jumpToBottom(100);
    }
  }

  //Emoji表情控件
  Widget getEmojiWidget(){
    return SizedBox(
      height: 200.0,
      width: 1000.0,
      child: EmojiPicker(
          onEmojiSelected: (Category category, Emoji emoji) {
            _onEmojiSelected(emoji);
          },
          onBackspacePressed: _onBackspacePressed,
          config: const Config(
              columns: 7,
              emojiSizeMax: 25.0,
              verticalSpacing: 0,
              horizontalSpacing: 0,
              initCategory: Category.RECENT,
              bgColor: Color(0xFFF2F2F2),
              indicatorColor: Color(0xff65DAC5),
              iconColor: Colors.orange,
              iconColorSelected: Color(0xff65DAC5),
              progressIndicatorColor: Color(0xff65DAC5),
              backspaceColor: Color(0xff65DAC5),
              showRecentsTab: true,
              recentsLimit: 28,
              categoryIcons: CategoryIcons(),
              buttonMode: ButtonMode.MATERIAL)),
    );
  }

  /**刷新红包、转账
   *@contentType 类型
   *@text 内容
   */
  void _refreshRedpacketAndTransfer(int contentType, String text) async {
    if (text.length > 0) {

      bool isNetwork = await CommonNetwork.isNetwork();
      if(!isNetwork) {
        CommonUtils.showNetworkError(context);
        return;
      }

      String messageId = UUID.getUUID();
      String addTime = WnDateUtils.getCurrentTime();
      ChatSendBean chatSendBean = ChatSendBean();
      chatSendBean.contentType = contentType;
      chatSendBean.content = text;
      chatSendBean.addTime = addTime;
      chatSendBean.second = 0;
      chatSendBean.messageId = messageId;
      String message = jsonEncode(chatSendBean);

      _sendMessage(message);
      controller.clear(); //清空输入框
      ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId, content: text,contentType: contentType, addTime: addTime, isRead: 1, messageId: messageId);
      await ChatRepository.getInstance().insertChat(chatBean);
      //状态变更,向聊天记录中插入新记录
      setState(() {
        items.add(chatBean);
      });
      jumpToBottom(100);
    }

  }

  //刷新转账
  void _refreshTransfer(int position) async {
    ChatBean chatBean = items[position];
    chatBean.isClick = 1;
    setState(() {

    });
  }


  void _initScreenUtil(BuildContext context) {
    ScreenUtil.init(
        BoxConstraints(
            maxWidth: MediaQuery.of(context).size.width,
            maxHeight: MediaQuery.of(context).size.height),
        designSize: const Size(375, 812),
        context: context);
  }

}

chat_add_view.dart实现:

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/9/24 14:46
 * Description : 聊天点击+按钮
 * 1单聊支持:相册、拍照、视频通话、语音通话、红包、转账
 * 2群聊支持:相册、拍照
 */
class ChatAddView extends StatefulWidget{

  //刷新列表
  final refreshMediaCallback;
  //发送信息
  final sendMedialCallback;
  //聊天id
  final String toChatId;

  //刷新红包、转账
  final refreshRedpacketAndTransfer;

  //1单聊, 2群聊
  final int viewType;

  ChatAddView({required this.viewType, required this.toChatId, required this.refreshMediaCallback, required this.sendMedialCallback, required this.refreshRedpacketAndTransfer});

  @override
  State<StatefulWidget> createState() => _ChatAddState();

}

class _ChatAddState extends State<ChatAddView>{

  @override
  Widget build(BuildContext context) {
    return getAddWidget();

  }

  //相册
  List ablums = [CommonUtils.getBaseIconUrlPng("wc_chat_album_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_album_selected")];
  int ablumsPosition = 0;

  //拍照
  List takePhotos = [CommonUtils.getBaseIconUrlPng("wc_chat_video_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_video_selected")];
  int takePhotosPosition = 0;

  //视频通话
  List videoCalls = [CommonUtils.getBaseIconUrlPng("wc_chat_video_call_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_video_call_selected")];
  int videoCallsPosition = 0;

  //语音通话
  List voiceCalls = [CommonUtils.getBaseIconUrlPng("wc_chat_voice_call_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_voice_call_selected")];
  int voiceCallsPosition = 0;

  //红包
  List redPackets = [CommonUtils.getBaseIconUrlPng("wc_chat_redpacket_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_redpacket_selected")];
  int redPacketsPosition = 0;

  //转账
  List transfers = [CommonUtils.getBaseIconUrlPng("wc_chat_transfer_normal"), CommonUtils.getBaseIconUrlPng("wc_chat_transfer_selected")];
  int transfersPosition = 0;

  //相册
  final TYPE_ABLUM = 1;
  //拍照
  final TYPE_TAKE_PHOTO = 2;
  //视频通话
  final TYPE_VIDEO_CALL = 3;
  //语音通话
  final TYPE_VOICE_CALL = 4;
  //红包
  final TYPE_RED_PACKET = 5;
  //转账
  final TYPE_TRANSFER = 6;

  //改变状态
  void _changeStatus(int type, int position){
    if(type == TYPE_ABLUM){
      ablumsPosition = position;
      ablums[ablumsPosition];
    } else if(type == TYPE_TAKE_PHOTO){
      takePhotosPosition = position;
      takePhotos[takePhotosPosition];
    } else if(type == TYPE_VIDEO_CALL){
      videoCallsPosition = position;
      videoCalls[videoCallsPosition];
    } else if(type == TYPE_VOICE_CALL){
      voiceCallsPosition = position;
      voiceCalls[voiceCallsPosition];
    } else if(type == TYPE_RED_PACKET){
      redPacketsPosition = position;
      redPackets[redPacketsPosition];
    } else if(type == TYPE_TRANSFER){
      transfersPosition = position;
      transfers[transfersPosition];
    }

    setState(() {
    });
  }

  Widget getAddWidget(){
    return Container(
        //margin: EdgeInsets.only(top: 40, bottom: AppManager.getInstance().getBottom(context) + 20),
        alignment: Alignment.center,
        child: Center(
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                //交叉轴的布局方式,对于column来说就是水平方向的布局方式
                crossAxisAlignment: CrossAxisAlignment.center,
                //就是字child的垂直布局方向,向上还是向下
                verticalDirection: VerticalDirection.down,
                children: [
                  _buildBottomItem(TYPE_ABLUM),
                  _buildBottomItem(TYPE_TAKE_PHOTO),
                  Offstage(
                    offstage: widget.viewType == CommonUtils.VIEW_TYPE_GROUP_CHAT,
                    child: _buildBottomItem(TYPE_VIDEO_CALL),
                  ),
                  Offstage(
                    offstage: widget.viewType == CommonUtils.VIEW_TYPE_GROUP_CHAT,
                    child: _buildBottomItem(TYPE_VOICE_CALL),
                  ),

                ],
              ),

          Offstage(
            offstage: widget.viewType == CommonUtils.VIEW_TYPE_GROUP_CHAT,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              //交叉轴的布局方式,对于column来说就是水平方向的布局方式
              crossAxisAlignment: CrossAxisAlignment.center,
              //就是字child的垂直布局方向,向上还是向下
              verticalDirection: VerticalDirection.down,
              children: [
                _buildBottomItem(TYPE_RED_PACKET),
                _buildBottomItem(TYPE_TRANSFER),
                _buildBottomItem(-1),
                _buildBottomItem(-1),
              ],
            ),
          ),


            ],
          ),
        ),
    );
  }

  Widget _buildBottomItem(int type){
    return Container(
      alignment: Alignment.center,
      margin: EdgeInsets.only(top: 10, bottom: 10),
      child: GestureDetector(
        child: _getBottomWidget(type),
        onTap: (){
          _changeStatus(type,0);
          if(type == TYPE_ABLUM){
            _openAblumPermission();
          } else if(type == TYPE_TAKE_PHOTO){
            _takePhotoPermission();
          } else if (type == TYPE_VIDEO_CALL){
            _openVideoCall();
          } else if (type == TYPE_VOICE_CALL){
            _openVoiceCall();
          } else if (type == TYPE_RED_PACKET){
            _openRedPacket();
          } else if (type == TYPE_TRANSFER){
            _openTransfer();
          }
        },
        onTapCancel: (){
          _changeStatus(type,0);
        },
        onTapDown: (data){
          _changeStatus(type,1);
        },
      ),
    );
  }


  Widget _getBottomWidget(int type){
    if(type == TYPE_ABLUM){
      return Column(
        children: [
          Image.asset(ablums[ablumsPosition], width: 50, height: 50,),
          const Text("相册"),
        ],
      );
    } else if(type == TYPE_TAKE_PHOTO){
      return Column(
        children: [
          Image.asset(takePhotos[takePhotosPosition], width: 50, height: 50,),
          const Text("拍照"),
        ],
      );
    } else if(type == TYPE_VIDEO_CALL){
      return Column(
        children: [
          Image.asset(videoCalls[videoCallsPosition], width: 50, height: 50,),
          const Text("视频通话"),
        ],
      );
    } else if(type == TYPE_VOICE_CALL){
      return Column(
        children: [
          Image.asset(voiceCalls[voiceCallsPosition], width: 50, height: 50,),
          const Text("语音通话"),
        ],
      );
    } else if(type == TYPE_RED_PACKET){
      return Column(
        children: [
          Image.asset(redPackets[redPacketsPosition], width: 50, height: 50,),
          const Text("红包"),
        ],
      );
    } else if(type == TYPE_TRANSFER){
      return Column(
        children: [
          Image.asset(transfers[transfersPosition], width: 50, height: 50,),
          const Text("转账"),
        ],
      );
    } else {
      //空白占位符
      return Column(
        children: [
          Container(
            width: 50,
            height: 50,
          ),
          Text(""),
        ],
      );
    }
  }

  //打开相册权限
  void _openAblumPermission() async {
    bool isPhotosGranted = await Permission.photos.isGranted;
    bool isPhotosDenied = await Permission.photos.isDenied;
    if(isPhotosGranted){
      _openAblum();
    } else {
      if(isPhotosDenied){
        _openAblum();
      } else {
        //跳转到设置页面提示
        _showPhotosConfirmationAlert(context);
      }
    }
  }

  // 为正常拍摄,请前往设备中的【设置】> 【隐私】> 【相机】中允许无他相机使用
  _showPhotosConfirmationAlert(BuildContext context) {
    showPlatformDialog(
      context: context,
      builder: (_) => BasicDialogAlert(
        title: Text("无法使用相册"),
        content: Text("为编辑照片,请前往设备中的【设置】> 【隐私】> 【照片】中允许${AppManager.getInstance().appName}使用"),
        actions: <Widget>[
          BasicDialogAction(
            title: Text("知道了"),
            onPressed: () {
              Navigator.pop(context);
            },
          ),
          BasicDialogAction(
            title: Text("去设置"),
            onPressed: () {
              // 跳转到系统设置页
              AppSettings.openAppSettings();
            },
          ),
        ],
      ),
    );
  }

  //打开相册
  void _openAblum() {
    LogUtils.d("打开相册");
    List<AssetEntity> selectedAssets = [];
    AssetPicker.pickAssets(
      context,
      pickerConfig: AssetPickerConfig(
        maxAssets: 1,
        selectedAssets: selectedAssets,
      ),
    ).then((imageList) {
      if(imageList == null){
        return;
      }
      imageList as List<AssetEntity>;
      for(int i = 0; i < imageList.length; i++){
        AssetEntity ae = imageList[i];
        ae.file.then((file) async {
          String resultFilePath = file?.path??"";
          _processVideoAndPicture(resultFilePath);
        });
      }
    });
  }

  //拍照权限
  _takePhotoPermission() async{
    bool isCameraGranted = await Permission.camera.isGranted;
    bool isCameraDenied = await Permission.camera.isDenied;
    bool isMicrophoneGranted = await Permission.microphone.isGranted;
    bool isMicrophoneDenied = await Permission.microphone.isDenied;
    LogUtils.d("拍照:${isCameraGranted}, ${isCameraDenied} , ${isMicrophoneGranted} , ${isMicrophoneDenied}");
    //如果2个权限都同意,直接打开
    if(isCameraGranted && isMicrophoneGranted){
      _takePhoto();
    } else if(isCameraDenied && isMicrophoneDenied){
      //如果2个权限都拒绝,直接打开
      _takePhoto();
    } else if(!isCameraGranted && isMicrophoneGranted){
      _takePhoto();
    } else if(isCameraGranted && !isCameraDenied){
      //提示设置麦克风权限
      String title = "无法使用麦克风";
      String content = "为正常录制声音,请前往设备中的【设置】> 【隐私】> 【麦克风】中允许${AppManager.getInstance().appName}使用";
      WnBaseDialog.showPermissionDialog(context, title: title, content: content);
    } else if(!isCameraDenied){
      String title = "无法使用相机";
      String content = "为正常拍摄,请前往设备中的【设置】> 【隐私】> 【相机】中允许${AppManager.getInstance().appName}使用";
      WnBaseDialog.showPermissionDialog(context, title: title, content: content);

    } else if(!isMicrophoneDenied){
      //提示设置麦克风权限
      LogUtils.d("拍照7");
      String title = "无法使用麦克风";
      String content = "为正常录制声音,请前往设备中的【设置】> 【隐私】> 【麦克风】中允许${AppManager.getInstance().appName}使用";
      WnBaseDialog.showPermissionDialog(context, title: title, content: content);
    }
  }

  //拍照
  void _takePhoto(){
    LogUtils.d("拍照");
    Feedback.forTap(context);
    CameraPicker.pickFromCamera(
        context,
        pickerConfig: const CameraPickerConfig(enableRecording: true, textDelegate: CameraPickerTextDelegate()),
        useRootNavigator: false
    ).then((resultAssetEntity) {
      resultAssetEntity?.file.then((resultFile) {
        LogUtils.d("2拍照返回:${resultFile?.path}");
        _processVideoAndPicture(resultFile?.path??"");
      });
    });
  }

  //打开红包
  void _openRedPacket() async {
    var balanceStr = await Navigator.push(context, MaterialPageRoute(builder: (context) => RedPacketWidget()));
    if(balanceStr == null){
      return;
    }
    widget.refreshRedpacketAndTransfer(CommonUtils.CHAT_CONTENT_TYPE_REDPACKET, balanceStr);
  }

  //打开转账
  void _openTransfer() async {
    var balanceStr = await Navigator.push(context, MaterialPageRoute(builder: (context) => PaymentTransfer(toUser: widget.toChatId,)));
    if(balanceStr == null){
      return;
    }
    widget.refreshRedpacketAndTransfer(CommonUtils.CHAT_CONTENT_TYPE_TRANSFER, balanceStr);
  }

  //打开语音通话
  void _openVoiceCall() async{
    Navigator.push(context, MaterialPageRoute(builder: (context) => VideoCallWidget(videoPeerId: widget.toChatId, mediaFlag: CommonUtils.MEDIA_FLAG_VOICE,)));
  }

  //打开视频通话
  void _openVideoCall(){
    Navigator.push(context, MaterialPageRoute(builder: (context) => VideoCallWidget(videoPeerId: widget.toChatId, mediaFlag: CommonUtils.MEDIA_FLAG_VIDEO,)));
  }

  //处理图片和小视频(相册、拍照)
  void _processVideoAndPicture(String resultFilePath) async {

    if(resultFilePath == null || "" == resultFilePath){
      return;
    }

    String messageId = UUID.getUUID();

    if(CommonUtils.isImage(resultFilePath)){
      //压缩图片完成再发送
      String compressImagePath = await CompressImageUtils.compressFile(fileName: resultFilePath);
      widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_IMG, compressImagePath,0 ,messageId);
      widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_IMG, compressImagePath, "",0, messageId);
    } else if(CommonUtils.isVideo(resultFilePath)){
      /**
       * 小视频发送流程,因为小视频比较大,压缩时间比较长。发送的视频,先本地优先显示,查看播放小视频
       * 1、先复制一份小视频
       * 2、生成缩略图显示
       * 3、压缩小视频
       * 4、删除复制的小视频
       */
      //_testmp4(resultFilePath);

      //最大100M的视频, 不是1024*1024*500 , 使用1000*100*500
      int maxSize = 100000000;
      int fileSize = File(resultFilePath).lengthSync();
      if(fileSize > maxSize){
        CommonToast.show(context, "上传视频大小不能超过100M", duration: 3);
        return ;
      }

      //小视频生成缩略图大概5秒左右,先刷新站位图
      widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, resultFilePath, "", 0, messageId);

      String videoFormat = await getVideoFormat(resultFilePath);
      File srcFile = File(resultFilePath);
      String newVideoFileName = await FileUtils.getBaseFile("new_${DateUtil.getNowDateMs()}.mp4");
      srcFile.copySync(newVideoFileName);

      String thumbnailFileName = await FileUtils.getBaseFile("thum_${DateUtil.getNowDateMs()}.png");
      //生成缩略图
      await VideoThumbnail.thumbnailFile(video: resultFilePath, thumbnailPath: thumbnailFileName);
      //获取视频时间
      int second = await getVideoTime(resultFilePath);
      //int size = File(resultFilePath).lengthSync();
      //先刷新
      widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, resultFilePath, thumbnailFileName, second, messageId);
      //压缩完成再发送

      MediaInfo? mediaInfo = await CompressVideoUtils.compressVideo(newVideoFileName);
      String compressVideoPath = mediaInfo?.path??"";

      //int csecond = await getVideoTime(resultFilePath);
      //int csize = File(compressVideoPath).lengthSync();

      widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, compressVideoPath, second, messageId);
    }
  }

  //获取视频格式
  Future<String> getVideoFormat(String resultFilePath) async {
    String videoFormat = "";
    final FlutterFFprobe _flutterFFprobe =  FlutterFFprobe();
    MediaInformation info = await _flutterFFprobe.getMediaInformation(resultFilePath);
    if (info.getStreams() != null) {
      List<StreamInformation>? streams = info.getStreams();
      if (streams != null && streams.length > 0) {
        for (var stream in streams) {
          videoFormat =  stream.getAllProperties()['codec_tag_string'];
        }
      }
    }
    return videoFormat;
  }

  //获取视频时间
  Future<int> getVideoTime(String resultFilePath) async {
    int time = 0;
    final FlutterFFprobe _flutterFFprobe =  FlutterFFprobe();
    MediaInformation info = await _flutterFFprobe.getMediaInformation(resultFilePath);
    if (info.getStreams() != null) {
      String duration = info.getMediaProperties()?['duration'];
      String size = info.getMediaProperties()?['size'];
      double durationDouble = double.parse(duration);
      time = durationDouble.toInt();
      LogUtils.d("多媒体文件大小:${size}");
    }
    return time;
  }

  void _testmp4(String resultFilePath){
    final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe();

    _flutterFFprobe.getMediaInformation(resultFilePath).then((info) {
      LogUtils.d("测试视频信息:Media Information");
      LogUtils.d("测试视频信息:Path: ${info.getMediaProperties()?['filename']}");
      LogUtils.d("测试视频信息:Format: ${info.getMediaProperties()?['format_name']}");
      LogUtils.d("测试视频信息:Duration: ${info.getMediaProperties()?['duration']}");
      LogUtils.d("测试视频信息:Start time: ${info.getMediaProperties()?['start_time']}");
      LogUtils.d("测试视频信息:Bitrate: ${info.getMediaProperties()?['bit_rate']}");
      //LogUtils.d("测试视频信息:CodecTagString: ${info.getMediaProperties()?['codec_tag_string']}");

      Map<dynamic, dynamic> tags = info.getMediaProperties()?['tags'];
      /*if (tags != null) {
        tags.forEach((key, value) {
          LogUtils.d("Tag: " + key + ":" + value + "\n");
        });
      }*/

      if (info.getStreams() != null) {
        List<StreamInformation>? streams = info.getStreams();

        if (streams != null && streams.length > 0) {
          for (var stream in streams) {
            LogUtils.d("测试视频信息:Stream id: ${stream.getAllProperties()['index']}");
            LogUtils.d("Stream type: ${stream.getAllProperties()['codec_type']}");
            LogUtils.d("Stream codec: ${stream.getAllProperties()['codec_name']}");
            LogUtils.d("Stream full codec: ${stream.getAllProperties()['codec_long_name']}");
            LogUtils.d("Stream format: ${stream.getAllProperties()['pix_fmt']}");
            LogUtils.d("Stream width: ${stream.getAllProperties()['width']}");
            LogUtils.d("Stream height: ${stream.getAllProperties()['height']}");
            LogUtils.d("Stream bitrate: ${stream.getAllProperties()['bit_rate']}");
            LogUtils.d("Stream sample rate: ${stream.getAllProperties()['sample_rate']}");
            LogUtils.d("Stream sample format: ${stream.getAllProperties()['sample_fmt']}");
            LogUtils.d("Stream channel layout: ${stream.getAllProperties()['channel_layout']}");
            LogUtils.d("Stream sar: ${stream.getAllProperties()['sample_aspect_ratio']}");
            LogUtils.d("Stream dar: ${stream.getAllProperties()['display_aspect_ratio']}");
            LogUtils.d("Stream average frame rate: ${stream.getAllProperties()['avg_frame_rate']}");
            LogUtils.d("Stream real frame rate: ${stream.getAllProperties()['r_frame_rate']}");
            LogUtils.d("Stream time base: ${stream.getAllProperties()['time_base']}");
            LogUtils.d("测试视频信息:Stream codec time base: ${stream.getAllProperties()['codec_time_base']}");
            LogUtils.d("A测试视频信息:Stream codec_tag_string: ${stream.getAllProperties()['codec_tag_string']}");

            /*Map<dynamic, dynamic> tags = stream.getAllProperties()['tags'];
            if (tags != null) {
              tags.forEach((key, value) {
                LogUtils.d("Stream tag: " + key + ":" + value + "\n");
              });
            }*/
          }
        }
      }
    });
  }

}

chat_content_view.dart实现:

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/9/24 12:09
 * Description : 单聊内容控件
 */
class ChatContentView extends StatefulWidget {

  final List<ChatBean> items;
  final ChatBean chatBean;
  final int index;
  final UserBean? otherUserBean;
  final UserBean? meUserBean;
  final String account;
  final deleteCallback;
  final List<String>? addTimeList;
  //点击语音播放回调
  final clickVoiceCallback;

  //点击领取转账,刷新页面
  final refreshTransfer;

  ChatContentView({required this.items,  required this.account, required this.chatBean, required this.index,
    required this.meUserBean, required this.otherUserBean, this.addTimeList, required this.deleteCallback, required this.clickVoiceCallback, required this.refreshTransfer});

  @override
  State<ChatContentView> createState() => _ChatContentViewState();

}


class _ChatContentViewState extends State<ChatContentView> {
  //判断是否已经存在转换好的时间
  @override
  void initState() {
    super.initState();
  }

  void goNewFriends(String account) async{
    UserBean? userBean = await UserRepository.getInstance().findUserByAccount(account);
    if(userBean != null){
      Navigator.push(context,MaterialPageRoute(builder: (context)=>AddFriends(userBean: userBean!,)));
    } else {
      userBean = await UserRepository.getInstance().getUserServer(account);
      Navigator.push(context,MaterialPageRoute(builder: (context)=>AddFriends(userBean: userBean!,)));
    }
  }

  @override
  Widget build(BuildContext context) {
    String addTimeResult = _getAddTime("${widget.chatBean.addTime}");
    bool isExistTime = isExistAddTime(addTimeResult);
    if(!isExistTime){
      widget.addTimeList?.add(addTimeResult);
    }
    //如果是最后一个,清除标志
    if(widget.index == widget.items.length -1){
      widget.addTimeList?.clear();
    }
    return Column(
      children: [
        Offstage(
          offstage: isExistTime,
          child: Container(
            margin: EdgeInsets.only(top: 12),
            child: Text("${addTimeResult}"),
          ),
        ),

        Container(
          child: widget.account == widget.chatBean.fromAccount
              ? fromAccountWidget()
              : toAccountWidget(),
        )
      ],
    );
  }

  //小视频缩略图
  Widget getCommonThumbnail(int second){
    return CommonThumbnailWidget(
        padding: EdgeInsets.only(
            top: 0.0,
            right: (widget.account == widget.chatBean.fromAccount ? 0.0 : 5.0),
            left: (widget.account == widget.chatBean.toAccount ? 2.0 : 0.0)),
        image: widget.chatBean.imgPathLocal??"",
        second: second,
        onPressed: () {
          Navigator.push(context, MaterialPageRoute(builder: (context) => VideoPlayLocalPreview(widget.chatBean.videoLocal!)));
        });
  }

  //显示我的
  Widget fromAccountWidget(){
    return Container(
      margin: EdgeInsets.only(top: 8.0, left: 68.0, right: 8),
      padding: EdgeInsets.all(2.0),
      child: Row(
        children: <Widget>[
          Expanded(
            child: GestureDetector(
              onLongPress: (){
                _showDeleteDialog(widget.chatBean);
              },
              onTap: () {
              },
              child: Stack(

                alignment: AlignmentDirectional.bottomEnd,
                children: [
                  //文本
                  widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TEXT?meTextWidget():Container(),

                  //语言
                  widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VOICE?meVoiceWidget():Container(),

                  //图片
                  widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_IMG?CommonUtils.showBaseImage(widget.chatBean.imgPathLocal??"", width:100, height:200, angle:1, onPressed: (data){
                    Navigator.push(context,MaterialPageRoute(builder: (context)=>CommonImagePreview(fileName: data)));
                  }):Container(),

                  //小视频
                  widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VIDEO?getCommonThumbnail(widget.chatBean.second??0):Container(),

                  //红包
                  widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_REDPACKET?meRedpacketWidget():Container(),

                  //转账
                  widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TRANSFER?meTransferWidget():Container(),

                ],
              ),
            ),
          ),
          //userImage
          Container(
            padding: EdgeInsets.only(left: 6, right: 6),
            child: GestureDetector(
              onTap: (){
                Navigator.push(context,MaterialPageRoute(builder: (context)=>ContactsDetails(toChatId: widget.chatBean.fromAccount??"")));
              },
              child: CommonAvatarView.showBaseImage(widget.meUserBean?.avatar??"", 38, 38),
            ),
          ),

        ],
      ),
    );
  }

  //显示好友
  Widget toAccountWidget(){
    return Container(
      margin: EdgeInsets.only(top: 8.0, right: 68.0),
      padding: EdgeInsets.all(2.0),
      child: Row(
        //crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          //userImage,
          Container(
            margin: EdgeInsets.only(left: 6, right: 6),
            child: GestureDetector(
              onTap : (){
                Navigator.push(context,MaterialPageRoute(builder: (context)=>ContactsDetails(toChatId: widget.otherUserBean?.account??"")));
              },
              child: CommonAvatarView.showBaseImage(widget.otherUserBean?.avatar??"", 38, 38),
            ),
          ),
          Expanded(
            child: GestureDetector(

                onLongPress: (){
                  _showDeleteDialog(widget.chatBean);
                },
                onTap: () {
                },

                child: Stack(
                  alignment: AlignmentDirectional.centerStart,
                  children: [

                    //文本
                    widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TEXT?toTextWidget():Container(),

                    //语音
                    widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VOICE?toVoiceWidget():Container(),

                    //图片
                    widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_IMG?CommonUtils.showBaseImage(widget.chatBean.imgPathLocal??"", width:100, height:200, angle:1, onPressed: (data){
                      Navigator.push(context,MaterialPageRoute(builder: (context)=>CommonImagePreview(fileName: data)));
                    }):Container(),

                    //小视频
                    widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_VIDEO?getCommonThumbnail(widget.chatBean.second??0):Container(),

                    //红包
                    widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_REDPACKET?toRedpacketWidget():Container(),

                    //转账
                    widget.chatBean.contentType == CommonUtils.CHAT_CONTENT_TYPE_TRANSFER?toTransferWidget():Container(),


                  ],
                )
            ),
          ),
          /**/
        ],
      ),
    );
  }


  //打开红包对话框
  void _onOpenRedpacket(){
  }

  //朋友的文本
  Widget toTextWidget(){
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Container(
          padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0),
          decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Color(0xFFEDEDED)),
          child: Text(
            widget.chatBean.content??"",
            textAlign: TextAlign.left,
            style: TextStyle(color: Colors.black, fontSize: 20.0),
          ),
        )
      ],
    );
  }

  //朋友的语音
  Widget toVoiceWidget(){
    return InkWell(
        onTap: () {
          setState(() {
            widget.chatBean.isPlayVoice = true;
          });
          LogUtils.d("点击语音");
          AudioPlayer.getInstance().playLocal(widget.chatBean.voiceLocal??"", callback: (data){
            LogUtils.d("录音回调:${data}");
            setState(() {
              widget.chatBean.isPlayVoice = false;
            });
          });
        },
        child : Container(
          width: 120,
          height: 40,
          padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 2.0),
          decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Color(0xFFEDEDED)),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              widget.chatBean.isPlayVoice?Image.asset("assets/chat/wn_chat_other_animator.gif", height: 34,):Image.asset("assets/chat/wn_chat_other_volume_3.png",  height: 34,),
              SizedBox(width: 4,),
              Text("${widget.chatBean.second}''"),
            ],
          ),

        )
    );
  }

  //朋友的红包
  Widget toRedpacketWidget(){
    return GestureDetector(
      onTap: (){
        if(widget.chatBean.isClick == 1){
          Navigator.push(context, MaterialPageRoute(builder: (context) => ReceiveRedpacketSuccess(fromUser: widget.meUserBean?.account??"", toUser: widget.otherUserBean?.account??"", balance: widget.chatBean?.content??"", addTime: widget.chatBean.addTime??"",)));
        } else {
          showRedPacket(context, _onOpenRedpacket, widget.otherUserBean?.account, widget.chatBean?.content??"", widget.index);
        }
      },
      child: Opacity(
        opacity: widget.chatBean.isClick == 1 ? 0.6 :1,
        child: Container(
          child: Stack(
            children: [
              toRedpacketBackground(),

              Positioned(
                left: 38, top: 20,
                child:CommonUtils.getBaseIconPng("wc_redpacket_icon", width: 40, height: 40),
              ),

              Positioned(
                left: 88, top: 30,
                child: Text("恭喜发财,大吉大利", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),
              ),

              Positioned(
                left: 88, top: 50,
                child: Container(
                  margin: EdgeInsets.only(top:10),
                  width: 120,
                  height: 1,
                  color: Colors.white,
                ),
              ),

              Positioned(
                left: 38, bottom: 14,
                child:Text("私人红包", style: TextStyle(fontSize:12, color: Colors.white38),),
              ),

            ],
          ),
        ),
      ),
    );
  }

  //处理转账
  void _processTransferDetails() async{
    var data = await Navigator.push(context, MaterialPageRoute(builder: (context) => TransferDetails(toUser: widget.chatBean?.toAccount??"", balance: double.parse(widget.chatBean?.content??""), messageId: widget.chatBean?.messageId??"")));
    if(data != null && data > 0){
      widget.refreshTransfer(widget.index);
    }
  }

  //朋友的转账
  Widget toTransferWidget(){
    return GestureDetector(
      onTap: (){
        _processTransferDetails();
      },
      child: Opacity(
        opacity: widget.chatBean.isClick == 1 ? 0.6 :1,
        child: Container(
          child: Stack(
            children: [
              toRedpacketBackground(),

              Positioned(
                left: 42, top: 20,
                child:CommonUtils.getBaseIconPng("wc_chat_transfer_icon", width: 40, height: 40),
              ),

              Positioned(
                left: 98, top: 14,
                child: Text("¥${double.parse(widget.chatBean.content??'0').toStringAsFixed(2)}", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold),),
              ),

              Positioned(
                left: 98, top: 40,
                child: Text("请收款", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),
              ),

              Positioned(
                left: 98, top: 54,
                child: Container(
                  margin: EdgeInsets.only(top:10),
                  width: 120,
                  height: 1,
                  color: Colors.white,
                ),
              ),

              Positioned(
                left: 38, bottom: 14,
                child:Text("私人转账", style: TextStyle(fontSize: 12, color: Colors.white38),),
              ),

            ],
          ),
        ),
      ),
    );
  }

  Widget toRedpacketBackground(){
    return CustomPaint(
      painter: RedPacketOther(
        strokeColor: Color(0xFFf58220),
        paintingStyle:
        PaintingStyle.fill,
      ),
      child: Container(
        height: 100,
        width: 280,
      ),
    );
  }

  //我的文本
  Widget meTextWidget(){
    return Column(
      // Column被Expanded包裹起来,使其内部文本可自动换行
      crossAxisAlignment: CrossAxisAlignment.end,
      children: <Widget>[
        Container(
          padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0),
          decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Color(0xFF9EEA6A),),
          child: Text(
            widget.chatBean.content??"",
            textAlign: TextAlign.left,
            style: TextStyle(color: Colors.black, fontSize: 20.0),
          ),
        )
      ],
    );
  }

  //我的语言
  Widget meVoiceWidget(){
    return InkWell(
        onTap: () {
          widget.clickVoiceCallback(true);
          setState(() {
            widget.chatBean.isPlayVoice = true;
          });
          //点击语音
          AudioPlayer.getInstance().playLocal(widget.chatBean.voiceLocal??"", callback: (data){
            //录音回调
            setState(() {
              widget.chatBean.isPlayVoice = false;
            });
          });
        },
        child : Container(
          width: 120,
          height: 40,
          padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 2.0),
          decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(1.0),),color: Color(0xFF9EEA6A),),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text("${widget.chatBean.second}''"),
              SizedBox(width: 4,),
              widget.chatBean.isPlayVoice?Image.asset("assets/chat/wn_chat_me_animator.gif", height: 24,):Image.asset("assets/chat/wn_chat_me_volume_3.png", height: 24,),
            ],
          ),
        )
    );
  }

  //我的红包
  Widget meRedpacketWidget(){
    return GestureDetector(
      onTap: (){
        //点击红包
        Navigator.push(context, MaterialPageRoute(builder: (context) => ReceiveRedpacketSuccess(fromUser: widget.meUserBean?.account??"", toUser: widget.otherUserBean?.account??"", balance: widget.chatBean?.content??"", addTime: widget.chatBean.addTime??"",)));
      },
      child: Container(
        child: Stack(
          children: [
            meRedpacketBackground(),

            Positioned(
              left: 20, top: 20,
              child:CommonUtils.getBaseIconPng("wc_redpacket_icon", width: 40, height: 40),
            ),

            Positioned(
              left: 70, top: 30,
              child: Text("恭喜发财,大吉大利", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),
            ),

            Positioned(
              left: 70, top: 50,
              child: Container(
                margin: EdgeInsets.only(top:10),
                width: 120,
                height: 1,
                color: Colors.white,
              ),
            ),

            Positioned(
              left: 20, bottom: 14,
              child:Text("私人红包", style: TextStyle(fontSize: 12, color: Colors.white38),),
            ),

          ],
        ),
      ),
    );
  }

  //我的转账
  Widget meTransferWidget(){
    return GestureDetector(
      onTap: (){
        //点击转账
        Navigator.push(context, MaterialPageRoute(builder: (context) => TransferDetails(toUser: widget.otherUserBean?.account??"", balance: double.parse(widget.chatBean?.content??""), messageId: widget.chatBean?.messageId??"")));
      },
      child: Container(
        child: Stack(
          children: [
            meRedpacketBackground(),

            Positioned(
              left: 20, top: 20,
              child:CommonUtils.getBaseIconPng("wc_chat_transfer_icon", width: 40, height: 40),
            ),

            Positioned(
              left: 70, top: 14,
              child: Text("¥${double.parse(widget.chatBean.content??'0').toStringAsFixed(2)}", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold),),
            ),

            Positioned(
              left: 70, top: 40,
              child: Text("你发起了一笔转账", style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold),),
            ),

            Positioned(
              left: 70, top: 54,
              child: Container(
                margin: EdgeInsets.only(top:10),
                width: 120,
                height: 1,
                color: Colors.white,
              ),
            ),

            Positioned(
              left: 20, bottom: 14,
              child:Text("私人转账", style: TextStyle(fontSize: 12, color: Colors.white38),),
            ),

          ],
        ),
      ),
    );
  }

  Widget meRedpacketBackground(){
    return CustomPaint(
      painter: RedPacketMe(
        strokeColor: Color(0xFFf58220),
        paintingStyle:
        PaintingStyle.fill,
      ),
      child: Container(
        height: 100,
        width: 280,
      ),
    );
  }

  bool isExistAddTime(String addTimeResult){
    return widget.addTimeList?.contains(addTimeResult)??false;
  }

  String _getAddTime(String addTime){
    return WnTimeUtils.timeUtils(startTime:  addTime);
  }

  //删除对话框
  Future<void> _showDeleteDialog(ChatBean chatBean) async {
    return showDialog<Null>(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text('确定要删除该消息吗?', style: new TextStyle(fontSize: 17.0)),
            actions: <Widget>[
              MaterialButton(
                child: Text('取消'),
                onPressed: (){
                  LogUtils.d("确定取消");
                  Navigator.of(context).pop();
                },
              ),
              MaterialButton(
                child: Text('确定'),
                onPressed: (){
                  LogUtils.d("确定删除");
                  Navigator.pop(context);
                  //_deleteContacts(contactsBeanComb);
                  _deleteChatBean(chatBean);
                },
              )
            ],
          );
        }
    );
  }

  //删除消息
  _deleteChatBean(ChatBean chatBean) async{
    int id = chatBean.id??0;
    await ChatRepository.getInstance().deleteChatById(id);
    widget.deleteCallback(true);
  }

}

chat_voice_view.dart实现:

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/9/24 12:21
 * Description : 语音动画
 */

class ChatVoiceView extends StatefulWidget{

  //刷新列表
  final refreshMediaCallback;
  //发送信息
  final sendMedialCallback;
  //停止语音播放
  final stopPlayVoiceCallback;

  ChatVoiceView({required this.refreshMediaCallback, required this.sendMedialCallback, required this.stopPlayVoiceCallback});

  @override
  State<StatefulWidget> createState() =>  ChatVoiceState();

}

class ChatVoiceState extends State<ChatVoiceView>{

  // 倒计时总时长
  int _countTotal = 60;
  double starty = 0.0;
  double offset = 0.0;
  bool isUp = false;
  String textShow = "按住 说话";
  String toastShow = "手指上滑,取消发送";
  String voiceIco = CommonUtils.getChatUrlPng("voice_volume_1");
  ///默认隐藏状态
  bool voiceState = true;
  Timer? _timer;
  int _count = 0;
  //录音总数
  int _soundSecond = 0;
  OverlayEntry? overlayEntry;
  final _audioRecorder = Record();

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _audioRecorder.dispose();
    _timer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return getPressVoiceWidget();
  }

  //打开录音权限
  void _openMicrophonePermission(details) async {
    bool isMicrophoneGranted = await Permission.microphone.isGranted;
    bool isMicrophoneDenied = await Permission.microphone.isDenied;
    if(isMicrophoneGranted){
      _onLongPressStart(details);
    } else {
      if(isMicrophoneDenied){
        PermissionUtils.requestMicrophonePermission();
      } else {
        //跳转到设置页面提示
        _showMicrophoneConfirmationAlert(context);
      }
    }
  }

  //无法使用相机
  // 为正常拍摄,请前往设备中的【设置】> 【隐私】> 【相机】中允许无他相机使用
  _showMicrophoneConfirmationAlert(BuildContext context) {
    showPlatformDialog(
      context: context,
      builder: (_) => BasicDialogAlert(
        title: Text("无法使用麦克风"),
        content: Text("为正常录制声音,请前往设备中的【设置】> 【隐私】> 【麦克风】中允许${AppManager.getInstance().appName}使用"),
        actions: <Widget>[
          BasicDialogAction(
            title: Text("知道了"),
            onPressed: () {
              Navigator.pop(context);
            },
          ),
          BasicDialogAction(
            title: Text("去设置"),
            onPressed: () {
              // 跳转到系统设置页
              AppSettings.openAppSettings();
            },
          ),
        ],
      ),
    );
  }

  void _onLongPressStart(details){
    starty = details.globalPosition.dy;
    _timer = Timer.periodic(Duration(milliseconds: 1000), (t) {
      _count++;
      if (_count == _countTotal) {
        hideVoiceView();
      }
    });
    showVoiceView();
  }

  void _onLongPressEnd() async {
    bool isMicrophoneGranted = await Permission.microphone.isGranted;
    if(isMicrophoneGranted){
      hideVoiceView();
    }
  }

  void _onLongPressMoveUpdate(details) async {
    bool isMicrophoneGranted = await Permission.microphone.isGranted;
    if(isMicrophoneGranted){
      offset = details.globalPosition.dy;
      moveVoiceView();
    }
  }

  Widget getPressVoiceWidget() {
    return GestureDetector(
      onLongPressStart: (details) {
        _openMicrophonePermission(details);
      },

      onLongPressEnd: (details) {
        _onLongPressEnd();
      },

      onLongPressMoveUpdate: (details) {
        _onLongPressMoveUpdate(details);
      },

      child: Container(
        alignment: Alignment.bottomCenter,
        padding: EdgeInsets.only(top: 10.0, bottom: 10.0),
        decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5.0),),color: Colors.black12),
        width: double.infinity,
        child: Text("按住 说话"),
      ),
    );
  }

  ///显示录音悬浮布局
  buildOverLayView(BuildContext context) {
    if (overlayEntry == null) {
      overlayEntry = new OverlayEntry(builder: (content) {
        return CustomOverlay(
          icon: Column(
            children: <Widget>[
              Container(
                margin: const EdgeInsets.only(top: 10),
                child: _countTotal - _count < 11
                    ? Center(
                  child: Padding(
                    padding: const EdgeInsets.only(bottom: 15.0),
                    child: Text(
                      (_countTotal - _count).toString(),
                      style: TextStyle(
                        fontSize: 70.0,
                        color: Colors.white,
                      ),
                    ),
                  ),
                )
                    : new Image.asset(
                  voiceIco,
                  width: 100,
                  height: 100,
                  //package: 'flutter_plugin_record',
                ),
              ),
              Container(
//                      padding: const EdgeInsets.only(right: 20, left: 20, top: 0),
                child: Text(
                  toastShow,
                  style: TextStyle(
                    fontStyle: FontStyle.normal,
                    color: Colors.white,
                    fontSize: 14,
                  ),
                ),
              )
            ],
          ),
        );
      });
      Overlay.of(context)!.insert(overlayEntry!);
    }
  }

  showVoiceView() {
    setState(() {
      textShow = "松开结束";
      voiceState = false;
    });

    ///显示录音悬浮布局
    buildOverLayView(context);
    hidePlayVoiceList();
    start();
    playAnimation();
  }

  //隐藏播放列表,停止播放录音
  hidePlayVoiceList(){
    widget.stopPlayVoiceCallback();
  }

  hideVoiceView() async {
    if (_timer!.isActive) {
      if (_count < 1) {
        CommonToast.showView(
            context: context,
            msg: '说话时间太短',
            icon: Text(
              '!',
              style: TextStyle(fontSize: 80, color: Colors.white),
            ));
        isUp = true;
      }
      _timer?.cancel();
      _soundSecond = _count;
      _count = 0;
    }

    setState(() {
      textShow = "按住 说话";
      voiceState = true;
    });

    stop();
    if (overlayEntry != null) {
      overlayEntry?.remove();
      overlayEntry = null;
    }

    if (isUp) {
      LogUtils.d("取消发送");
      File file = File(fileName);
      if(fileName != null && file.existsSync()){
        file.deleteSync();
      }
    } else {
      LogUtils.d("进行发送 ${_soundSecond}");
      await _audioRecorder.stop();
      String messageId = UUID.getUUID();
      widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VOICE, fileName, "", _soundSecond, messageId);
      widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_VOICE, fileName, _soundSecond, messageId);
    }
  }

  moveVoiceView() {
    setState(() {
      isUp = starty - offset > 100 ? true : false;
      if (isUp) {
        textShow = "松开手指,取消发送";
        toastShow = textShow;
      } else {
        textShow = "松开结束";
        toastShow = "手指上滑,取消发送";
      }
    });
  }

  String fileName = "";
  ///开始语音录制的方法
  void start() async {
    try {
      fileName = await FileUtils.getBaseFile("voice_${DateUtil.getNowDateMs()}.m4a");
      File file = File(fileName);
      if(!file.existsSync()){
        file.createSync();
      }

      if (await _audioRecorder.hasPermission()) {
        final isSupported = await _audioRecorder.isEncoderSupported(
          AudioEncoder.aacLc,
        );
        LogUtils.d("录音文件:${fileName}");
        await _audioRecorder.start(path: fileName, encoder: AudioEncoder.aacLc, bitRate: 262144, samplingRate: 48000);
      }
    } catch (e) {
      LogUtils.d("${e}");
    }
  }

  Timer? periodicTimer;

  //播放录音动画
  void playAnimation(){
    int i =0;
    periodicTimer = Timer.periodic(
      const Duration(milliseconds: 150),(timer) {
      i++;
      if (i == 1) {
        voiceIco = CommonUtils.getChatUrlPng("voice_volume_2");
      } else if (i == 2) {
        voiceIco = CommonUtils.getChatUrlPng("voice_volume_3");
      } else if (i == 3) {
        voiceIco = CommonUtils.getChatUrlPng("voice_volume_4");
      } else if (i == 4) {
        voiceIco = CommonUtils.getChatUrlPng("voice_volume_5");
      } else if (i == 5) {
        voiceIco = CommonUtils.getChatUrlPng("voice_volume_6");
      } else if (i == 6) {
        voiceIco = CommonUtils.getChatUrlPng("voice_volume_7");
      } else {
        i = 0;
      }
      if (overlayEntry != null) {
        overlayEntry!.markNeedsBuild();
      }

    },
    );

  }

  ///停止语音录制的方法
  void stop() async{
    await _audioRecorder.stop();
    periodicTimer?.cancel();
  }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30841.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML学生个人网站作业设计——HTML+CSS+JavaScript优分期大学生分期购物商城(7页)

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

Vue3【Composition API 的优势、新的组件(Fragment、Teleport、Suspense)、全局API的转移】

文章目录四、Composition API 的优势1.Options API 存在的问题2.Composition API 的优势五、新的组件1.Fragment2.Teleport3.Suspense六、其他1.全局API的转移2.其他改变四、Composition API 的优势 1.Options API 存在的问题 使用传统OptionsAPI中&#xff0c;新增或者修改一…

web前端网页设计与制作:HTML+CSS旅游网页设计——桂林旅游(3页) web前端旅游风景网页设计与制作 div静态网页设计

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

Flink时间窗口语义

Flink时间窗口语义WarterMarker特点自定义水位线策略周期性水位线生成器&#xff08;Periodic Generator&#xff09;断点式水位线生成器&#xff08;Punctuared Generator&#xff09;在自定义数据源中发送水位线水位线的传递水位线总结窗口&#xff08;Window&#xff09;窗口…

spark-core-源码、Worker启动、sparksubmit提交、Driver启动

sparksubmit源码解析 在提交我们写好的jar包时候&#xff0c;用到submit命令&#xff0c;他的源码解析流程如上图 位于deploy里的SparkSubmit里面&#xff0c;根据main方法一点点run进去&#xff0c;分配我们传的参数&#xff0c;尤其是 val (childArgs, childClasspath, spa…

电脑屏幕亮度怎么调?四种自由调节亮度方式

现在的电脑是很方便的&#xff0c;可以说我们日常的生活中离不开电脑了。但是电脑屏幕亮度怎么调呢&#xff1f;这是一个问题&#xff0c;我们应该如何去调节&#xff1f;其实调节的方式有很多&#xff0c;我们根据自己的需求进行调节即可。接下来&#xff0c;为大家介绍四种电…

今日睡眠质量记录82分

昨天回去得比较晚了&#xff0c;不过睡眠质量还不错的&#xff0c;睡得比较沉&#xff0c;睡眠质量记录还不错的&#xff0c;大概有82分左右了。

计算机的发展史

文章目录计算机的发展史一&#xff0c;算盘二&#xff0c;纳皮尔骨筹三&#xff0c;帕斯卡林四&#xff0c;莱布尼茨步进计算器五&#xff0c;差分机六&#xff0c;分析机七&#xff0c;制表机八&#xff0c;微分分析机九&#xff0c;Mark I 计算机十&#xff0c;五代计算机计算…

如何使用CSS创建高级动画,这个函数必须掌握

微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势&#xff0c;学习途径等等。 本文 GitHub https://github.com/qq449245884/xiaozhi 已收录&#xff0c;有一线大厂面试完整考点、资料以及我的系列文章。 我们每天都在网上摸鱼&#xff0c;作为前端开发人员&#xff0…

SD-WAN行业经常说CPE、uCPE、vCPE是什么意思,各自有什么区别和应用场景?

我们先来看下传统的硬件终端CPE到底是什么。 CPE&#xff08;CustomerPremisesEquipment&#xff0c;客户端设备&#xff09;是指位于用户端的网络终端设备&#xff0c;用于与运营商对接服务&#xff0c;是网络解决方案的重要组成部分&#xff0c;通常是路由器、防火墙或者路由…

Linux 性能分析命令详解

top 命令 top -1 按数字1可以看到 多个核&#xff0c;每个核的cpu的使用情况 监控工具\平台来收集cpu的使用率 是所有cpu数量的一个总体的使用率 top -E 按大写字母E可以看到不同单位的内存使用情况 KB MB GB TB mem&#xff1a; buffer cache swap buffer是磁盘虚拟出来…

99-104-Hadoop-MapReduce-排序:

99-Hadoop-MapReduce-排序&#xff1a; WritableComparable 排序 排序是MapReduce框架中最重要的操作之一。 MapTask和ReduceTask均会对数据按 照key进行排序。该操作属于 Hadoop的默认行为。任何应用程序中的数据均会被排序&#xff0c;而不管逻辑上是否需要。 默认排序是按…

PID控制原理基本介绍(图解)

PID控制原理基本介绍(图解) 这里先以一个阶跃响应做图解说明: 如下图所示,目标值设定为单位1,随着我们逐渐增大比例系数Kp,可以看到系统相应速度逐渐加快,但是始终存在稳态误差, 如下图所示,引入积分环节后,随着积分控制系数Ki逐渐加大,误差逐渐减小,并最终达…

SSM毕设项目 - 基于SSM的婚纱摄影网站(含源码+论文)

文章目录1 项目简介2 实现效果2.1 界面展示3 设计方案3.1 概述3.2 系统流程3.2.1 系统开发流程3.3 系统结构设计4 项目获取1 项目简介 Hi&#xff0c;各位同学好呀&#xff0c;这里是M学姐&#xff01; 今天向大家分享一个今年(2022)最新完成的毕业设计项目作品&#xff0c;【…

OPNET Modeler 的安装及其相关配置

文章目录前言一、安装包下载1、OPNET Modeler 安装包下载2、Visual Studio 2010 安装包下载二、配置C/C环境变量三、OPNET Modeler 的安装1、安装 modeler_145A_PL1_7116_win2、安装 modeler_docs_28-Jan-2008_win3、安装 models_145A_PL1_27Feb08_win4、安装 OPNET.Modeler.14…

PLC中ST编程的基础知识

程序组织单元&#xff0c;简称POU&#xff1b;完整的PLC程序就是由无数个POU组成的&#xff1b; FB&#xff1a;功能块&#xff0c;也称函数块&#xff0c;执行时产生一个或多个值&#xff0c;一个功能块可以创建多个实例&#xff1b; VAR_IN&#xff1a;输入变量&#xff0c…

java绘制标注框,注册字体

文章目录场景思路步骤1.注册字体2.绘制标注框保存文本3.效果如下:场景 有个项目需要在java的后台将AI算法的标识框&#xff0c;置信度值&#xff0c;画到上传的报警图片上。以前都在算法部分画&#xff0c;但是效率有点低&#xff0c;所以传过来原始的图片&#xff08;也会用来…

第三章:JVM监控及诊断工具-GUI篇

JVM监控及诊断工具-GUI篇 使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息&#xff0c;但它们存在下列局限: 无法获取方法级别的分析数据&#xff0c;如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。要求用户登录到…

手写一个react,看透react运行机制

适合人群 本文适合0.5~3年的react开发人员的进阶。 讲讲废话&#xff1a; react的源码&#xff0c;的确是比vue的难度要深一些&#xff0c;本文也是针对初中级&#xff0c;本意让博友们了解整个react的执行过程。 写源码之前的必备知识点 JSX 首先我们需要了解什么是JSX。…

Flutter高仿微信-第19篇-支付-我的零钱

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 实现代码&#xff1a; /*** Author : wangning* Email : maoning20080809163.…