封装气泡组件
/// 气泡组件封装////// 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;// 边框宽度final strokeWidth;// 填充样式final style;// 子 Widgetfinal 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);@overrideWidget 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);@overridevoid 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);}@overridebool shouldRepaint(CustomPainter oldDelegate) {return true;}}double _angle(angle) {return angle * pi / 180;}
测试代码
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- 复制删除,撤回消息-气泡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>[
                  // 复制按钮
                  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),
                    ),
                  ),
                  // 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- 复制删除,撤回消息-气泡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>[
                // 复制按钮
                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),
                  ),
                ),
                // 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- 复制删除,撤回消息-气泡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>[
                  // 复制按钮
                  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),
                    ),
                  ),
                  // 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- 复制删除,撤回消息-气泡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>[
                // 复制按钮
                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),
                  ),
                ),
                // 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('我现在还不会动态计算高度,只可用作背景!',
                        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('继续加油!',
                        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),
        ),
      ),
    );
  }
}
其他例子
import 'package:flutter/material.dart';
class LogisticsInformationItemextends StatefulWidget {
   ColortopColor;
  ColorcenterColor;
  ColorbottomColor;
  ColortextColor;
  Stringtext;
  LogisticsInformationItem({
this.topColor,
    this.centerColor,
    this.bottomColor,
    this.textColor,
    this.text,
  });
  @override
  _LogisticsInformationItemStatecreateState() =>_LogisticsInformationItemState();
}
class _LogisticsInformationItemStateextends State {
  double item_height =0.0;
  GlobalKey textKey =new GlobalKey();
  @override
  void initState() {
// TODO: implement initState
    super.initState();
    ///  监听是否渲染完
    WidgetsBinding widgetsBinding = WidgetsBinding.instance;
    widgetsBinding.addPostFrameCallback((callback){
///  获取相应控件的size
      RenderObject renderObject =textKey.currentContext.findRenderObject();
      setState(() {
item_height = renderObject.semanticBounds.size.height;
      });
    });
  }
@override
  Widget build(BuildContext context) {
return Container(
color: Colors.white,
      padding:EdgeInsets.only(left:20, right:10),
      child:Row(
children: [
///  左侧的线
          Container(
margin:EdgeInsets.only(left:20),
            width:10,
            height:item_height,
            child:Column(
mainAxisAlignment: MainAxisAlignment.center,
              children: [
Expanded(
child:Container(
width:0.9,
                      color:widget.topColor,
                    )
),
                Container(
height:10,
                  width:10,
                  decoration:BoxDecoration(
color:widget.centerColor,
                    borderRadius:BorderRadius.all(Radius.circular(5)),
                  ),
                ),
                Expanded(
child:Container(
width:0.9,
                      color:widget.bottomColor,
                    )
),
              ],
            ),
          ),
          ///  右侧的文案
          Expanded(
child:Padding(
key:textKey,
              padding:const EdgeInsets.only(left:20, top:10, bottom:10),
              child:Text(
widget.text,
                style:TextStyle(fontSize:15, color:widget.textColor),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
                    
