flutter 微信语音输入

Flutter  高仿微信 语音输入上划取消输入,网上的只有微信老版本的实现方式; 这边是最新的实现效果全网首发;先看效果:(只实现了页面事件效果,具体录音后面篇幅再实现

实现起来有几个麻烦的地方

  • 遮罩层的实现,刚开始用的不管是 普通的widget, 还是 showDialog 都会阻断 拖拽事件
  • 遮罩内容的更新问题

遮罩弹出层的方案选择:

刚开始的时候,观察微信发现 从“长按 说话” 按钮开始;有一个全局遮罩 然后上抬圆弧;开始监听 上下滑动的事件。应为需要全局遮罩:

      首先 想到的是 showDialog,但是显示 showDialog 会阻断“开始 说话” 按钮的拖拽事件(相当于打开了新的页面)。用过忽略事件(IgnorePointer AbsorbPointer)都不好使。还想从 GestureDetector底层实现 入手 都以失败告终

  然后勉强 使用 stack 实现 老版本的效果

折腾了好几天(业余时间),本来就要妥协了 后来想到了 OverlayEntry, 还是试了一下没想到 这个 OverlayEntry 显示的时候 原来 按钮的拖拽事件还是可以继续监听的!所以姿势就对了!

下面就是显示 三件套

void _showAudioRecord() {
  _hideAudioRecord();

  _overlayEntry = OverlayEntry(builder: (BuildContext context) {
    return ChatAudioMask(recordAudioState: _recordState);
  });
  _overlayState!.insert(_overlayEntry!);
}

void _updateAudioRecord() {
  final overlayEntry = this._overlayEntry;
  if (overlayEntry != null) {
    overlayEntry.markNeedsBuild();
  }
}

void _hideAudioRecord() {
  if (_overlayEntry != null) {
    _overlayEntry?.remove();
    _overlayEntry = null;

    if (_recordState.recording) {
      if (_recordState.recordingState == 1) {
        print("用户录音成功");
      } else {
        print("用户取消录音");
      }
    }
  }
}

响应上下滑动事件:

这个还是再 GestureDetector 中实现的,老三样没有新姿势挖掘

GestureDetector(
    behavior: HitTestBehavior.translucent,
    onVerticalDragStart: (DragStartDetails details) {
      print(
          '-------------------------->onVerticalDragStart');
      widget.providerChatContent.updateRecordAudioState(
        RecordAudioState(
            recording: true,
            recordingState: 1,
            noticeMessage: '松开 发送'),
      );
      _showAudioRecord();
    },
    onVerticalDragUpdate: (DragUpdateDetails details) {
      // print(
      //     '-------------------------->onVerticalDragUpdate:${details.delta}');
      // print(
      //     '-------------------------->onVerticalDragUpdate:${details.localPosition.dy}');
      if (details.localPosition.dy > -150) {
        widget.providerChatContent.updateRecordAudioState(
          RecordAudioState(
              recording: true,
              recordingState: 1,
              noticeMessage: '松开 发送'),
        );
      } else {
        widget.providerChatContent.updateRecordAudioState(
          RecordAudioState(
              recording: true,
              recordingState: -1,
              noticeMessage: '松开 取消'),
        );
      }
      _updateAudioRecord();
    },
    onVerticalDragEnd: (DragEndDetails details) {
      // print('-------------------------->onVerticalDragEnd');
      _hideAudioRecord();
      widget.providerChatContent.updateRecordAudioState(
        RecordAudioState(
            recording: false,
            recordingState: 1,
            noticeMessage: ''),
      );
    },
    onVerticalDragCancel: () {
      _hideAudioRecord();
      widget.providerChatContent.updateRecordAudioState(
        RecordAudioState(
            recording: false,
            recordingState: 1,
            noticeMessage: ''),
      );
    },
    child: Container(
      margin: EdgeInsets.symmetric(horizontal: 20.cale),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(7.cale),
      ),
      height: 80.cale,
      child: Center(
        child: Text(
          '按住 说话',
          style: AppTextStyle.textStyle_30_000000,
        ),
      ),
    ),

遮罩层的更新:

这个 OverlayEntryinsertOverlayState 中的至于更新 OverlayEntry 内容;不可能每次更新都 重新 insert  到OverlayState 的

 后来使用 :

markNeedsBuild
void _updateAudioRecord() {
  final overlayEntry = this._overlayEntry;
  if (overlayEntry != null) {
    overlayEntry.markNeedsBuild();
  }
}

 代码结构:

--- chatCommon

        -------chat_audio_mask.dart        聊天语音遮罩具体实现

        ------ chat_bottom.dart                 聊天底部输入框

        ------ chat_element_other.dart      聊天时别人信息的显示

        ------ chat_element_self.dart        聊天时自己信息的显示

        ------ chat_input_box.dart             聊天文本输入框封装

        ------ page_chat_group.dart         群聊

        ------ page_chat_person.dart       单聊

        ------ provider_chat_content.dart  聊天键盘显示 事件的传递 /键盘高度的处理

 chat_audio_mask

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:imflutter/const/app_colors.dart';
import 'package:imflutter/const/app_icon.dart';
import 'package:imflutter/const/app_textStyle.dart';
import 'package:imflutter/pages/chatCommon/provider_chat_content.dart';
import 'package:imflutter/wrap/extension/extension.dart';
import 'package:lottie/lottie.dart';

class ChatAudioMask extends StatefulWidget {
  final RecordAudioState recordAudioState;
  const ChatAudioMask({Key? key, required this.recordAudioState})
      : super(key: key);

  @override
  State<ChatAudioMask> createState() => _ChatAudioMaskState();
}

class _ChatAudioMaskState extends State<ChatAudioMask> {
  double _height = 0;
  bool _showAudioWave = false;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    Future.delayed(const Duration(milliseconds: 150)).then((value) {
      if (mounted) {
        setState(() {
          _height = 250.cale;
        });
      }
    });
    Future.delayed(const Duration(milliseconds: 350)).then((value) {
      if (mounted) {
        setState(() {
          _showAudioWave = true;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      child: Material(
        color: Colors.black.withOpacity(0.5),
        child: Column(
          children: [
            Expanded(
              child: Center(
                child: _showAudioWave
                    ? Container(
                        height: 200.cale,
                        width: 360.cale,
                        decoration: BoxDecoration(
                          color: widget.recordAudioState.recordingState == 1
                              ? AppColor.color8FED6D
                              : Colors.red,
                          borderRadius: BorderRadius.circular(30.cale),
                        ),
                        child: Center(
                          child: Container(
                            height: 200.cale,
                            width: 200.cale,
                            child: Lottie.asset(
                                'assets/lottieAnimation/record_auido.json'),
                          ),
                        ),
                      )
                    : Container(),
              ),
            ),
            Container(
              padding: EdgeInsets.symmetric(vertical: 50.cale),
              child: Text(
                widget.recordAudioState.noticeMessage,
                style: AppTextStyle.textStyle_30_F7F7F7,
              ),
            ),
            Center(
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 250),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(250.cale),
                    topRight: Radius.circular(250.cale),
                  ),
                ),
                // color: Colors.grey,
                width: double.infinity,
                height: _height,
                child: Icon(
                  AppIcon.audioSymbol,
                  color: AppColor.color7E7E7E,
                  size: 60.cale,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }
}

chat_bottom

import 'package:flutter/material.dart';
import 'package:imflutter/const/app_textStyle.dart';
import 'package:imflutter/pages/chatCommon/chat_audio_mask.dart';
import 'package:imflutter/pages/chatCommon/provider_chat_content.dart';
import 'package:imflutter/wrap/extension/extension.dart';
import '../../const/app_colors.dart';
import '../../const/app_icon.dart';
import '../../wrap/widget/app_widget.dart';
import 'chat_input_box.dart';

class ChatBottom extends StatefulWidget {
  final ProviderChatContent providerChatContent;
  const ChatBottom({Key? key, required this.providerChatContent})
      : super(key: key);

  @override
  State<ChatBottom> createState() => _ChatBottomState();
}

class _ChatBottomState extends State<ChatBottom> with WidgetsBindingObserver {
  // 0 语音 1 键盘 2 表情
  int _inputType = 0;
  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  late OverlayEntry? _overlayEntry = null;
  late OverlayState? _overlayState = null;

  bool get _keyboardShow => widget.providerChatContent.contentShow;
  RecordAudioState get _recordState =>
      widget.providerChatContent.recordAudioState;

  final List<Map> _listOption = [
    {'title': '相册', 'icon': 'assets/common/chat/ic_details_photo.webp'},
    {'title': '拍照', 'icon': 'assets/common/chat/ic_details_camera.webp'},
    // {'title': '视频通话', 'icon': 'assets/common/chat/ic_details_video.webp'},
    // {'title': '位置', 'icon': 'assets/common/chat/ic_details_localtion.webp'},
    // {'title': '红包', 'icon': 'assets/common/chat/ic_details_red.webp'},
    // {'title': '转账', 'icon': 'assets/common/chat/ic_details_transfer.webp'},
    // {'title': '语音输入', 'icon': 'assets/common/chat/ic_chat_voice.webp'},
    // {'title': '我的收藏', 'icon': 'assets/common/chat/ic_details_favorite.webp'},
  ];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // debugPaintPointersEnabled = true;
    WidgetsBinding.instance.addObserver(this);
    _overlayState = Overlay.of(context);
    _controller.addListener(() {
      setState(() {});
    });
    _focusNode.addListener(() {
      if (_focusNode.hasFocus) {
        widget.providerChatContent.updateContentShow(true);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print('ChatBottom------------------------build');
    return Container(
      padding: EdgeInsets.symmetric(vertical: 20.cale),
      decoration: BoxDecoration(
        color: AppColor.colorF7F7F7,
        border: Border(
          top: BorderSide(width: 1.cale, color: AppColor.colordddddd),
        ),
      ),
      // height: 110.cale,
      child: Column(
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              AnimatedSwitcher(
                duration: const Duration(milliseconds: 20),
                transitionBuilder: (Widget child, Animation<double> animation) {
                  return FadeTransition(
                    opacity: animation,
                    child: child,
                  );
                },
                child: _inputType == 0
                    ? AppWidget.inkWellEffectNone(
                        key: const ValueKey("AppIcon.audio"),
                        onTap: () {
                          _inputType = 1;
                          print('启动音频 _inputType = 1');
                          widget.providerChatContent.updateContentShow(false);
                        },
                        child: Padding(
                          padding:
                              EdgeInsets.only(left: 20.cale, bottom: 15.cale),
                          child: Icon(
                            AppIcon.audio,
                            size: 50.cale,
                            color: Colors.black,
                          ),
                        ),
                      )
                    : AppWidget.inkWellEffectNone(
                        key: const ValueKey("AppIcon.keyboard"),
                        onTap: () {
                          _inputType = 0;
                          widget.providerChatContent.updateContentShow(true);
                          print('启动键盘 _inputType = 0');
                          _focusNode.requestFocus();
                        },
                        child: Padding(
                          padding:
                              EdgeInsets.only(left: 20.cale, bottom: 15.cale),
                          child: Icon(
                            AppIcon.keyboard,
                            size: 50.cale,
                            color: Colors.black,
                          ),
                        ),
                      ),
              ),
              Expanded(
                child: _inputType == 0
                    ? Padding(
                        padding: EdgeInsets.symmetric(
                          horizontal: 20.cale,
                        ),
                        child: ChatInputBox(
                          style: AppTextStyle.textStyle_30_000000,
                          onEditingComplete: () {
                            print("onEditingComplete");
                          },
                          onSubmitted: (str) {
                            print("onSubmitted:$str");
                          },
                          controller: _controller,
                          focusNode: _focusNode,
                        ),
                      )
                    : GestureDetector(
                        behavior: HitTestBehavior.translucent,
                        onVerticalDragStart: (DragStartDetails details) {
                          print(
                              '-------------------------->onVerticalDragStart');
                          widget.providerChatContent.updateRecordAudioState(
                            RecordAudioState(
                                recording: true,
                                recordingState: 1,
                                noticeMessage: '松开 发送'),
                          );
                          _showAudioRecord();
                        },
                        onVerticalDragUpdate: (DragUpdateDetails details) {
                          // print(
                          //     '-------------------------->onVerticalDragUpdate:${details.delta}');
                          // print(
                          //     '-------------------------->onVerticalDragUpdate:${details.localPosition.dy}');
                          if (details.localPosition.dy > -150) {
                            widget.providerChatContent.updateRecordAudioState(
                              RecordAudioState(
                                  recording: true,
                                  recordingState: 1,
                                  noticeMessage: '松开 发送'),
                            );
                          } else {
                            widget.providerChatContent.updateRecordAudioState(
                              RecordAudioState(
                                  recording: true,
                                  recordingState: -1,
                                  noticeMessage: '松开 取消'),
                            );
                          }
                          _updateAudioRecord();
                        },
                        onVerticalDragEnd: (DragEndDetails details) {
                          // print('-------------------------->onVerticalDragEnd');
                          _hideAudioRecord();
                          widget.providerChatContent.updateRecordAudioState(
                            RecordAudioState(
                                recording: false,
                                recordingState: 1,
                                noticeMessage: ''),
                          );
                        },
                        onVerticalDragCancel: () {
                          _hideAudioRecord();
                          widget.providerChatContent.updateRecordAudioState(
                            RecordAudioState(
                                recording: false,
                                recordingState: 1,
                                noticeMessage: ''),
                          );
                        },
                        child: Container(
                          margin: EdgeInsets.symmetric(horizontal: 20.cale),
                          decoration: BoxDecoration(
                            color: Colors.white,
                            borderRadius: BorderRadius.circular(7.cale),
                          ),
                          height: 80.cale,
                          child: Center(
                            child: Text(
                              '按住 说话',
                              style: AppTextStyle.textStyle_30_000000,
                            ),
                          ),
                        ),
                      ),
              ),
              AppWidget.inkWellEffectNone(
                onTap: () {
                  print('添加表情符号');
                },
                child: Padding(
                  padding: EdgeInsets.only(bottom: 15.cale),
                  child: Icon(
                    AppIcon.faceHappy,
                    size: 50.cale,
                    color: Colors.black,
                  ),
                ),
              ),
              AnimatedSwitcher(
                duration: const Duration(milliseconds: 50),
                transitionBuilder: (Widget child, Animation<double> animation) {
                  return ScaleTransition(
                    scale: animation,
                    alignment: Alignment.centerRight,
                    child: FadeTransition(
                      opacity: animation,
                      child: child,
                    ),
                  );
                },
                child: _inputType == 0 && _controller.value.text.isNotEmpty
                    ? AppWidget.inkWellEffectNone(
                        key: const ValueKey('发送'),
                        onTap: () {
                          print('发送');
                          _controller.clear();
                        },
                        child: Container(
                          margin: EdgeInsets.only(
                              left: 20.cale, right: 24.cale, bottom: 10.cale),
                          padding: EdgeInsets.symmetric(
                            horizontal: 24.cale,
                            vertical: 10.cale,
                          ),
                          decoration: BoxDecoration(
                            color: AppColor.color05C160,
                            borderRadius: BorderRadius.circular(12.cale),
                          ),
                          child: Center(
                              child: Text(
                            '发送',
                            style: AppTextStyle.textStyle_30_FFFFFF,
                          )),
                        ),
                      )
                    : AppWidget.inkWellEffectNone(
                        key: const ValueKey('AppIcon.add'),
                        onTap: () {
                          print('添加附件 图片视频');
                          setState(() {
                            if (_focusNode.hasFocus) {
                              _focusNode.unfocus();
                            }
                            widget.providerChatContent.updateContentShow(true);
                          });
                        },
                        child: Padding(
                          padding: EdgeInsets.only(
                              left: 10.cale, right: 20.cale, bottom: 10.cale),
                          child: Icon(
                            AppIcon.add,
                            size: 58.cale,
                            color: Colors.black,
                          ),
                        ),
                      ),
              ),
            ],
          ),
          if (_keyboardShow)
            Container(
              width: double.infinity,
              padding: EdgeInsets.only(
                top: 20.cale,
              ),
              height:
                  widget.providerChatContent.keyboardHeight >= 0 ? 0 : 240.cale,
              child: Container(
                decoration: BoxDecoration(
                  border: Border(
                    top: BorderSide(width: 1.cale, color: AppColor.colordddddd),
                  ),
                ),
                child: Wrap(
                  runAlignment: WrapAlignment.center,
                  alignment: WrapAlignment.center,
                  //crossAxisAlignment: WrapCrossAlignment.center,
                  spacing: 150.cale,
                  runSpacing: 80.cale,
                  children: _listOption
                      .asMap()
                      .map(
                        (key, value) => MapEntry(
                          key,
                          SizedBox(
                            width: 100.cale,
                            height: 150.cale,
                            child: Column(
                              children: [
                                Container(
                                  width: 100.cale,
                                  height: 100.cale,
                                  decoration: BoxDecoration(
                                    color: Colors.white,
                                    borderRadius:
                                        BorderRadius.circular(25.cale),
                                  ),
                                  child: Image.asset(
                                    value['icon'],
                                    width: 50.cale,
                                    height: 50.cale,
                                  ),
                                ),
                                Padding(
                                  padding: EdgeInsets.only(top: 16.cale),
                                  child: Text(
                                    value['title'],
                                    style: AppTextStyle.textStyle_20_656565,
                                  ),
                                )
                              ],
                            ),
                          ),
                        ),
                      )
                      .values
                      .toList(),
                ),
              ),
            )
        ],
      ),
    );
  }

  ///应用尺寸改变时回调,例如旋转 键盘
  @override
  void didChangeMetrics() {
    // TODO: implement didChangeMetrics
    super.didChangeMetrics();
    if (mounted) {
      // 键盘高度
      final double viewInsetsBottom = EdgeInsets.fromWindowPadding(
              WidgetsBinding.instance.window.viewInsets,
              WidgetsBinding.instance.window.devicePixelRatio)
          .bottom;
      widget.providerChatContent.updateKeyboardHeight(viewInsetsBottom);
      // if (viewInsetsBottom > 0) {
      //   widget.providerChatContent.updateKeyboardHeight(viewInsetsBottom);
      // }
    }
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _focusNode.dispose();
    _controller.dispose();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  void _showAudioRecord() {
    _hideAudioRecord();

    _overlayEntry = OverlayEntry(builder: (BuildContext context) {
      return ChatAudioMask(recordAudioState: _recordState);
    });
    _overlayState!.insert(_overlayEntry!);
  }

  void _updateAudioRecord() {
    final overlayEntry = this._overlayEntry;
    if (overlayEntry != null) {
      overlayEntry.markNeedsBuild();
    }
  }

  void _hideAudioRecord() {
    if (_overlayEntry != null) {
      _overlayEntry?.remove();
      _overlayEntry = null;

      if (_recordState.recording) {
        if (_recordState.recordingState == 1) {
          print("用户录音成功");
        } else {
          print("用户取消录音");
        }
      }
    }
  }
}

chat_element_other

import 'package:flutter/material.dart';
import 'package:imflutter/const/app_colors.dart';
import 'package:imflutter/wrap/extension/extension.dart';
import 'package:imflutter/wrap/widget/app_widget.dart';

class ChatElementOther extends StatefulWidget {
  /// 用户信息
  final Map userInfo;

  /// 消息
  final Map chatMessage;
  const ChatElementOther(
      {Key? key, required this.userInfo, required this.chatMessage})
      : super(key: key);

  @override
  State<ChatElementOther> createState() => _ChatElementOtherState();
}

class _ChatElementOtherState extends State<ChatElementOther> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: 24.cale),
      child: Column(
        children: [
          Padding(
            padding: EdgeInsets.only(bottom: 40.cale),
            child: Text('11:25'),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: EdgeInsets.only(left: 24.cale),
                child: AppWidget.inkWellEffectNone(
                  onTap: () {},
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(7.cale),
                    child: AppWidget.cachedImage(widget.userInfo['icon'],
                        width: 75.cale, height: 75.cale),
                  ),
                ),
              ),
              _chatContent(),
            ],
          )
        ],
      ),
    );
  }

  Widget _chatContent() {
    /// 1 文本
    /// 2 图片
    /// 3 语音
    /// 4 视频
    /// 5 提示消息
    /// 6 提示消息
    switch (widget.chatMessage['type']) {
      case 1:
        return _chatType1();
        break;
      case 2:
        return _chatType2();
        break;
      case 3:
        return _chatType3();
        break;
      case 4:
        return _chatType4();
        break;
      case 5:
        return _chatType5();
        break;
      case 6:
        return _chatType6();
        break;
      default:
        return Container();
        break;
    }
  }

  Widget _chatType1() {
    return Stack(
      children: [
        Container(
          margin: EdgeInsets.only(left: 25.cale),
          constraints: BoxConstraints(maxWidth: 500.cale),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(12.cale),
          ),
          padding: EdgeInsets.symmetric(
            vertical: 18.cale,
            horizontal: 20.cale,
          ),
          child: Text(
            widget.chatMessage['content_1'],
            softWrap: true,
          ),
        ),
        Positioned(
          top: 25.cale,
          left: 10.cale,
          child: CustomPaint(
            size: Size(20.cale, 30.cale),
            painter: TrianglePainter(),
          ),
        ),
      ],
    );
  }

  Widget _chatType2() {
    return Container(
      constraints: BoxConstraints(
        maxWidth: 320.cale,
        maxHeight: 300.cale,
        minHeight: 120.cale,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(7.cale),
        border: Border.all(width: 1.cale / 2, color: AppColor.color636363),
      ),
      margin: EdgeInsets.only(left: 20.cale),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(7.cale),
        child: AppWidget.cachedImage(
          widget.chatMessage['content_2']['picture_mini']['url'],
        ),
      ),
    );
  }

  Widget _chatType3() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是语音"),
    );
  }

  Widget _chatType4() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是视频"),
    );
  }

  Widget _chatType5() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是提示5"),
    );
  }

  Widget _chatType6() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是提示6"),
    );
  }
}

class TrianglePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()..color = Colors.white;
    Path path = Path();
    path.moveTo(0, size.height / 2);
    path.lineTo(size.width, 0);
    path.lineTo(size.width, size.height);
    path.close();
    canvas.drawPath(path, paint);
    return;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return false;
  }
}

chat_element_self

import 'package:flutter/material.dart';
import 'package:imflutter/const/app_colors.dart';
import 'package:imflutter/wrap/extension/extension.dart';
import 'package:imflutter/wrap/widget/app_widget.dart';

class ChatElementSelf extends StatefulWidget {
  /// 用户信息
  final Map userInfo;

  /// 消息
  final Map chatMessage;
  const ChatElementSelf(
      {Key? key, required this.userInfo, required this.chatMessage})
      : super(key: key);

  @override
  State<ChatElementSelf> createState() => _ChatElementSelfState();
}

class _ChatElementSelfState extends State<ChatElementSelf> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: 24.cale),
      child: Column(
        children: [
          Padding(
            padding: EdgeInsets.only(bottom: 40.cale),
            child: Text('11:25'),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _chatContent(),
              Padding(
                padding: EdgeInsets.only(right: 24.cale),
                child: AppWidget.inkWellEffectNone(
                  onTap: () {},
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(7.cale),
                    child: AppWidget.cachedImage(widget.userInfo['icon'],
                        width: 75.cale, height: 75.cale),
                  ),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }

  Widget _chatContent() {
    /// 1 文本
    /// 2 图片
    /// 3 语音
    /// 4 视频
    /// 5 提示消息
    /// 6 提示消息
    switch (widget.chatMessage['type']) {
      case 1:
        return _chatType1();
        break;
      case 2:
        return _chatType2();
        break;
      case 3:
        return _chatType3();
        break;
      case 4:
        return _chatType4();
        break;
      case 5:
        return _chatType5();
        break;
      case 6:
        return _chatType6();
        break;
      default:
        return Container();
        break;
    }
  }

  Widget _chatType1() {
    return Stack(
      children: [
        Container(
          margin: EdgeInsets.only(right: 25.cale),
          constraints: BoxConstraints(maxWidth: 500.cale),
          decoration: BoxDecoration(
            color: AppColor.color94ED6D,
            borderRadius: BorderRadius.circular(12.cale),
          ),
          padding: EdgeInsets.symmetric(
            vertical: 18.cale,
            horizontal: 20.cale,
          ),
          child: Text(
            widget.chatMessage['content_1'],
            softWrap: true,
          ),
        ),
        Positioned(
          top: 25.cale,
          right: 10.cale,
          child: CustomPaint(
            size: Size(20.cale, 30.cale),
            painter: TrianglePainter(),
          ),
        ),
      ],
    );
  }

  Widget _chatType2() {
    return Container(
      constraints: BoxConstraints(
        maxWidth: 320.cale,
        maxHeight: 300.cale,
        minHeight: 120.cale,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(7.cale),
        border: Border.all(width: 1.cale / 2, color: AppColor.color636363),
      ),
      margin: EdgeInsets.only(right: 20.cale),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(7.cale),
        child: AppWidget.cachedImage(
          widget.chatMessage['content_2']['picture_mini']['url'],
        ),
      ),
    );
  }

  Widget _chatType3() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是语音"),
    );
  }

  Widget _chatType4() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是视频"),
    );
  }

  Widget _chatType5() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是提示5"),
    );
  }

  Widget _chatType6() {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(18.cale),
      child: Text("这是提示6"),
    );
  }
}

class TrianglePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()..color = AppColor.color94ED6D;
    Path path = Path();
    // path.moveTo(0, 0);
    // path.lineTo(0, size.height);
    // path.lineTo(size.width, size.height);
    // path.lineTo(size.width, 0);
    path.moveTo(0, 0);
    path.lineTo(0, size.height);
    path.lineTo(size.width, size.height / 2);
    path.close();
    canvas.drawPath(path, paint);
    return;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return false;
  }
}

chat_input_box

import 'package:flutter/material.dart';
import 'package:imflutter/const/app_colors.dart';
import 'package:imflutter/wrap/extension/extension.dart';

import '../../const/app_textStyle.dart';

class ChatInputBox extends StatelessWidget {
  final String? hintText;
  final int? maxLength;
  final VoidCallback? onEditingComplete;
  final ValueChanged<String>? onSubmitted;
  final EdgeInsetsGeometry? contentPadding;
  final TextEditingController? controller;
  final String? errorText;
  final Widget? prefixIcon;
  final TextInputType? keyboardType;
  final BoxConstraints? prefixIconConstraints;
  final BoxDecoration? decoration;
  final TextStyle? style;
  final TextStyle? hintStyle;
  final FocusNode? focusNode;
  const ChatInputBox({
    Key? key,
    this.maxLength = 20,
    this.controller,
    this.errorText,
    this.prefixIcon,
    this.prefixIconConstraints,
    this.onEditingComplete,
    this.onSubmitted,
    this.contentPadding = EdgeInsets.zero,
    this.decoration,
    this.keyboardType,
    this.style,
    this.hintStyle,
    this.focusNode,
    this.hintText,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      // height: 75.cale,
      // margin: EdgeInsets.all(5.cale),
      constraints: BoxConstraints(
        minHeight: 75.cale,
        maxHeight: 350.cale,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(7.cale),
        color: Colors.white,
      ),
      child: TextField(
        // maxLength: maxLength,
        focusNode: focusNode,
        maxLines: null,
        maxLength: 200,
        cursorColor: AppColor.color3BAB71,
        controller: controller,
        textAlignVertical: TextAlignVertical.center,
        keyboardType: keyboardType,
        onEditingComplete: onEditingComplete,
        onSubmitted: onSubmitted,
        style: style ?? AppTextStyle.textStyle_28_333333,
        // inputFormatters: inputFormatters,
        decoration: InputDecoration(
          focusedBorder: const OutlineInputBorder(
              borderSide: BorderSide(width: 0, color: Colors.transparent)),
          disabledBorder: const OutlineInputBorder(
              borderSide: BorderSide(width: 0, color: Colors.transparent)),
          enabledBorder: const OutlineInputBorder(
              borderSide: BorderSide(width: 0, color: Colors.transparent)),
          border: OutlineInputBorder(
            borderSide: BorderSide.none,
            borderRadius: BorderRadius.circular(7.cale),
            //borderSide: BorderSide(width: 0, color: Colors.transparent),
            // borderSide: BorderSide(width: 0, color: Colors.transparent),
          ),
          hintText: hintText,
          prefixIcon: prefixIcon,
          prefixIconConstraints: prefixIconConstraints,
          hintStyle: hintStyle ?? AppTextStyle.textStyle_28_AAAAAA,
          counterText: '', //取消文字计数器
          // border: InputBorder.none,
          isDense: true,
          errorText: errorText,
          contentPadding: EdgeInsets.symmetric(
            horizontal: 16.cale,
            vertical: 20.cale,
          ),
        ),
        // contentPadding:
        //     EdgeInsets.only(left: 16.cale, right: 16.cale, top: 20.cale),

        // errorText: "输入错误",
      ),
    );
  }
}

page_chat_group

import 'package:flutter/cupertino.dart';

class PageChatGroup extends StatefulWidget {
  const PageChatGroup({Key? key}) : super(key: key);

  @override
  State<PageChatGroup> createState() => _PageChatGroupState();
}

class _PageChatGroupState extends State<PageChatGroup> {
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

page_chat_person

import 'package:flutter/material.dart';
import 'package:imflutter/wrap/extension/extension.dart';
import 'package:imflutter/wrap/navigator/app_navigator.dart';
import 'package:imflutter/pages/chatCommon/chat_element_other.dart';
import 'package:provider/provider.dart';

import '../../const/app_colors.dart';
import '../../const/app_icon.dart';
import '../../const/app_textStyle.dart';
import '../../wrap/widget/app_widget.dart';
import 'provider_chat_content.dart';
import 'chat_bottom.dart';
import 'chat_element_self.dart';

class PageChatPerson extends StatefulWidget {
  final Map userInfoOther;
  const PageChatPerson({Key? key, required this.userInfoOther})
      : super(key: key);

  @override
  State<PageChatPerson> createState() => _PageChatPersonState();
}

class _PageChatPersonState extends State<PageChatPerson> {
  /// 1 文本
  /// 2 图片
  /// 3 语音
  /// 4 视频
  /// 5 提示消息
  /// 6 提示消息
  final List<Map> _arrayChatMessage = [];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // for (int i = 13; i >= 0; i--) {
    //   //_arrayChatMessage.addAll(_cache);
    //   print("------------------i % 6 + 1:${i % 6 + 1}");
    //   _arrayChatMessage.add({
    //     'id': i,
    //     'type': i % 6 + 1,
    //     'content_1': '你吃饭了吗2${i}-${i % 6}',
    //     'content_2': {
    //       'picture_mini': {
    //         'url':
    //             'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
    //         'width': 450,
    //         'height': 200
    //       },
    //       'picture':
    //           'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
    //     },
    //     'content_3':
    //         'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
    //     'content_4': '',
    //     'content_5': '',
    //     'content_6': '',
    //     'times': 1000000 + i
    //   });
    // }

    _arrayChatMessage.add({
      'id': 99,
      'type': 1,
      'content_1': '你吃饭了吗? ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 1,
      'content_1': '我吃过了你呢? ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://lmg.jj20.com/up/allimg/1114/033021091503/210330091503-6-1200.jpg',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://lmg.jj20.com/up/allimg/1114/033021091503/210330091503-6-1200.jpg',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F16%2F20210716215819_76234.thumb.1000_0.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1679722694&t=6ddea52a86e658f1a73f6e0e3865bad6',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url':
              'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F16%2F20210716215819_76234.thumb.1000_0.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1679722694&t=6ddea52a86e658f1a73f6e0e3865bad6',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url': 'https://photo.tuchong.com/4274381/f/1139873881.jpg',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });
    _arrayChatMessage.add({
      'id': 100,
      'type': 2,
      'content_1': ' ',
      'content_2': {
        'picture_mini': {
          'url': 'https://photo.tuchong.com/4274381/f/11398738812.jpg',
          'width': 800,
          'height': 500
        },
        'picture':
            'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      },
      'content_3':
          'https://user-info-1302720239.cos.ap-nanjing.myqcloud.com/userIcon/user_icon_000100.jpg',
      'content_4': '',
      'content_5': '',
      'content_6': '',
      'times': 1000000 + 9
    });

    //print('--datum:${widget.userInfoOther}');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // resizeToAvoidBottomInset: false,
      appBar: AppBar(
        backgroundColor: AppColor.colorEDEDED,
        shadowColor: AppColor.colordddddd,
        elevation: 1.cale,
        leading: AppWidget.iconBack(() {
          AppNavigator().navigateBack();
        }),
        centerTitle: true,
        title: Text(
          widget.userInfoOther['name'],
          style: AppTextStyle.textStyle_34_000000,
        ),
        actions: [
          Padding(
            padding: EdgeInsets.only(right: 24.cale),
            child: AppWidget.inkWellEffectNone(
              onTap: () {},
              child: Icon(
                AppIcon.dot3,
                size: 46.cale,
                color: Colors.black,
              ),
            ),
          )
        ],
      ),
      body: ChangeNotifierProvider<ProviderChatContent>(
        create: (BuildContext context) => ProviderChatContent(),
        child: Consumer(builder: (BuildContext context,
            ProviderChatContent providerChatContent, child) {
          return Builder(
            builder: (BuildContext context) {
              return Column(
                children: [
                  Expanded(
                    child: AppWidget.inkWellEffectNone(
                      onTap: () {
                        FocusScope.of(context).requestFocus(
                          FocusNode(),
                        );
                        context
                            .read<ProviderChatContent>()
                            .updateContentShow(false);
                      },
                      child: ListView.builder(
                        padding: EdgeInsets.symmetric(vertical: 30.cale),
                        physics: const BouncingScrollPhysics(
                          parent: AlwaysScrollableScrollPhysics(),
                        ),
                        shrinkWrap: false,
                        reverse: _arrayChatMessage.length > 7,
                        itemCount: _arrayChatMessage.length,
                        // itemExtent: 188.cale,
                        itemBuilder: (BuildContext context, int index) {
                          if (index % 2 != 0) {
                            return ChatElementSelf(
                              userInfo: widget.userInfoOther,
                              chatMessage: _arrayChatMessage[index],
                            );
                          } else {
                            return ChatElementOther(
                                userInfo: widget.userInfoOther,
                                chatMessage: _arrayChatMessage[index]);
                          }
                        },
                      ),
                    ),
                  ),
                  ChatBottom(
                    providerChatContent: providerChatContent,
                  ),
                ],
              );
            },
          );
        }),
      ),
    );
  }

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

provider_chat_content

import 'package:flutter/cupertino.dart';
import 'package:imflutter/wrap/extension/extension.dart';

///用于 软键盘区/发送附件  域显示控制
class ProviderChatContent extends ChangeNotifier {
  bool _contentShow = false;
  double _keyboardHeight = 200;

  RecordAudioState _recordAudioState = RecordAudioState(
    recording: false,
    recordingState: -1,
    noticeMessage: '',
  );

  /// 是否显示 附件区域
  bool get contentShow => _contentShow;

  /// 键盘高度
  double get keyboardHeight => _keyboardHeight - 20.cale;

  RecordAudioState get recordAudioState => _recordAudioState;

  ///更新区域 展示
  void updateContentShow(bool isShow) {
    _contentShow = isShow;
    notifyListeners();
  }

  void updateKeyboardHeight(double height) {
    _keyboardHeight = height;
    notifyListeners();
  }

  void updateRecordAudioState(RecordAudioState state) {
    _recordAudioState = state;
    notifyListeners();
  }
}

class RecordAudioState {
  final bool recording;
  final int recordingState;
  final String noticeMessage;
  RecordAudioState(
      {required this.recording,
      required this.recordingState,
      required this.noticeMessage});
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nicepainkiller

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值