flutter聊天界面-聊天气泡长按弹出复制、删除按钮菜单

flutter聊天界面-聊天气泡长按弹出复制、删除按钮菜单
在之前实现了flutter聊天界面的富文本展示内容,这里记录一下当长按聊天气泡的时候弹出复制、删除等菜单功能

一、查看效果

当长按聊天气泡的时候弹出复制、删除等菜单,可新增更多按钮

在这里插入图片描述

二、代码实现

实现箭头效果,这里实现自定义的CustomPainter。flutter提供一块2D画布Canvas,Canvas内部封装了一些基本绘制的API,开发者可以通过Canvas绘制各种自定义图形。在Flutter中,提供了一个CustomPaint 组件,它可以结合画笔CustomPainter来实现自定义图形绘制。

绘制箭头效果代码

class ChatBubbleMenuShape extends CustomPainter {
    
    
  final Color bgColor;
  final double arrowSize;

  ChatBubbleMenuShape(this.bgColor, this.arrowSize);

  
  void paint(Canvas canvas, Size size) {
    
    
    var paint = Paint()..color = bgColor;

    var path = Path();
    path.lineTo(-arrowSize, 0);
    path.lineTo(0, arrowSize);
    path.lineTo(arrowSize, 0);

    canvas.drawPath(path, paint);
  }

  
  bool shouldRepaint(CustomPainter oldDelegate) {
    
    
    return false;
  }
}

// 长按气泡菜单的容器,展示具体的菜单容器

// 长按气泡菜单的容器
class ChatBubbleMenuContainer extends StatefulWidget {
    
    
  const ChatBubbleMenuContainer({
    
    
    Key? key,
    required this.chatMessage,
    required this.bubbleOffset,
    required this.bubbleSize,
    required this.onBubbleMenuButtonPressed,
  }) : super(key: key);

  final CommonChatMessage chatMessage;
  final Offset bubbleOffset;
  final Size bubbleSize;
  final Function(int index) onBubbleMenuButtonPressed;

  
  State<ChatBubbleMenuContainer> createState() =>
      _ChatBubbleMenuContainerState();
}

class _ChatBubbleMenuContainerState extends State<ChatBubbleMenuContainer> {
    
    
  
  Widget build(BuildContext context) {
    
    
    double itemWidth = 60;
    double itemHeight = 40;

    double menuWidth = itemWidth * 2;
    double menuHeight = itemHeight * 2;

    double dx =
        widget.bubbleOffset.dx + (widget.bubbleSize.width - menuWidth) / 2.0;
    double dy = widget.bubbleOffset.dy;

    print("widget.bubbleOffset:${
      
      widget.bubbleOffset}");

    LoggerManager().debug("chatBubbleFrame offset:${
      
      widget.bubbleOffset},"
        "size:${
      
      widget.bubbleSize}");

    double arrowSize = 10.0;

    return Stack(
      children: [
        Positioned(
          left: dx - arrowSize / 2.0,
          top: dy - menuHeight / 2.0,
          child: buildMenu(
            context,
            Size(itemWidth, itemHeight),
          ),
        ),
        Positioned(
          left: dx + menuWidth / 2 + arrowSize / 2.0,
          top: dy - menuHeight / 2.0 + itemHeight + arrowSize - 2.0,
          child: CustomPaint(
            painter:
                ChatBubbleMenuShape(ColorUtil.hexColor(0x454545), arrowSize),
          ),
        ),
      ],
    );
  }

  Widget buildMenu(BuildContext context, Size itemSize) {
    
    
    return Container(
      padding: const EdgeInsets.all(5.0),
      decoration: BoxDecoration(
        color: ColorUtil.hexColor(0x454545),
        borderRadius: const BorderRadius.only(
          topRight: Radius.circular(3),
          topLeft: Radius.circular(3),
          bottomLeft: Radius.circular(3),
          bottomRight: Radius.circular(3),
        ),
      ),
      child: Wrap(
        spacing: 8.0, // 主轴(水平)方向间距
        runSpacing: 4.0, // 纵轴(垂直)方向间距
        alignment: WrapAlignment.center, //沿主轴方向居中
        children: [
          ChatBubbleMenuButton(
            width: itemSize.width,
            height: itemSize.height,
            icon: "file://ic_post_unlike.png",
            name: "复制",
            onBubbleMenuButtonPressed: () {
    
    
              widget.onBubbleMenuButtonPressed(0);
            },
          ),
          ChatBubbleMenuButton(
            width: itemSize.width,
            height: itemSize.height,
            icon: "file://ic_post_unlike.png",
            name: "删除",
            onBubbleMenuButtonPressed: () {
    
    
              widget.onBubbleMenuButtonPressed(1);
            },
          ),
        ],
      ),
    );
  }
}

// 显示气泡菜单
class ChatBubbleMenuButton extends StatelessWidget {
    
    
  const ChatBubbleMenuButton({
    
    
    Key? key,
    required this.icon,
    required this.name,
    required this.onBubbleMenuButtonPressed,
    required this.width,
    required this.height,
  }) : super(key: key);

  final String icon;
  final String name;
  final Function onBubbleMenuButtonPressed;
  final double width;
  final double height;

  
  Widget build(BuildContext context) {
    
    
    return ButtonWidget(
      width: width,
      height: height,
      borderRadius: 6.0,
      onPressed: () {
    
    
        onBubbleMenuButtonPressed();
      },
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          buildButtonIcon(context),
          SizedBox(
            height: 2.0,
          ),
          Text(
            "${
      
      name}",
            textAlign: TextAlign.left,
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              fontSize: 11,
              fontWeight: FontWeight.w500,
              fontStyle: FontStyle.normal,
              color: ColorUtil.hexColor(0xffffff),
              decoration: TextDecoration.none,
            ),
          ),
        ],
      ),
    );
  }

  Widget buildButtonIcon(BuildContext context) {
    
    
    // 本地图片
    String imageUrl = "${
    
    icon ?? ""}";
    String start = "file://";
    if (imageUrl.startsWith(start)) {
    
    
      String imageAssetFile = imageUrl.substring(start.length);

      return ImageHelper.wrapAssetAtImages(
        "icons/${
      
      imageAssetFile}",
        width: 18.0,
        height: 18.0,
      );
    }

    // 网络图片
    return ImageHelper.imageNetwork(
      imageUrl: imageUrl,
      width: 18.0,
      height: 18.0,
      errorHolder: Container(),
    );
  }
}

我们需要在聊天气泡上使用Gesture实现长按获取到获取气泡的位置及大小

GestureDetector(
        onTap: () {
    
    
          if (widget.onBubbleTapPressed != null) {
    
    

          }
        },
        onDoubleTap: () {
    
    
          if (widget.onBubbleDoubleTapPressed != null) {
    
    

          }
        },
        onLongPressStart: (LongPressStartDetails details) {
    
    
// 获取到获取气泡的位置及大小
        },
        child: Container(),
      );

获取大小代码

if (bubbleKey.currentContext == null) return null;
    // 获取输入框的位置
    final renderObject =
        bubbleKey.currentContext!.findRenderObject() as RenderBox;
    if (renderObject == null) return null;

    // offset.dx , offset.dy 就是控件的左上角坐标
    Offset offset = renderObject.localToGlobal(Offset.zero);
    //获取size
    Size size = renderObject.size;

三、实现弹窗功能

showGeneralDialog:用于自定义提示框

// 气泡长按操作
  static void elemBubbleLongPress(
      BuildContext context, CommonChatMessage chatMessage,
      {
    
    Map<String, dynamic>? additionalArguments,
      required LongPressStartDetails details,
      ChatBubbleFrame? chatBubbleFrame}) {
    
    
    if (ChatBubbleFrame == null) {
    
    
      // 没有气泡大小的时候
      return;
    }

    Offset bubbleOffset = chatBubbleFrame!.offset;
    Size bubbleSize = chatBubbleFrame!.size;

    LoggerManager().debug("chatBubbleFrame offset:${
      
      chatBubbleFrame.offset},"
        "size:${
      
      chatBubbleFrame.size}");

    // 气泡长按弹出菜单
    showGeneralDialog(
      context: context,
      barrierLabel: '',
      barrierColor: Colors.black.withOpacity(0.0),
      transitionDuration: const Duration(milliseconds: 200),
      barrierDismissible: true,
      pageBuilder: (BuildContext dialogContext, Animation animation,
          Animation secondaryAnimation) {
    
    
        return GestureDetector(
          child: ChatBubbleMenuContainer(
            chatMessage: chatMessage,
            bubbleOffset: bubbleOffset,
            bubbleSize: bubbleSize,
            onBubbleMenuButtonPressed: (int index) {
    
    
              Navigator.of(dialogContext).pop();
            },
          ),
          onTapDown: (TapDownDetails details) {
    
    
            Navigator.of(dialogContext).pop();
          },
        );
      },
      transitionBuilder: (_, anim, __, child) {
    
    
        return FadeTransition(
          opacity: anim,
          child: child,
        );
      },
    );

四、小结

flutter聊天界面-聊天气泡长按弹出复制、删除按钮菜单,主要实现Canvas结合画笔CustomPainter绘制,根据GestureDetector获取位置,通过findRenderObject、localToGlobal获取当前气泡的大小及位置,最后使用showGeneralDialog弹出。

学习记录,每天不停进步。

猜你喜欢

转载自blog.csdn.net/gloryFlow/article/details/131595742