Flutter开发 9.Widget-图片加载及展示详解(Image、Container、Decoration、Clip)
爪爪-详解图片及效果处理
1. 静态资源配置
1.1 概念描述
Flutter应用程序可以包含代码和 assets(有时称为资源)。asset是打包到程序安装包中的,可在运行时访问。常见类型的asset包括静态数据(例如JSON文件),配置文件,图标和图片(JPEG,WebP,GIF,动画WebP / GIF,PNG,BMP和WBMP)。
1.2 资源配置方式
我们先添加一个图片资源文件到lib下的images目录中,我们用这张图片来做测试,下载到本地后,改名为avatar.png
将图片COPY到lib/images目录下:
Flutter使用pubspec.yaml文件(位于项目根目录),来识别应用程序所需的asset。打开pubspec.yaml文件,添加图片文件的路径。
flutter:
assets:
- lib/images/avatar.png
到此资源文件设置就完成了。每当添加一个资源就需要在此配置一下。
如果找不到资源文件,先确认一下在每行前面加的空格数量是否正确,assets前面有2个空格,-前面有三个空格。
1.3 多分辨率适配
另外Flutter可以为当前设备加载适合其分辨率的图像。
assets:
- lib/images/avatar.png
- lib/images/2.0x/avatar.png
- lib/images/3.0x/avatar.png
2. Image组件
2.1 属性详细说明
属性名 | 类型 | 说明 |
---|---|---|
image | ImageProvider | 实现ImageProvider抽象类的所有对象 |
frameBuilder | ImageFrameBuilder | 通常用来显示图片加载成功前的默认图,或者从未加载到加载成功的一些动画效果。下面有示例 |
loadingBuilder | ImageLoadingBuilder | 图片加载进度的回调方法. 下面有示例 |
errorBuilder | ImageErrorWidgetBuilder | 图片加载出错失败时的回调方法 |
width | double | 图片的宽 |
height | double | 图片高度 |
color | Color | 图片的混合色值 |
colorBlendMode | BlendMode | 混合模式 |
fit | BoxFit | 缩放模式 |
alignment | Alignment | 对齐方式 |
repeat | ImageRepeat | 重复方式 |
semanticLabel | String | 用于描述图片语义的字符串 |
excludeFromSemantics | bool | 是否启用图像的语义描述 |
isAntiAlias | bool | 是否抗锯齿 |
filterQuality | FilterQuality | 图片缩放时图片的质量 none low medium hight |
centerSlice | Rect | 将图片设置成类似于安卓中的.9图,用于图片拉伸 |
gaplessPlayback | bool | 当 ImageProvider 发生变化后,重新加载图片时,新图显示前原图是否保留。 |
2.2 使用本地资源
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
spawnRegularText("本地图片:"),
const Image(image: AssetImage("lib/images/avatar.png")),
])))));
}
直接用AssetImage 将本地的图片路径字符串传入就可以得到ImageProvider对像了。
class AssetImage extends AssetBundleImageProvider
abstract class AssetBundleImageProvider extends ImageProvider
直接运行就可以显示出图片了
2.3 repeat参数用法
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
spawnRegularText("本地图片:"),
const SizedBox(
width: 600,
height: 200,
child: Image(
image: AssetImage("lib/images/avatar.png"),
width: 120,
height: 120,
repeat: ImageRepeat.repeatX,
),
),
])))));
}
外层包了一个SizedBox为指定里边图片重复显示的最大宽度
枚举值 | 说明 |
---|---|
ImageRepeat.repeat | X、Y 轴都重复 |
ImageRepeat.repeatX | 只在 X 轴重复 |
ImageRepeat.repeatY | 只在 Y 轴重复 |
ImageRepeat.noRepeat | 不重复 |
运行效果如下
2.4 colorBlendMode参数详解
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
spawnRegularText("本地图片:"),
const Image(
image: AssetImage("lib/images/avatar.png"),
width: 120,
height: 120,
color: Colors.yellowAccent,
colorBlendMode: BlendMode.colorBurn,
),
const Image(
image: AssetImage("lib/images/avatar.png"),
width: 120,
height: 120,
color: Colors.purple,
colorBlendMode: BlendMode.difference,
),
const Image(
image: AssetImage("lib/images/avatar.png"),
width: 120,
height: 120,
color: Colors.red,
colorBlendMode: BlendMode.xor,
),
])))));
}
因为这个枚举参数比较多,所以爪爪这边直接先运行先看一下效果:
为了便于直观的看到效果,爪爪将所有参数都运行一遍,截图列在右侧.
BlendMode枚举值 | 说明 | 预览图 |
---|---|---|
clear | Drop both the source and destination images, leaving nothing.(全清除,什么都不留) | |
src | Drop the destination image, only paint the source image.(删除目标图,只画原图,即:只显示你指定的color颜色) | |
dst | Drop the source image, only paint the destination image.(只画目标图,不显示源图, 即:只显示图, color没用了) | |
srcOver | Composite the source image over the destination image.(在目标图像上合成源图像。Colors.yellowAccent) | |
dstOver | Composite the source image under the destination image.(在目标图像下合成源图像。Colors.yellowAccent) | |
srcIn | Show the source image, but only where the two images overlap. (显示源图像,但仅显示两个图像重叠的位置。) | |
srcOut | Show the source image, but only where the two images do not overlap.(显示源图像,但仅在两个图像不重叠的地方显示。Colors.yellowAccent) | |
dstOut | Show the destination image, but only where the two images do not overlap.(显示目标图像,但仅在两个图像不重叠的地方显示。) | |
srcATop | Composite the source image over the destination image, but only where it overlaps the destination.(在目标图像上合成源图像,但仅在其与目标重叠的位置合成。Colors.yellowAccent) | |
dstATop | Composite the destination image over the source image, but only where it overlaps the source.(在源图像上合成目标图像,但仅在其与源重叠的位置合成。) | |
xor | Apply a bitwise xor operator to the source and destination images.(对源图像和目标图像应用按位异或运算。) | |
plus | Sum the components of the source and destination images.(对源图像和目标图像的成份求和。Colors.red) | |
modulate | Multiply the color components of the source and destination images.(将源图像和目标图像的颜色分量相乘。Colors.red) | |
screen | Following blend modes are defined in the CSS Compositing standard.(CSS合成标准中定义了以下混合模式。Colors.red) | |
overlay | Multiply the components of the source and destination images after adjusting them to favor the destination.(将源图像和目标图像的分量相乘,然后调整它们以有利于目标。Colors.red) | |
darken | Composite the source and destination image by choosing the lowest value from each color channel.(通过从每个颜色通道中选择最低值来合成源图像和目标图像。Colors.red) | |
lighten | Composite the source and destination image by choosing the highest value from each color channel.(通过从每个颜色通道中选择最高值来合成源图像和目标图像。Colors.red) | |
colorDodge | Divide the destination by the inverse of the source.(将目标除以源的倒数。Colors.red) | |
colorBurn | Divide the inverse of the destination by the source, and inverse the result.(将目标的倒数除以源,然后将结果倒数。Colors.red) | |
hardLight | Multiply the components of the source and destination images after adjusting them to favor the source.(将源图像和目标图像的分量相乘,然后将它们调整为有利于源图像。Colors.red) | |
softLight | Use [colorDodge] for source values below 0.5 and [colorBurn] for source values above 0.5.(对于低于0.5的源值,使用[colorDodge],对于高于0.5的源值,使用[colorBurn]。 Colors.red) | |
difference | Subtract the smaller value from the bigger value for each channel.(从每个通道的较大值中减去较小的值。 Colors.red) | |
exclusion | Subtract double the product of the two images from the sum of the two images.(从两个图像的总和中减去两个图像的乘积的两倍。 | Colors.red) |
multiply | Multiply the components of the source and destination images, including the alpha channel.(将源图像和目标图像的组件相乘,包括alpha通道。Colors.red) | |
hue | Take the hue of the source image, and the saturation and luminosity of the destination image.(获取源图像的色调,以及目标图像的饱和度和亮度。Colors.red) | |
saturation | Take the saturation of the source image, and the hue and luminosity of the destination image.(获取源图像的饱和度,以及目标图像的色调和亮度。Colors.red) | |
color | Take the hue and saturation of the source image, and the luminosity of the destination image.(获取源图像的色调和饱和度,以及目标图像的亮度。Colors.red) | |
luminosity | Take the luminosity of the source image, and the hue and saturation of the destination image.(获取源图像的亮度,以及目标图像的色调和饱和度。Colors.red) |
2.5 NetworkImage
NetworkImage实现了ImageProvider投象类,他实现的是一个异步方式从网络下载图片的功能,我们将在演示代码中使用它来加载网络图片。他可以只传入一个图片的URL地址即可,使用特别方便。
const factory NetworkImage(String url, { double scale, Map<String, String>? headers }) = network_image.NetworkImage;
2.6 frameBuilder详解
官方文档的原文说明如下:
If this is null, this widget will display an image that is painted as soon as the first image frame is available (and will appear to “pop” in if it becomes available asynchronously). Callers might use this builder to add effects to the image (such as fading the image in when it becomes available) or to display a placeholder widget while the image is loading.
大概意思就是:
如果该值为空,则该组件将在第一个图像帧可用时立即显示绘制的图像(如果异步可用,直接显示)。调用者可以使用此生成器向图像添加效果(例如,图像可用时淡入淡出),或者在加载图像时显示占位符组件。
按照官方的示例代码大概如下:
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
const SizedBox(height: 50, width: 1),
spawnRegularText("网络图片:"),
Image(image: const NetworkImage(
"https://img-blog.csdnimg.cn/20190520151631821.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1YW53ZW5jaGFv,size_16,color_FFFFFF,t_70",
),
frameBuilder: (BuildContext context, Widget child, int? frame,
bool wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) return child;
return AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: const Duration(seconds: 4),
curve: Curves.easeOut,
child: child);
},
运行效果如下:
frameBuilder渐变加载图片
2.7 loadingBuilder详解
loadingBuilder回调方法会在图片加载过程中不断的执行,可以实时的得到当前已经加载的进度。
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
// ignore: avoid_print
print("loadingBuilder: expectedTotalBytes=${loadingProgress?.expectedTotalBytes},cumulativeBytesLoaded=${loadingProgress?.cumulativeBytesLoaded}");
执行这段代码后,在一张图片加载中,可以看到打印的LOG:
flutter: loadingBuilder: expectedTotalBytes=null,cumulativeBytesLoaded=null
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=15831
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=73176
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=130519
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=179673
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=212439
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=253400
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=286168
flutter: loadingBuilder: expectedTotalBytes=324457,cumulativeBytesLoaded=324457
flutter: loadingBuilder: expectedTotalBytes=null,cumulativeBytesLoaded=null
我们可以为图片加载做一个加载中的状态,演示代码如下
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
const SizedBox(height: 50, width: 1),
spawnRegularText("网络图片:"),
Image(
image: const NetworkImage(
"https://img-blog.csdnimg.cn/20190520151631821.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1YW53ZW5jaGFv,size_16,color_FFFFFF,t_70",
),
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
(loadingProgress.expectedTotalBytes)!
: null,
),
);
},
),
])))));
}
运行效果如下:
loadingBuilder 加载动画效果
3.图片圆角实现
3.1 通过Container的decoration实现
3.1.1 Container简介
Container是一个组合类容器,它本身不对应具体的RenderObject,它是DecoratedBox、ConstrainedBox、Transform、Padding、Align等组件组合的一个多功能容器,所以我们只需通过一个Container组件可以实现同时需要装饰、变换、限制的场景。
属性名 | 类型 | 说明 |
---|---|---|
color | Color | 背景色 |
decoration | Decoration | 背景装饰器 |
foregroundDecoration | Decoration | 前景装饰器 |
width | double | 容器的宽度 |
height | double | 容器的高度 |
constraints | BoxConstraints | 容器限制条件 |
child | Widget | 包含在容器内的组件 |
3.1.2 BoxDecoration简介
可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等。BoxDecoration定义如下:
属性名 | 类型 | 说明 |
---|---|---|
color | Color | 颜色 |
image | DecorationImage | 装饰器图片资源 |
border | BoxBorder | 边框 |
borderRadius | BorderRadiusGeometry | 圆角 |
boxShadow | List < BoxShadow > | 阴影 |
gradient | Gradient | 渐变色 |
backgroundBlendMode | BlendMode | 背景混合模式 |
shape | BoxShape | 形状 |
3.1.3 演示实例
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
const SizedBox(height: 50, width: 1),
spawnRegularText("网络图片:"),
Container(
width: 300,
height: 300,
decoration: const BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
"https://img-blog.csdnimg.cn/20190520151631821.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1YW53ZW5jaGFv,size_16,color_FFFFFF,t_70",
),
fit: BoxFit.fill),
),
),
])))));
}
运行一下这段代码,执行的效果如下:
3.2 通过Clip实现
3.2.1 Clip介绍
Flutter中提供了一些剪裁函数,用于对组件进行剪裁,返回的Clip也是一个组件的子类.
class ClipRRect extends SingleChildRenderObjectWidget
abstract class RenderObjectWidget extends Widget
Clip名称 | 说明 |
---|---|
ClipOval | 子组件为正方形时剪裁成内贴圆形;为矩形时,剪裁成内贴椭圆 |
ClipRRect | 将子组件剪裁为圆角矩形 |
ClipRect | 默认剪裁掉子组件布局空间之外的绘制内容(溢出部分剪裁) |
ClipPath | 按照自定义的路径剪裁 |
3.2.2 ClipRect示例
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
const SizedBox(height: 50, width: 1),
spawnRegularText("网络图片:"),
ClipRRect(
borderRadius: BorderRadius.circular(30),
child: const Image(
image: NetworkImage(
"https://img-blog.csdnimg.cn/20190520151631821.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1YW53ZW5jaGFv,size_16,color_FFFFFF,t_70",
),
width: 300,
height: 300,
fit: BoxFit.fill),
),
])))));
}
运行一下这段代码,执行的效果如下:
3.2.3 ClipOval示例
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
const SizedBox(height: 50, width: 1),
spawnRegularText("网络图片:"),
const ClipOval(
child: Image(
image: NetworkImage(
"https://img-blog.csdnimg.cn/20190520151631821.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1YW53ZW5jaGFv,size_16,color_FFFFFF,t_70",
),
width: 300,
height: 300,
fit: BoxFit.fill),
),
])))));
}
运行一下这段代码,执行的效果如下: