Flutter 氣泡效果合集(全網(wǎng)最全)

\color{red}{提醒氣泡效果補(bǔ)充}【2020-1-10】

效果圖

remind_bubble.gif

使用案例

class BubbleDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _BubbleDemoState();
  }
}

class _BubbleDemoState extends State<BubbleDemo> {

  // 是否顯示左邊氣泡
  bool leftTipReplay = false;

  // 是否顯示左邊氣泡
  bool rightTipReplay = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppbar.appBar(
        context,
        title: '提醒氣泡組件',
        theme: CustomAppbarTheme.white,
      ),
      body: Column(
        children: <Widget>[
          SizedBox(height: 50,),
          // 氣泡左邊
          GestureDetector(
            child: Stack(
              children: <Widget>[
                //主題
                Container(
                  width: Screen.width,
                  height: 200,
                  color: Colors.red,
                  child: Center(
                    child: Text(
                      'BUBBLE_LEFT',
                      style: TextStyle(
                        fontSize: 36,
                        color: Colors.white,
                      ),
                    ),
                  ),
                ),
                Positioned(
                  top: 20,
                  left: -20,
                  child: _showLeftBubble(),
                ),
              ],
            ),
            onTap: () {
              rightTipReplay = false;
              leftTipReplay = true;
              setState(() {});

            },
          ),
          SizedBox(height: 50,),
          // 氣泡右邊
          GestureDetector(
            child: Stack(
              children: <Widget>[
                //主題
                Container(
                  width: Screen.width,
                  height: 200,
                  color: Colors.blueAccent,
                  child: Center(
                    child: Text(
                      'BUBBLE_RIGHT',
                      style: TextStyle(
                        fontSize: 36,
                        color: Colors.white,
                      ),
                    ),
                  ),
                ),
                Positioned(
                  top: 20,
                  left: -20,
                  child: _showRightBubble(),
                ),
              ],
            ),
            onTap: () {
              leftTipReplay = false;
              rightTipReplay = true;
              setState(() {});

            },
          )
        ],
      ),
    );
  }

  Widget _showLeftBubble() {
    return leftTipReplay
        ? Container(
            width: AdaptationUtils.setWidth(228.0),
            height: AdaptationUtils.setWidth(50.0),
            child: TipBubble(
              content: '雙方在倒計(jì)時(shí)結(jié)束前確認(rèn)CP關(guān)系后即可查看對(duì)方聯(lián)系方式',
              tipReplay: leftTipReplay,
//              tipDuration: 90000,
              top: AdaptationUtils.px(-20.0),
              left: AdaptationUtils.px(64.0),
              hornDirction: 'left',
            ),
          )
        : Container();
  }

  Widget _showRightBubble() {
    return rightTipReplay
        ? Container(
      width: AdaptationUtils.setWidth(228.0),
      height: AdaptationUtils.setWidth(50.0),
      child: TipBubble(
        content: '雙方在倒計(jì)時(shí)結(jié)束前確認(rèn)CP關(guān)系后即可查看對(duì)方聯(lián)系方式',
        tipReplay: rightTipReplay,
//        tipDuration: 90000,
        top: AdaptationUtils.px(-20.0),
        left: AdaptationUtils.px(64.0),
        hornDirction: 'right',
      ),
    )
        : Container();
  }
}

先上效果圖(聊天氣泡)

效果圖

1.BubbleWidget封裝

  • 通過(guò)系統(tǒng)的Canvas繪制
/// 氣泡組件封裝
///
/// created by hujintao
/// created at 2019-10-21
//

import 'dart:math';
import 'package:flutter/material.dart';

enum BubbleArrowDirection { top, bottom, right, left, topLeft }

class BubbleWidget extends StatelessWidget {
  // 尖角位置
  final position;

  // 尖角高度
  var arrHeight;

  // 尖角角度
  var arrAngle;

  // 圓角半徑
  var radius;

  // 寬度
  final width;

  // 高度
  final height;

  // 邊距
  double length;

  // 顏色
  Color color;

  // 邊框顏色
  Color borderColor;

  // 邊框?qū)挾?  final strokeWidth;

  // 填充樣式
  final style;

  // 子 Widget
  final child;

  // 子 Widget 與起泡間距
  var innerPadding;

  BubbleWidget(
    this.width,
    this.height,
    this.color,
    this.position, {
    Key key,
    this.length = 1,
    this.arrHeight = 12.0,
    this.arrAngle = 60.0,
    this.radius = 10.0,
    this.strokeWidth = 4.0,
    this.style = PaintingStyle.fill,
    this.borderColor,
    this.child,
    this.innerPadding = 6.0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    if (style == PaintingStyle.stroke && borderColor == null) {
      borderColor = color;
    }
    if (arrAngle < 0.0 || arrAngle >= 180.0) {
      arrAngle = 60.0;
    }
    if (arrHeight < 0.0) {
      arrHeight = 0.0;
    }
    if (radius < 0.0 || radius > width * 0.5 || radius > height * 0.5) {
      radius = 0.0;
    }
    if (position == BubbleArrowDirection.top ||
        position == BubbleArrowDirection.bottom) {
      if (length < 0.0 || length >= width - 2 * radius) {
        length = width * 0.5 - arrHeight * tan(_angle(arrAngle * 0.5)) - radius;
      }
    } else {
      if (length < 0.0 || length >= height - 2 * radius) {
        length =
            height * 0.5 - arrHeight * tan(_angle(arrAngle * 0.5)) - radius;
      }
    }
    if (innerPadding < 0.0 ||
        innerPadding >= width * 0.5 ||
        innerPadding >= height * 0.5) {
      innerPadding = 2.0;
    }
    Widget bubbleWidget;
    if (style == PaintingStyle.fill) {
      bubbleWidget = Container(
          width: width,
          height: height,
          child: Stack(children: <Widget>[
            CustomPaint(
                painter: BubbleCanvas(context, width, height, color, position,
                    arrHeight, arrAngle, radius, strokeWidth, style, length)),
            _paddingWidget()
          ]));
    } else {
      bubbleWidget = Container(
          width: width,
          height: height,
          child: Stack(children: <Widget>[
            CustomPaint(
                painter: BubbleCanvas(
                    context,
                    width,
                    height,
                    color,
                    position,
                    arrHeight,
                    arrAngle,
                    radius,
                    strokeWidth,
                    PaintingStyle.fill,
                    length)),
            CustomPaint(
                painter: BubbleCanvas(
                    context,
                    width,
                    height,
                    borderColor,
                    position,
                    arrHeight,
                    arrAngle,
                    radius,
                    strokeWidth,
                    style,
                    length)),
            _paddingWidget()
          ]));
    }
    return bubbleWidget;
  }

  Widget _paddingWidget() {
    return Padding(
        padding: EdgeInsets.only(
            top: (position == BubbleArrowDirection.top)
                ? arrHeight + innerPadding
                : innerPadding,
            right: (position == BubbleArrowDirection.right)
                ? arrHeight + innerPadding
                : innerPadding,
            bottom: (position == BubbleArrowDirection.bottom)
                ? arrHeight + innerPadding
                : innerPadding,
            left: (position == BubbleArrowDirection.left)
                ? arrHeight + innerPadding
                : innerPadding),
        child: Center(child: this.child));
  }
}

class BubbleCanvas extends CustomPainter {
  BuildContext context;
  final position;
  final arrHeight;
  final arrAngle;
  final radius;
  final width;
  final height;
  final length;
  final color;
  final strokeWidth;
  final style;

  BubbleCanvas(
      this.context,
      this.width,
      this.height,
      this.color,
      this.position,
      this.arrHeight,
      this.arrAngle,
      this.radius,
      this.strokeWidth,
      this.style,
      this.length);

  @override
  void paint(Canvas canvas, Size size) {
    Path path = Path();
    path.arcTo(
        Rect.fromCircle(
            center: Offset(
                (position == BubbleArrowDirection.left)
                    ? radius + arrHeight
                    : radius,
                (position == BubbleArrowDirection.top)
                    ? radius + arrHeight
                    : radius),
            radius: radius),
        pi,
        pi * 0.5,
        false);
    if (position == BubbleArrowDirection.top) {
      path.lineTo(length + radius, arrHeight);
      path.lineTo(
          length + radius + arrHeight * tan(_angle(arrAngle * 0.5)), 0.0);
      path.lineTo(length + radius + arrHeight * tan(_angle(arrAngle * 0.5)) * 2,
          arrHeight);
    }
    path.lineTo(
        (position == BubbleArrowDirection.right)
            ? width - radius - arrHeight
            : width - radius,
        (position == BubbleArrowDirection.top) ? arrHeight : 0.0);
    path.arcTo(
        Rect.fromCircle(
            center: Offset(
                (position == BubbleArrowDirection.right)
                    ? width - radius - arrHeight
                    : width - radius,
                (position == BubbleArrowDirection.top)
                    ? radius + arrHeight
                    : radius),
            radius: radius),
        -pi * 0.5,
        pi * 0.5,
        false);
    if (position == BubbleArrowDirection.right) {
      path.lineTo(width - arrHeight, length + radius);
      path.lineTo(
          width, length + radius + arrHeight * tan(_angle(arrAngle * 0.5)));
      path.lineTo(width - arrHeight,
          length + radius + arrHeight * tan(_angle(arrAngle * 0.5)) * 2);
    }
    path.lineTo(
        (position == BubbleArrowDirection.right) ? width - arrHeight : width,
        (position == BubbleArrowDirection.bottom)
            ? height - radius - arrHeight
            : height - radius);
    path.arcTo(
        Rect.fromCircle(
            center: Offset(
                (position == BubbleArrowDirection.right)
                    ? width - radius - arrHeight
                    : width - radius,
                (position == BubbleArrowDirection.bottom)
                    ? height - radius - arrHeight
                    : height - radius),
            radius: radius),
        pi * 0,
        pi * 0.5,
        false);
    if (position == BubbleArrowDirection.bottom) {
      path.lineTo(width - radius - length, height - arrHeight);
      path.lineTo(
          width - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)),
          height);
      path.lineTo(
          width - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)) * 2,
          height - arrHeight);
    }
    path.lineTo(
        (position == BubbleArrowDirection.left) ? radius + arrHeight : radius,
        (position == BubbleArrowDirection.bottom)
            ? height - arrHeight
            : height);
    path.arcTo(
        Rect.fromCircle(
            center: Offset(
                (position == BubbleArrowDirection.left)
                    ? radius + arrHeight
                    : radius,
                (position == BubbleArrowDirection.bottom)
                    ? height - radius - arrHeight
                    : height - radius),
            radius: radius),
        pi * 0.5,
        pi * 0.5,
        false);
    if (position == BubbleArrowDirection.left) {
      path.lineTo(arrHeight, height - radius - length);
      path.lineTo(0.0,
          height - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)));
      path.lineTo(
          arrHeight,
          height -
              radius -
              length -
              arrHeight * tan(_angle(arrAngle * 0.5)) * 2);
    }
    path.lineTo((position == BubbleArrowDirection.left) ? arrHeight : 0.0,
        (position == BubbleArrowDirection.top) ? radius + arrHeight : radius);
    path.close();
    canvas.drawPath(
        path,
        Paint()
          ..color = color
          ..style = style
          ..strokeCap = StrokeCap.round
          ..strokeWidth = strokeWidth);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

double _angle(angle) {
  return angle * pi / 180;
}

2.氣泡組件使用

注意事項(xiàng)

  • 必填參數(shù)
    • 寬度 ScreenUtil().setWidth(326),
    • 高度 ScreenUtil().setWidth(64),
    • 背景顏色 Color(0xff333333),
    • 位置 BubbleArrowDirection.bottom
  • 可選參數(shù)
    • 箭頭寬度 length: ScreenUtil().setWidth(20)
    • 箭頭高度 arrHeight : ScreenUtil().setWidth(12)
    • 箭頭讀書(shū) arrAngle: 75.0,
    • 子Widget與起泡間距 innerPadding
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fpdxapp/components/bubble/bubble_widget.dart';

class BubblePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(children: <Widget>[
        SizedBox(
          height: 20,
        ),

        ///1- 復(fù)制刪除,撤回消息-氣泡BottomRight
        Padding(
            padding: EdgeInsets.all(4.0),
            child: BubbleWidget(
              ScreenUtil().setWidth(326),
              ScreenUtil().setWidth(64),
              Color(0xff333333),
              BubbleArrowDirection.bottom,
              length: ScreenUtil().setWidth(20),
              child: Row(
                mainAxisSize: MainAxisSize.max,
                children: <Widget>[
                  // 復(fù)制按鈕
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: Center(
                        child: Text(
                          '復(fù)制',
                          style: TextStyle(
                              color: Color(0xffE4E4E4),
                              fontSize: ScreenUtil().setSp(20)),
                        ),
                      ),
                      width: ScreenUtil().setWidth(108),
                      height: ScreenUtil().setWidth(64),
                    ),
                  ),
                  // line
                  Container(
                      width: ScreenUtil().setWidth(1),
                      color: Color(0xff707070)),
                  // 刪除按鈕
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: Center(
                        child: Text(
                          '刪除',
                          style: TextStyle(
                              color: Color(0xffE4E4E4),
                              fontSize: ScreenUtil().setSp(20)),
                        ),
                      ),
                      width: ScreenUtil().setWidth(108),
                      height: ScreenUtil().setWidth(64),
                    ),
                  ),
                  // line
                  Container(
                      width: ScreenUtil().setWidth(1),
                      color: Color(0xff707070)),
                  // 撤回按鈕
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: Center(
                        child: Text(
                          '撤回',
                          style: TextStyle(
                              color: Color(0xffE4E4E4),
                              fontSize: ScreenUtil().setSp(20)),
                        ),
                      ),
                      width: ScreenUtil().setWidth(108),
                      height: ScreenUtil().setWidth(64),
                    ),
                  ),
                ],
              ),
              arrHeight: ScreenUtil().setWidth(12),
              arrAngle: 75.0,
              innerPadding: 0.0,
            )),
        SizedBox(
          height: 5,
        ),

        ///2- 復(fù)制刪除,撤回消息-氣泡BottomLeft
        Padding(
          padding: EdgeInsets.all(4.0),
          child: BubbleWidget(
            ScreenUtil().setWidth(326),
            ScreenUtil().setWidth(64),
            Color(0xff333333),
            BubbleArrowDirection.bottom,
            length: ScreenUtil().setWidth(250),
            child: Row(
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                // 復(fù)制按鈕
                GestureDetector(
                  onTap: () {},
                  child: Container(
                    child: Center(
                      child: Text(
                        '復(fù)制',
                        style: TextStyle(
                            color: Color(0xffE4E4E4),
                            fontSize: ScreenUtil().setSp(20)),
                      ),
                    ),
                    width: ScreenUtil().setWidth(108),
                    height: ScreenUtil().setWidth(64),
                  ),
                ),
                // line
                Container(
                    width: ScreenUtil().setWidth(1), color: Color(0xff707070)),
                // 刪除按鈕
                GestureDetector(
                  onTap: () {},
                  child: Container(
                    child: Center(
                      child: Text(
                        '刪除',
                        style: TextStyle(
                            color: Color(0xffE4E4E4),
                            fontSize: ScreenUtil().setSp(20)),
                      ),
                    ),
                    width: ScreenUtil().setWidth(108),
                    height: ScreenUtil().setWidth(64),
                  ),
                ),
                // line
                Container(
                    width: ScreenUtil().setWidth(1), color: Color(0xff707070)),
                // 撤回按鈕
                GestureDetector(
                  onTap: () {},
                  child: Container(
                    child: Center(
                      child: Text(
                        '撤回',
                        style: TextStyle(
                            color: Color(0xffE4E4E4),
                            fontSize: ScreenUtil().setSp(20)),
                      ),
                    ),
                    width: ScreenUtil().setWidth(108),
                    height: ScreenUtil().setWidth(64),
                  ),
                ),
              ],
            ),
            arrHeight: ScreenUtil().setWidth(12),
            arrAngle: 75.0,
            innerPadding: 0.0,
          ),
        ),

        SizedBox(
          height: 5,
        ),

        ///3- 復(fù)制刪除,撤回消息-氣泡TopLeft
        Padding(
            padding: EdgeInsets.all(4.0),
            child: BubbleWidget(
              ScreenUtil().setWidth(326),
              ScreenUtil().setWidth(64),
              Color(0xff333333),
              BubbleArrowDirection.top,
              length: ScreenUtil().setWidth(20),
              child: Row(
                mainAxisSize: MainAxisSize.max,
                children: <Widget>[
                  // 復(fù)制按鈕
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: Center(
                        child: Text(
                          '復(fù)制',
                          style: TextStyle(
                              color: Color(0xffE4E4E4),
                              fontSize: ScreenUtil().setSp(20)),
                        ),
                      ),
                      width: ScreenUtil().setWidth(108),
                      height: ScreenUtil().setWidth(64),
                    ),
                  ),
                  // line
                  Container(
                      width: ScreenUtil().setWidth(1),
                      color: Color(0xff707070)),
                  // 刪除按鈕
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: Center(
                        child: Text(
                          '刪除',
                          style: TextStyle(
                              color: Color(0xffE4E4E4),
                              fontSize: ScreenUtil().setSp(20)),
                        ),
                      ),
                      width: ScreenUtil().setWidth(108),
                      height: ScreenUtil().setWidth(64),
                    ),
                  ),
                  // line
                  Container(
                      width: ScreenUtil().setWidth(1),
                      color: Color(0xff707070)),
                  // 撤回按鈕
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: Center(
                        child: Text(
                          '撤回',
                          style: TextStyle(
                              color: Color(0xffE4E4E4),
                              fontSize: ScreenUtil().setSp(20)),
                        ),
                      ),
                      width: ScreenUtil().setWidth(108),
                      height: ScreenUtil().setWidth(64),
                    ),
                  ),
                ],
              ),
              arrHeight: ScreenUtil().setWidth(12),
              arrAngle: 75.0,
              innerPadding: 0.0,
            )),

        SizedBox(
          height: 5,
        ),

        ///4- 復(fù)制刪除,撤回消息-氣泡TopRight
        Padding(
          padding: EdgeInsets.all(4.0),
          child: BubbleWidget(
            ScreenUtil().setWidth(326),
            ScreenUtil().setWidth(64),
            Color(0xff333333),
            BubbleArrowDirection.top,
            length: ScreenUtil().setWidth(250),
            child: Row(
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                // 復(fù)制按鈕
                GestureDetector(
                  onTap: () {},
                  child: Container(
                    child: Center(
                      child: Text(
                        '復(fù)制',
                        style: TextStyle(
                            color: Color(0xffE4E4E4),
                            fontSize: ScreenUtil().setSp(20)),
                      ),
                    ),
                    width: ScreenUtil().setWidth(108),
                    height: ScreenUtil().setWidth(64),
                  ),
                ),
                // line
                Container(
                    width: ScreenUtil().setWidth(1), color: Color(0xff707070)),
                // 刪除按鈕
                GestureDetector(
                  onTap: () {},
                  child: Container(
                    child: Center(
                      child: Text(
                        '刪除',
                        style: TextStyle(
                            color: Color(0xffE4E4E4),
                            fontSize: ScreenUtil().setSp(20)),
                      ),
                    ),
                    width: ScreenUtil().setWidth(108),
                    height: ScreenUtil().setWidth(64),
                  ),
                ),
                // line
                Container(
                    width: ScreenUtil().setWidth(1), color: Color(0xff707070)),
                // 撤回按鈕
                GestureDetector(
                  onTap: () {},
                  child: Container(
                    child: Center(
                      child: Text(
                        '撤回',
                        style: TextStyle(
                            color: Color(0xffE4E4E4),
                            fontSize: ScreenUtil().setSp(20)),
                      ),
                    ),
                    width: ScreenUtil().setWidth(108),
                    height: ScreenUtil().setWidth(64),
                  ),
                ),
              ],
            ),
            arrHeight: ScreenUtil().setWidth(12),
            arrAngle: 75.0,
            innerPadding: 0.0,
          ),
        ),
        SizedBox(
          height: 5,
        ),

        // 氣泡右
        Padding(
            padding: EdgeInsets.all(4.0),
            child: Container(
                alignment: Alignment.centerRight,
                child: BubbleWidget(200.0, 40.0, Colors.blue.withOpacity(0.7),
                    BubbleArrowDirection.right,
                    child: Text('你好,我是BubbleWidget!',
                        style:
                            TextStyle(color: Colors.white, fontSize: 14.0))))),
        Padding(
            padding: EdgeInsets.all(4.0),
            child: Container(
                alignment: Alignment.bottomLeft,
                child: BubbleWidget(300.0, 40.0, Colors.red.withOpacity(0.7),
                    BubbleArrowDirection.top,
                    length: 20,
                    child: Text('你好,你有什么特性化?',
                        style:
                            TextStyle(color: Colors.white, fontSize: 14.0))))),

        Padding(
            padding: EdgeInsets.all(4.0),
            child: Container(
                alignment: Alignment.centerRight,
                child: BubbleWidget(300.0, 90.0, Colors.blue.withOpacity(0.7),
                    BubbleArrowDirection.right,
                    child: Text('我可以自定義:\n尖角方向,尖角高度,尖角角度,\n距圓角位置,圓角大小,邊框樣式等!',
                        style:
                            TextStyle(color: Colors.white, fontSize: 16.0))))),
        Padding(
            padding: EdgeInsets.all(4.0),
            child: Container(
                alignment: Alignment.centerLeft,
                child: BubbleWidget(140.0, 40.0, Colors.cyan.withOpacity(0.7),
                    BubbleArrowDirection.left,
                    child: Text('你有什么不足?',
                        style:
                            TextStyle(color: Colors.white, fontSize: 14.0))))),
        Padding(
            padding: EdgeInsets.all(4.0),
            child: Container(
                alignment: Alignment.centerRight,
                child: BubbleWidget(350.0, 60.0, Colors.green.withOpacity(0.7),
                    BubbleArrowDirection.right,
                    child: Text('我現(xiàn)在還不會(huì)動(dòng)態(tài)計(jì)算高度,只可用作背景!',
                        style:
                            TextStyle(color: Colors.white, fontSize: 16.0))))),
        Padding(
            padding: EdgeInsets.all(4.0),
            child: Container(
                alignment: Alignment.centerLeft,
                child: BubbleWidget(
                    105.0,
                    60.0,
                    Colors.deepOrange.withOpacity(0.7),
                    BubbleArrowDirection.left,
                    child: Text('繼續(xù)加油!',
                        style:
                            TextStyle(color: Colors.white, fontSize: 16.0))))),
      ]),
      appBar: AppBar(
        centerTitle: true,
        leading: GestureDetector(
          child: Icon(Icons.arrow_back_ios,
              size: 20, color: Color(0xff333333)),
          onTap: () {
            Navigator.of(context).maybePop();
          },
        ),
        title: Text(
          '氣泡合集',
          style: TextStyle(color: Colors.black),
        ),
      ),
    );
  }
}

方案二:通過(guò) 自定義PopupRoute 實(shí)現(xiàn)BubbleWidget氣泡

  • 通過(guò) 自定義PopupRoute 實(shí)現(xiàn)多個(gè)Bubble只選擇一個(gè),避免多個(gè)
  • PopupWindow 自定義
  • 通過(guò)路由實(shí)現(xiàn)氣泡的顯示和隱藏
  • 通過(guò) GlobalKey 獲取Widget的位置
  • 顯示
 Navigator.push(
      context,
      PopRoute(
        child: Bubble()
      ),
    );

效果圖

效果圖

關(guān)鍵源碼封裝

import 'package:flutter/material.dart';

class Popup extends StatelessWidget {
  final Widget child;
  final Function onClick; //點(diǎn)擊child事件
  final EdgeInsetsGeometry padding;

  Popup({
    @required this.child,
    this.onClick,
    this.padding,
  });

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: GestureDetector(
        child: Stack(
          children: <Widget>[
            Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              color: Colors.transparent,
            ),
            Padding(
              child: child,
              padding: padding,
            ),
          ],
        ),
        onTap: () {
          //點(diǎn)擊空白處
          Navigator.of(context).pop();
        },
      ),
    );
  }
}

class PopRoute extends PopupRoute {
  final Duration _duration = Duration(milliseconds: 300);
  Widget child;

  PopRoute({@required this.child});

  @override
  Color get barrierColor => null;

  @override
  bool get barrierDismissible => true;

  @override
  String get barrierLabel => null;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return child;
  }

  @override
  Duration get transitionDuration => _duration;
}

使用案例

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fpdxapp/components/bubble/bubble.dart';
import 'package:fpdxapp/components/bubble/bubble_widget.dart';
import 'package:fpdxapp/components/custom_appbar.dart';
import 'package:fpdxapp/constants/icons.dart';
import 'package:fpdxapp/utils/toast.dart';
import 'package:fpdxapp/utils/widget.dart';

class BubbleDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _BubbleDemoState();
  }
}

class _BubbleDemoState extends State<BubbleDemo> {
  GlobalKey btnKey = GlobalKey(debugLabel: '1');
  List menus = [{'text':'ShowMenu1','isYourSelf':true}, {'text':'ShowMenu2','isYourSelf':false}, {'text':'ShowMenu3','isYourSelf':true}];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppbar.appBar(
        context,
        title: '氣泡',
        theme: CustomAppbarTheme.white,
      ),
      body: Container(
        child: Column(
          children: menus.map((item) {
            return CellForRow(
              text: item['text'],
              isYourSelf: item['isYourSelf'],
            );
          }).toList(),
        ),
        margin: EdgeInsets.only(top: 100),
      ),
    );
  }
}

class CellForRow extends StatefulWidget {
  final String text;
  final bool isYourSelf;

  CellForRow({this.text,this.isYourSelf = true});

  @override
  _CellForRowState createState() => _CellForRowState();
}

class _CellForRowState extends State<CellForRow> {
  GlobalKey btnKey = GlobalKey();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    if (this.widget.text != null) {
      btnKey = GlobalKey(debugLabel: this.widget.text);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: MaterialButton(
        key: btnKey,
        height: 45.0,
        onPressed: () {
          _showBubble(
            context,
            globalKey: btnKey,
            valueChanged: (i, text) {
              Toast.show("值變化了${text}");
            },
            isYourSelf: this.widget.isYourSelf,
          );
        },
        child: Text(this.widget.text),
      ),
    );
  }

  ///彈出退出按鈕
  void _showBubble(@required BuildContext context,
      {Widget child,
      GlobalKey globalKey,
      List<String> titles,
      Function(int i, String text) valueChanged,
      bool isYourSelf = true}) {
    if (globalKey == null) {
      return;
    }
    if (titles == null) {
      titles = ['復(fù)制', '刪除', '撤回'];
    }

    Rect rect = WidgetUtil.getRectWithWidgetGlobalKey(globalKey);
    print('氣泡===============>>>>>>>>>>>>rect${rect}');
    Navigator.push(
      context,
      PopRoute(
        child: isYourSelf
            ? Popup(
                child: BubbleWidget(
                  ScreenUtil().setWidth(109) * titles.length,
                  ScreenUtil().setWidth(64),
                  Color(0xff333333),
                  BubbleArrowDirection.bottom,
                  length: ScreenUtil().setWidth(250),
                  child: Row(
                    mainAxisSize: MainAxisSize.max,
                    children: titles.asMap().keys.map((i) {
                      return Container(
                        child: Row(
                          children: <Widget>[
                            GestureDetector(
                              onTap: () {
                                if (valueChanged != null) {
                                  valueChanged(i, titles[i]);
                                }
                                Navigator.of(context).pop();

                              },
                              child: Container(
                                child: Center(
                                  child: Text(
                                    titles[i],
                                    style: TextStyle(
                                        color: Color(0xffE4E4E4),
                                        fontSize: ScreenUtil().setSp(20)),
                                  ),
                                ),
                                width: ScreenUtil().setWidth(108),
                                height: ScreenUtil().setWidth(64),
                              ),
                            ),
                            i == titles.length - 1
                                ? Container()
                                : Container(
                                    width: ScreenUtil().setWidth(1),
                                    color: Color(0xff707070)),
                          ],
                        ),
                      );
                    }).toList(),
                  ),
                  arrHeight: ScreenUtil().setWidth(12),
                  arrAngle: 75.0,
                  innerPadding: 0.0,
                ),
                padding: EdgeInsets.fromLTRB(
                    rect.left + ScreenUtil().setWidth(40),
                    rect.top - ScreenUtil().setWidth(20),
                    rect.right,
                    rect.bottom),
              )
            : Popup(
                child: BubbleWidget(
                  ScreenUtil().setWidth(109) * titles.length,
                  ScreenUtil().setWidth(64),
                  Color(0xff333333),
                  BubbleArrowDirection.bottom,
                  length: ScreenUtil().setWidth(20),
                  child: Row(
                    mainAxisSize: MainAxisSize.max,
                    children: titles.asMap().keys.map((i) {
                      return Container(
                        child: Row(
                          children: <Widget>[
                            GestureDetector(
                              onTap: () {
                                if (valueChanged != null) {
                                  valueChanged(i, titles[i]);
                                }
                                Navigator.of(context).pop();

                              },
                              child: Container(
                                child: Center(
                                  child: Text(
                                    titles[i],
                                    style: TextStyle(
                                        color: Color(0xffE4E4E4),
                                        fontSize: ScreenUtil().setSp(20)),
                                  ),
                                ),
                                width: ScreenUtil().setWidth(108),
                                height: ScreenUtil().setWidth(64),
                              ),
                            ),
                            i == titles.length - 1
                                ? Container()
                                : Container(
                                    width: ScreenUtil().setWidth(1),
                                    color: Color(0xff707070)),
                          ],
                        ),
                      );
                    }).toList(),
                  ),
                  arrHeight: ScreenUtil().setWidth(12),
                  arrAngle: 75.0,
                  innerPadding: 0.0,
                ),
                padding: EdgeInsets.fromLTRB(
                    rect.left + ScreenUtil().setWidth(40),
                    rect.top - ScreenUtil().setWidth(20),
                    rect.right,
                    rect.bottom),
              ),
      ),
    );
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容