[》跳过拾光记忆]
拾光记忆
1-15.资产管理 Fam、手势触摸、枚举高阶用法、快速实现单选和多选、Diy 滑动轨道、水印功能、Image 高阶用法、矩阵16个参数含义、颜色差异、颜色填充、图像镜像、图像旋转、图像去色等功能的集合
简介: 该篇主要介绍15 篇文章
含有功能的目录,可根据自己的需求选择对应的功能介绍查看。
推荐: ⭐️⭐️⭐️⭐️⭐️
16. Flutter 之 IImage 图像反色处理
简介: 该篇主要介绍 Flutter 之 IImage 库中如何实现图像反色功能以及实现原理的介绍。
推荐: ⭐️⭐️⭐️⭐️⭐️
17. Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~(一)
18. Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~(二)
19. Flutter 绘制路径 Path 的全部方法介绍,一篇足矣~(三)
简介: 该篇主要介绍 Flutter
之 图形(Canvas
) 绘制路径 (Path
)基础功能以及方法实现底层代码的解析。
推荐: ⭐️⭐️⭐️⭐️⭐️
一、简述
随着技术的发展,聊天软件如雨后春笋般不断涌现。为了提升用户的视觉体验,许多软件采用了聊天内容以气泡形式展示,并设计了多种聊天气泡款式供用户选择更换。而当用户输入的文字变化时,气泡能够跟随内容进行相应变化,这一功能真是神奇!那么在Flutter中,我们如何实现这样的效果呢?接下来,我们将介绍聊天气泡的实现过程,并解决其中遇到的问题。
二、实现阐述
聊天气泡目前实现方式大致分为两种:
- 气泡形式使用 Canvas 绘制来实现气泡的变化
- 借助UI切图,拉伸图片来实现气泡的变化
从上述介绍的两种方式,第 2 种方式实现相比第 1 种方式很容易实现, 而第 2 种方式的核心就是点九图。
三、点九图
点九图 是一种可拉伸的位图,自动调整它的大小,来使图像在充当背景时可以在界面中自适应。 在 Flutter 中点九图表现为将图片切割成九份,如下图所示:
我们可以设置第5块区域的大小和位置也就能确定第 2、4、6、8 四个模块; 如果气泡发生水平变化,那么第 2、5、8 三个模块进行水平拉伸处理;如果气泡垂直发生变化,那么第 4、5、6 三个模块进行垂直拉伸处理;如果气泡垂直和水平都有变化时, 第 2、8 做水平拉伸;第 4、6 做垂直拉伸;第 5 同时水平和垂直拉伸处理。上面介绍的在 Flutter 的 canvas.drawImageNine
和 DecorationImage:centerSlice
中涉及到。
四、DecorationImage:centerSlice - 气泡
下面是一个气泡实例,如下所示:
flutter
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 600, maxHeight: 600),
decoration: const BoxDecoration(
image: DecorationImage(
centerSlice: Rect.fromLTWH(10, 15, 3, 3),
image: AssetImage(FamManager.db),
),
),
child: const Text(
'Flutter 聊化聊化聊化聊化聊化化聊化聊化聊化聊化聊化聊化聊化',
style: TextStyle(color: Colors.red),
).insetsSymmetric(vertical: 20, horizontal: 20),
),
),
);
}
}
从上面代码的第 13 行,我们设置点九图的中心区域大小, 如下图所示:
我们运行上面视图,发现显示不出效果,同时还有异常提醒,如下所示:
我们根据异常提示,很容易找到异常的位置,如下图所示:
在上面图片代码中,有 final FittedSizes fittedSizes = applyBoxFit(fit, inputSize / scale, outputSize);
这一行代码,这行代码是获取到的 fittedSizes
决定着 sourceSize
的值。该方法的底层实现如下:
flutter
FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 || outputSize.width <= 0.0) {
return const FittedSizes(Size.zero, Size.zero);
}
Size sourceSize, destinationSize;
switch (fit) {
case BoxFit.fill:
sourceSize = inputSize;
destinationSize = outputSize;
break;
// .... 省略无关代码
}
return FittedSizes(sourceSize, destinationSize);
}
我们开始分析该方传入的参数,如下所示:
-
fit
我们从上图代码的
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
可知applyBoxFit
方法中传入的fit
参数值是fill
, 所以才隐藏其他类型的无关代码。 -
inputSize
有上面图片中的
flutterSize outputSize = rect.size; Size inputSize = Size(image.width.toDouble(), image.height.toDouble()); Offset? sliceBorder; if (centerSlice != null) { sliceBorder = inputSize / scale - centerSlice.size as Offset; outputSize = outputSize - sliceBorder as Size; inputSize = inputSize - sliceBorder * scale as Size; }
这些代码可知
inputSize
的大小就是我们设置中心区域的大小(scale =1 时),则inputSize
传入的值的大小是Size(3,3)
。 -
outputSize
outputSize
是我们组件的尺寸减去裁剪边框sliceBorder
得到的。则outputSize
传入的值的大小是Size(355,-4)
。
上面参数值的结果我们可以通过打断点的形式获取。然后我们以获取到 applyBoxFit
方法传入的参数值,则有
flutter
if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 || outputSize.width <= 0.0) {
return const FittedSizes(Size.zero, Size.zero);
}
代码进行判定,由于 outputSize.height
的高度是 -4
, 而返回 FittedSizes(Size.zero, Size.zero);
。
-
从上述代码分析,是
outputSize
的高度出现负数导致异常。 而outputSize
的高度计算公式如下:height = rect.height - (image.height / scale - centerSlice.height)
, 结合上边的例子将数值带入公式,height : 60 - (67 / 1 - 3 ) = -4
,而得到验证。 -
从
height = rect.height - (image.height / scale - centerSlice.height)
公式想让height
大于零有两种方式。如下所示:-
调整
rect.height
, 让组件初始高度增加。比如:我们给文本增加Padding
。 示例如下:flutterclass MyWidget extends StatelessWidget { const MyWidget({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Container( constraints: const BoxConstraints(maxWidth: 600, maxHeight: 600), decoration: const BoxDecoration( image: DecorationImage( centerSlice: Rect.fromLTWH(10, 15, 3, 3), image: AssetImage(FamManager.db), ), ), child: const Text( 'Flutter 聊化聊化聊化聊化聊化化聊化聊化聊化聊化聊化聊化聊化', style: TextStyle(color: Colors.red), ).insetsSymmetric(vertical: 25, horizontal: 20), ), ), ); } }
上述代码运行视图如下:
上边视图显示正常,我们粗略计算一下高度:
25 * 2 + 14 * 1.4 - (67/1 - 3) = 5.9
, 我们利用断点得到的高度如图所示:
-
调整图像的
scale
值, 同时也要等比缩放centerSlice
的值 ,让裁剪边距变大。实例如下:flutterclass MyWidget extends StatelessWidget { const MyWidget({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Container( constraints: const BoxConstraints(maxWidth: 600, maxHeight: 600), decoration: const BoxDecoration( image: DecorationImage( centerSlice: Rect.fromLTWH(10 / 2, 15 / 2, 3 / 2, 3 / 2), image: AssetImage(FamManager.db), scale: 2, ), ), child: const Text( 'Flutter 聊化聊化聊化聊化聊化化聊化聊化聊化聊化聊化聊化聊化', style: TextStyle(color: Colors.red), ).insetsSymmetric(vertical: 20, horizontal: 20), ), ), ); } }
上述代码运行实例如下:
我们粗略计算一下:
20 * 2 + 14 * 1.4 - (67/2 - 3/2) = 27.6
, 我们再利用断点获取一下高度如下所示:
-
五、Canvas::drawImageNine
下面是应用实例如下:
flutter
class MyWidgetCanvas extends StatelessWidget {
const MyWidgetCanvas({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FutureBuilder(
future: getImageFromAssets(FamManager.db),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.connectionState == ConnectionState.active) {
return const Text('加载中');
}
return CustomPaint(
painter: ChatBubblePainter(snapshot.data!),
child: const Text(
'Flutter 聊化聊化聊化聊化聊化聊化化聊化聊化聊化化聊化聊化聊化化聊聊化',
style: TextStyle(color: Colors.red),
).insetsSymmetric(vertical: 20, horizontal: 20),
);
},
),
),
);
}
Future<ui.Image> getImageFromAssets(String path) async {
final ImmutableBuffer immutableBuffer = await rootBundle.loadBuffer(path);
final ui.Codec codec = await ui.instantiateImageCodecFromBuffer(
immutableBuffer,
);
final ui.FrameInfo frameInfo = await codec.getNextFrame();
return frameInfo.image;
}
}
class ChatBubblePainter extends CustomPainter {
const ChatBubblePainter(this.image);
final ui.Image image;
@override
void paint(Canvas canvas, Size size) {
canvas.drawImageNine(image, const Rect.fromLTWH(10 / 2, 15 / 2, 3 / 2, 3 / 2), Offset.zero & size, Paint());
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
上述代码运行的视图结果如下:
从上述可以看到,气泡的实现使用绘制比UI相对繁琐。大家可以根据需求选择实现方式。
六、鼓励与支持
上面介绍了聊天气泡的实现方式以及在使用过程中遇到问题的总结和解决问题的探索。希望该篇文章功能让你对Flutter 中的点九图有更深入的了解。如果你感觉文章写的还可以,那请留下你的收藏与评论。该篇文章使用的资源管理是由 fam
提供;使用的便捷添加边距是由 idkit
提供。本篇的实例代码地址如下: 聊天气泡实例仓库。