/
bubble.dart
150 lines (140 loc) · 4.19 KB
/
bubble.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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;
}