You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
4.2 KiB

import 'package:flutter/cupertino.dart';
// normally, bubble view width is longer than its height,
// so make the triangle smaller
const Size _kBubbleTriangleSizeH = Size(9.0, 16.0);
const Size _kBubbleTriangleSizeV = Size(18.0, 9.0);
const BorderRadius _kBubbleBorderRadius =
BorderRadius.all(Radius.circular(7.5));
/// triangle position
enum FLBubbleFrom { bottom, top, left, right }
class FLBubble extends StatelessWidget {
FLBubble(
{Key? key,
this.backgroundColor = CupertinoColors.white,
this.from = FLBubbleFrom.bottom,
this.padding = const EdgeInsets.all(8),
@required this.child})
: super(key: key);
final Color backgroundColor;
final FLBubbleFrom from;
final Widget? child;
final EdgeInsetsGeometry padding;
@override
Widget build(BuildContext context) {
FLBubbleFrom _from = from;
final TextDirection textDirection = Directionality.of(context);
final bool isRtl = textDirection == TextDirection.rtl;
final bool isHorizontal =
(_from == FLBubbleFrom.left || _from == FLBubbleFrom.right);
if (isRtl && isHorizontal) {
_from =
_from == FLBubbleFrom.left ? FLBubbleFrom.right : FLBubbleFrom.left;
}
// triangle
final Size triangleSize =
isHorizontal ? _kBubbleTriangleSizeH : _kBubbleTriangleSizeV;
final Widget triangle = SizedBox.fromSize(
size: triangleSize,
child: CustomPaint(
painter:
_FLBubbleNotchPainter(pos: _from, backgroundColor: backgroundColor),
),
);
// main rect
final Widget rect = ClipRRect(
borderRadius: _kBubbleBorderRadius,
child: DecoratedBox(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: _kBubbleBorderRadius,
border: Border.all(color: backgroundColor, width: 0),
),
child: Container(
padding: padding,
child: child,
),
),
);
// layout use original from, Row will auto change direction.
if (from == FLBubbleFrom.bottom) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
rect,
triangle,
const Padding(padding: EdgeInsets.only(bottom: 8.0))
],
);
} else if (from == FLBubbleFrom.top) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 8.0)),
triangle,
rect
],
);
} else if (from == FLBubbleFrom.left) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Padding(padding: EdgeInsets.only(left: 8.0)),
triangle,
rect
],
);
} else {
// FLBubbleFrom.right
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
rect,
triangle,
const Padding(padding: EdgeInsets.only(right: 8.0))
],
);
}
}
}
class _FLBubbleNotchPainter extends CustomPainter {
_FLBubbleNotchPainter({this.pos, this.backgroundColor});
final FLBubbleFrom? pos;
final Color? backgroundColor;
@override
void paint(Canvas canvas, Size size) {
// paint
final Paint paint = Paint()
..color = backgroundColor!
..style = PaintingStyle.fill;
// triangle
Path triangle;
if (pos == FLBubbleFrom.bottom) {
triangle = Path()
..lineTo(size.width / 2, 0.0)
..lineTo(0.0, size.height)
..lineTo(-(size.width / 2), 0.0)
..close();
} else if (pos == FLBubbleFrom.left) {
triangle = Path()
..lineTo(size.width, size.height / 2)
..lineTo(size.width, -(size.height / 2))
..close();
} else if (pos == FLBubbleFrom.top) {
triangle = Path()
..lineTo(size.width / 2, size.height)
..lineTo(-(size.width / 2), size.height)
..close();
} else {
// FLBubbleFrom.right
triangle = Path()
..lineTo(0.0, size.height / 2)
..lineTo(size.width, 0.0)
..lineTo(0.0, -(size.height / 2))
..close();
}
// draw
canvas.drawPath(triangle, paint);
}
@override
bool shouldRepaint(_FLBubbleNotchPainter oldPainter) => false;
}