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:
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))), ), ), ], ), ), ), ), ); }}