Flutter – ClipPath

with this simple custom ShapeBorder:

class MessageBorder extends ShapeBorder {
  final bool usePadding;

  MessageBorder({this.usePadding = true});

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: usePadding? 20 : 0);

  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    rect = Rect.fromPoints(rect.topLeft, rect.bottomRight - Offset(0, 20));
    return Path()
      ..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(rect.height / 2)))
      ..moveTo(rect.bottomCenter.dx - 10, rect.bottomCenter.dy)
      ..relativeLineTo(10, 20)
      ..relativeLineTo(20, -20)
      ..close();
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}

  @override
  ShapeBorder scale(double t) => this;
}

and that usage code:

Container(
  height: 64,
  decoration: ShapeDecoration(
    color: Colors.white,
    shape: MessageBorder(),
    shadows: [
      BoxShadow(color: Colors.black, blurRadius: 4.0, offset: Offset(2, 2)),
    ],
  ),
  alignment: Alignment.centerRight,
  padding: EdgeInsets.only(right: 8),
  child: Container(
    width: 30,
    decoration: BoxDecoration(
      color: Colors.blueAccent,
      shape: BoxShape.circle,
    ),
  ),
),

you can have the result like this:

enter image description here

EDIT: if you want your Widget to be clickable then use something like this:

class ButtonMessage extends StatelessWidget {
  final String text;
  final GestureTapCallback onTap;

  const ButtonMessage(this.text, this.onTap);

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.white,
      elevation: 4,
      clipBehavior: Clip.antiAlias,
      shape: MessageBorder(),
      child: InkWell(
        splashColor: Colors.orange,
        hoverColor: Colors.blueGrey,
        highlightColor: Colors.transparent,
        onTap: onTap,
        child: Container(
          height: 64,
          padding: EdgeInsets.only(bottom: 20, right: 8),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              Container(
                width: 7,
                height: 8,
                decoration: BoxDecoration(color: Color(0xFFCCCCCC), shape: BoxShape.circle),
              ),
              Container(width: 3,),
              Container(
                width: 7,
                height: 8,
                decoration: BoxDecoration(color: Color(0xFFCCCCCC), shape: BoxShape.circle),
              ),
              Container(width: 3,),
              Container(
                width: 7,
                height: 8,
                decoration: BoxDecoration(color: Color(0xFFCCCCCC), shape: BoxShape.circle),
              ),
              Container(width: 6,),
              Container(
                width: 25,
                height: 24,
                decoration: BoxDecoration(color: Color(0xFF1287BA), shape: BoxShape.circle),
                child: Center(
                  child: Text(text, style: TextStyle(color: Color(0xFFFFFFFF))),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

EDIT2: clickable baloon with a custom shadows:

class ButtonMessage extends StatelessWidget {
  final String text;
  final GestureTapCallback onTap;

  const ButtonMessage(this.text, this.onTap);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: ShapeDecoration(
        shape: MessageBorder(usePadding: false),
        shadows: [
          BoxShadow(color: Colors.black, blurRadius: 4, offset: Offset(2, 2)),
        ],
      ),
      child: Material(
        color: Colors.white,
        clipBehavior: Clip.antiAlias,
        shape: MessageBorder(),
        child: InkWell(
          splashColor: Colors.orange,
          hoverColor: Colors.blueGrey,
          highlightColor: Colors.transparent,
          onTap: onTap,
          child: Container(
            height: 64,
            padding: EdgeInsets.only(bottom: 20, right: 8),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[
                Container(
                  width: 7,
                  height: 8,
                  decoration: BoxDecoration(color: Color(0xFFCCCCCC), shape: BoxShape.circle),
                ),
                Container(width: 3,),
                Container(
                  width: 7,
                  height: 8,
                  decoration: BoxDecoration(color: Color(0xFFCCCCCC), shape: BoxShape.circle),
                ),
                Container(width: 3,),
                Container(
                  width: 7,
                  height: 8,
                  decoration: BoxDecoration(color: Color(0xFFCCCCCC), shape: BoxShape.circle),
                ),
                Container(width: 6,),
                Container(
                  width: 25,
                  height: 24,
                  decoration: BoxDecoration(color: Color(0xFF1287BA), shape: BoxShape.circle),
                  child: Center(
                    child: Text(text, style: TextStyle(color: Color(0xFFFFFFFF))),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Leave a Comment