'How to make a custom bubble shape in flutter?

I am trying to create a custom tooltip with the triangle shape on either side. I have created a bubble but how to add the triangle in there without using any library?

class SdToolTip extends StatelessWidget {
  final Widget child;
  final String message;

  const SdToolTip({
    required this.message,
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Tooltip(
        child: child,
        message: message,
        padding: const EdgeInsets.all(8),
        decoration: BoxDecoration(
            color: Colors.blueAccent.withOpacity(0.6),
            borderRadius: BorderRadius.circular(22)),
        textStyle: const TextStyle(
            fontSize: 15, fontStyle: FontStyle.italic, color: Colors.white),
      ),
    );
  }
}

enter image description here



Solution 1:[1]

You can do it by CustomPainter without any library.

Example 1: Create Custom Painter Class,

class customStyleArrow extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Colors.white
      ..strokeWidth = 1
      ..style = PaintingStyle.fill;
    final double triangleH = 10;
    final double triangleW = 25.0;
    final double width = size.width;
    final double height = size.height;

    final Path trianglePath = Path()
      ..moveTo(width / 2 - triangleW / 2, height)
      ..lineTo(width / 2, triangleH + height)
      ..lineTo(width / 2 + triangleW / 2, height)
      ..lineTo(width / 2 - triangleW / 2, height);
    canvas.drawPath(trianglePath, paint);
    final BorderRadius borderRadius = BorderRadius.circular(15);
    final Rect rect = Rect.fromLTRB(0, 0, width, height);
    final RRect outer = borderRadius.toRRect(rect);
    canvas.drawRRect(outer, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Wrap your text widget with CustomPaint,

return CustomPaint(
      painter: customStyleArrow(),
      child: Container(
        padding: EdgeInsets.only(left: 15, right: 15, bottom: 20, top: 20),
        child: Text("This is the custom painter for arrow down curve",
            style: TextStyle(
              color: Colors.black,
            )),
      ),
    );

enter image description here

Example 2: Check below example code for tooltip shapedecoration

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Customize Tooltip'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Tooltip(
          child: const IconButton(
            icon: Icon(Icons.info, size: 30.0),
            onPressed: null,
          ),
          message: 'Hover Icon for Tooltip...',
          padding: const EdgeInsets.all(20),
          showDuration: const Duration(seconds: 10),
          decoration: ShapeDecoration(
            color: Colors.blue,
            shape: ToolTipCustomShape(),
          ),
          textStyle: const TextStyle(color: Colors.white),
          preferBelow: false,
          verticalOffset: 20,
        ),
      ),
    );
  }
}

class ToolTipCustomShape extends ShapeBorder {
  final bool usePadding;

  ToolTipCustomShape({this.usePadding = true});

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

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

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

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

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

enter image description here

Solution 2:[2]

Wrap your widget with CustomPaint refer to this article https://medium.com/flutter-community/a-deep-dive-into-custompaint-in-flutter-47ab44e3f216 and documentation for more info, should do the trick.

Solution 3:[3]

Try this package https://pub.dev/packages/shape_of_view_null_safe

ShapeOfView(
  shape: BubbleShape(
    position: BubblePosition.Bottom,
    arrowPositionPercent: 0.5,
    borderRadius: 20,
    arrowHeight: 10,
    arrowWidth: 10
  ),
//Your Data goes here

  child: ...,
)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 YaDev
Solution 3