import 'dart:math';
import 'package:flutter/material.dart';
// 尖角方向枚舉
enum BubbleAngleDirection { left, right }
class BubbleWidget extends StatelessWidget {
BubbleWidget(
this.data, {
Key key,
this.textStyle = const TextStyle(color: Colors.black, fontSize: 13),
this.maxWidth,
this.color = Colors.lightGreen,
this.radius = 10,
this.padding = 10,
//
this.angle = 60,
this.angleHeight = 8,
this.anglePos = BubbleAngleDirection.left,
}) : super(key: key);
final String data; //文本內(nèi)容
final TextStyle textStyle;//文本樣式
final double maxWidth; //最大寬帶
final Color color; //背景顏色
final double radius;
final double padding;
// 尖角
final double angle; //尖角角度
final double angleHeight; //尖角高度
final BubbleAngleDirection anglePos;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _BubbleCanvas(
context: context,
data: data,
textStyle: textStyle,
// 默認(rèn)最大寬帶為屏幕寬度的3/4
maxWidth: maxWidth == null ? MediaQuery.of(context).size.width * 0.75 : maxWidth,
color: color,
padding: padding,
radius: radius,
//
angle: angle,
angleHeight: angleHeight,
anglePos: anglePos));
}
}
class _BubbleCanvas extends CustomPainter {
_BubbleCanvas({
this.context,
this.data,
this.textStyle,
//
this.maxWidth,
this.color,
this.padding,
this.radius,
//
this.angle,
this.angleHeight,
this.anglePos,
});
final BuildContext context;
final String data;
final TextStyle textStyle;
final double maxWidth;
final Color color;
final double padding;
final double radius;
final double angle;
final double angleHeight;
final BubbleAngleDirection anglePos;
double _angle(angle) => angle * pi / 180;
@override
void paint(Canvas canvas, Size size) {
// 氣泡組件的實(shí)際寬度
double width = 0;
double maxHeight = 0;
int lines = 1;
// 計(jì)算文字內(nèi)容所需的寬和高
data.runes.forEach((element) {
String str = String.fromCharCode(element);
TextPainter tp = TextPainter(text: TextSpan(style: textStyle, text: str), textDirection: TextDirection.rtl);
tp.layout();
if (width + tp.width > (maxWidth - padding)) {
lines++;
width = 0;
}
if (maxHeight < tp.height) maxHeight = tp.height;
width += tp.width;
});
width = lines > 1 ? maxWidth : (width + padding * 2 + angleHeight);
//氣泡組件的實(shí)際高度
double height = maxHeight * lines + padding * 2;
double angleLength = angleHeight * tan(_angle(angle * 0.5));
// 重新計(jì)算坐標(biāo)原點(diǎn), 注意Row的mainAxisAlignment屬性會影響組件的坐標(biāo)原點(diǎn),需要重新計(jì)算
Offset origin = Offset(anglePos == BubbleAngleDirection.left ? 0 : -width, -height / 2);
Path path = Path();
//左上角圓角
Offset leftTop = Offset(anglePos == BubbleAngleDirection.left ? radius + angleHeight : radius, radius);
path.arcTo(Rect.fromCircle(center: Offset(origin.dx + leftTop.dx, origin.dy + leftTop.dy), radius: radius), pi, pi * 0.5, false);
// 右上角圓角
Offset rightTop = Offset(anglePos == BubbleAngleDirection.right ? width - angleHeight - radius : width - radius, radius);
path.arcTo(Rect.fromCircle(center: Offset(origin.dx + rightTop.dx, origin.dy + rightTop.dy), radius: radius), -pi * 0.5, pi * 0.5, false);
if (anglePos == BubbleAngleDirection.right) {
path.lineTo(origin.dx + width - angleHeight, origin.dy + padding + maxHeight / 2 - angleLength);
path.lineTo(origin.dx + width, origin.dy + padding + maxHeight / 2);
path.lineTo(origin.dx + width - angleHeight, origin.dy + padding + maxHeight / 2 + angleLength);
}
// 右下角圓角
Offset rightBottom = Offset(anglePos == BubbleAngleDirection.right ? width - angleHeight - radius : width - radius, height - radius);
path.arcTo(Rect.fromCircle(center: Offset(origin.dx + rightBottom.dx, origin.dy + rightBottom.dy), radius: radius), 0, pi * 0.5, false);
// 左下角圓角
Offset leftBottom = Offset((anglePos == BubbleAngleDirection.left) ? angleHeight + radius : radius, height - radius);
path.arcTo(Rect.fromCircle(center: Offset(origin.dx + leftBottom.dx, origin.dy + leftBottom.dy), radius: radius), pi * 0.5, pi * 0.5, false);
if (anglePos == BubbleAngleDirection.left) {
path.lineTo(origin.dx + angleHeight, origin.dy + padding + maxHeight / 2 - angleLength);
path.lineTo(origin.dx, origin.dy + padding + maxHeight / 2);
path.lineTo(origin.dx + angleHeight, origin.dy + padding + maxHeight / 2 + angleLength);
}
path.close();
canvas.drawPath(
path,
Paint()
..color = color
..style = PaintingStyle.fill
..strokeCap = StrokeCap.round
..isAntiAlias = true);
canvas.save();
// 計(jì)算文本內(nèi)容繪制的起始坐標(biāo)
final defautX = anglePos == BubbleAngleDirection.left ? origin.dx + (angleHeight + padding) : origin.dx + padding;
double offsetX = defautX;
double offsetY = origin.dy + padding;
data.runes.forEach((element) {
String str = String.fromCharCode(element);
TextPainter tp = TextPainter(text: TextSpan(style: textStyle, text: str), textDirection: TextDirection.rtl);
tp.layout();
//橫向達(dá)到氣泡組件的最大寬度時,換行
if (offsetX + tp.width > (maxWidth - padding)) {
offsetY += tp.height;
offsetX = defautX;
}
//繪制文本
//tp.height < maxHeight ? (maxHeight - tp.height) : 0 是因?yàn)樽帜负椭形牡母叨炔灰粯?這樣可以讓字母和文字底部對齊
tp.paint(canvas, Offset(offsetX, offsetY + (tp.height < maxHeight ? (maxHeight - tp.height) : 0)));
offsetX += tp.width;
});
canvas.restore();
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
}
[Flutter]聊天氣泡組件
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 微信和QQ是使用人數(shù)最多的兩款社交軟件,大家應(yīng)該都使用過。比如我,在上學(xué)時候,使用的是QQ,工作以后呢,更多使用的...
- 挖掘技術(shù)哪家強(qiáng)?氣泡到底是什么鬼?浴室那些肥皂泡泡嗎?NONONO,本文所講的氣泡絕非一般的氣泡,而是跟我們網(wǎng)絡(luò)生...
- 有道是 不積硅步,無以至千里 ;不積小流,無以成江海 。 這章內(nèi)容比較簡單 ,就直接貼上代碼 自定義組件MyApp...
- 一、Flutter Text 組件 二、Container 組件 說明 : border:Border.all()...
- 合上你的雙眼,留意自己的呼吸。放松。你不需要去改變。只需去覺察自己。你有一種很強(qiáng)大的能力一一覺察。這也是你的本能。...