图片组件是Flutter基础组件之一,和文本组件一样必不可少。图片组件包含Image和Icon两个组件,本质上Icon不属于图片组件,但其外形效果上类似于图片。
在项目中建议优先使用Icon组件,Icon本质上是一种字体,只不过显示的不是文字,而是图标,而Image组件先通过图片解码器将图片解码,所以Icon有如下优点:
- 通常情况下,图标比图片体积更小,显著的减少App包体积。
- 图标不会出现失真或者模糊的现象,例如将20x20的图片,渲染在200x200的屏幕上,图片会失真或模糊,而图标是矢量图,不会失真,就像字体一样。
- 多个图标可以存放在一个文件中,方便管理。
- 全平台通用。
图片
https://api.flutter.dev/flutter/widgets/Image-class.html
Flutter中,我们可以通过Image组件来加载并显示图片,Image的数据源可以是asset、文件、内存以及网络。
ImageProvider
ImageProvider 是一个抽象类,主要定义了图片数据获取的接口load(),从不同的数据源获取图片需要实现不同的ImageProvider ,如AssetImage是实现了从Asset中加载图片的ImageProvider,而NetworkImage实现了从网络加载图片的ImageProvider。
Image
Image组件用于显示图片,图片的来源可以是网络、项目中图片或者设备上的图片。
Image组件有一个必选的image参数,它对应一个ImageProvider。
网络图片
Container(margin: EdgeInsets.only(top: 50, left: 50),width: 200.0,height: 200.0,color: Colors.grey,// child: Image(// fit: BoxFit.fill,// image: NetworkImage('https://lstatic.zuimeia.com/common/image/2020/11/3/cb418da0-d7a3-4b8c-829f-8cdf23ff1b08_4834_1450.jpeg'),// ),// Image也提供了一个快捷的构造函数Image.network用于从网络加载、显示图片:child: Image.network('https://lstatic.zuimeia.com/common/image/2020/11/3/cb418da0-d7a3-4b8c-829f-8cdf23ff1b08_4834_1450.jpeg',fit: BoxFit.fill,),);

本地图片
1、将图片拷贝到项目中,通常情况下,拷贝到 assets/images/ 目录下,assets/images/ 目录为手动创建,新建的项目默认是没有此目录的。
2、设置 pubspec.yaml 配置文件
flutter:assets:- assets/images/# 或者指定具体图片的名称:- assets/images/1.jpg
注意: 由于 yaml 文件对缩进严格,所以必须严格按照每一层两个空格的方式进行缩进,此处assets前面应有两个空格。
3、加载图片
Container(margin: EdgeInsets.only(top: 50, left: 50),width: 200.0,height: 200.0,color: Colors.grey,// child: Image(// image: AssetImage('assets/images/1.jpg'),// ),// Image也提供了一个快捷的构造函数Image.asset用于从asset中加载、显示图片:child: Image.asset('assets/images/1.jpg'),);

加载设备上的图片
要加载设备(手机)上的图片首先需要获取设备图片的路径,由于不同平台的路径不同,因此路径的获取必须依靠原生支持,如果了解原生(Android和iOS)开发,可以直接使用MethodChannel获取路径,如果不懂原生(Android和iOS)开发,可以使用第三方插件获取路径,这里推荐官方的 path_provider。
Image.file(File('path'))
Image组件参数
Image 在显示图片时定义了一系列参数,通过这些参数我们可以控制图片的显示外观、大小、混合效果等。
alignment对齐方式width、height用于设置图片的宽、高,当不指定宽高时,图片会根据当前父容器的限制,尽可能的显示其原始大小,如果只设置width、height的其中一个,那么另一个属性默认会按比例缩放,但可以通过fit属性来指定适应规则。fit该属性用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。适应模式是在BoxFit中定义,它是一个枚举类型,有如下值:fit: BoxFit.fillfill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。cover:会按图片的长宽比放大后居中填满显示空间,图片不会变形,超出显示空间部分会被剪裁。contain:这是图片的默认适应规则,图片会在保证图片本身长宽比不变的情况下缩放以适应当前显示空间,图片不会变形。fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。scaleDown:当组件比图片小时,图片等比缩小,效果和contain一样。

repeat当图片本身大小小于显示空间时,指定图片的重复规则。- repeat : 横向和纵向都进行重复,直到铺满整个画布。
- repeatX: 横向重复,纵向不重复。
- repeatY:纵向重复,横向不重复。
- noRepeat:不重复。

Image.asset('assets/images/1.jpg',repeat: ImageRepeat.repeatY,),
color、colorBlendMode:在图片绘制时可以对每一个像素进行颜色混合处理,color指定混合色,而colorBlendMode指定混合模式。Image.asset('assets/images/1.jpg',color: Colors.blue,colorBlendMode: BlendMode.difference,),

下面介绍的混合模式比较多,浏览一遍即可,此属性可以用于简单的滤镜效果。
- clear:清楚源图像和目标图像。
- color:获取源图像的色相和饱和度以及目标图像的光度。
- colorBurn:将目标的倒数除以源,然后将结果倒数。
- colorDodge:将目标除以源的倒数。
- darken:通过从每个颜色通道中选择最小值来合成源图像和目标图像。
- difference:从每个通道的较大值中减去较小的值。合成黑色没有效果。合成白色会使另一张图像的颜色反转。
- dst:仅绘制目标图像。
- dstATop:将目标图像合成到源图像上,但仅在与源图像重叠的位置合成。
- dstIn:显示目标图像,但仅显示两个图像重叠的位置。不渲染源图像,仅将其视为蒙版。源的颜色通道将被忽略,只有不透明度才起作用。
- dstOut:显示目标图像,但仅显示两个图像不重叠的位置。不渲染源图像,仅将其视为蒙版。源的颜色通道将被忽略,只有不透明度才起作用。
- dstOver:将源图像合成到目标图像下。
- exclusion:从两个图像的总和中减去两个图像的乘积的两倍。
- hardLight:调整源图像和目标图像的成分以使其适合源图像之后,将它们相乘。
- hue:获取源图像的色相,以及目标图像的饱和度和光度。
- lighten:通过从每个颜色通道中选择最大值来合成源图像和目标图像。
- luminosity:获取源图像的亮度,以及目标图像的色相和饱和度。
- modulate:将源图像和目标图像的颜色分量相乘。
- multiply:将源图像和目标图像的分量相乘,包括alpha通道。
- overlay:调整源图像和目标图像的分量以使其适合目标后,将它们相乘。
- plus:对源图像和目标图像的组成部分求和。
- saturation:获取源图像的饱和度以及目标图像的色相和亮度。
- screen:将源图像和目标图像的分量的逆值相乘,然后对结果求逆。
- softLight:对于低于0.5的源值使用colorDodge,对于高于0.5的源值使用colorBurn。
- src:放置目标图像,仅绘制源图像。
- srcATop:将源图像合成到目标图像上,但仅在与目标图像重叠的位置合成。
- srcIn:显示源图像,但仅显示两个图像重叠的位置。目标图像未渲染,仅被视为蒙版。目标的颜色通道将被忽略,只有不透明度才起作用。
- srcOut:显示源图像,但仅显示两个图像不重叠的位置。
- srcOver:将源图像合成到目标图像上。
- xor:将按位异或运算符应用于源图像和目标图像。
各个属性的效果:

matchTextDirection设置为true时,图片的绘制方向为TextDirection设置的方向,其父组件必须为Directionality。

Directionality(textDirection: TextDirection.rtl,child: Wrap(spacing: 5,direction: Axis.horizontal,children: [Text('你好'),Image.asset('assets/images/1.jpg',height: 150,matchTextDirection: true, //左图为true,显示镜像;右图为false,显示原图),],),),
filterQuality表示绘制图像的质量,从高到低为:high->medium->low->none。越高效果越好,越平滑,当然性能损耗越大,默认是low,如果发现图片有锯齿,可以设置此参数。frameBuilder当加载图片的时候回调frameBuilder,当此参数为null时,此控件将会在图片加载完成后显示,未加载完成时显示空白,尤其在加载网络图片时会更明显。因此此参数可以用于处理图片加载时显示占位图片和加载图片的过渡效果,比如淡入淡出效果。
示例:淡入淡出效果:

Container(width: double.infinity,height: 200,color: Colors.grey,child: Image.network('https://images.h128.com/upload/202012/20/202012201302083641.jpg',fit: BoxFit.contain,frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {if (wasSynchronouslyLoaded) {return child;}return AnimatedOpacity(child: child,opacity: frame == null ? 0 : 1,duration: const Duration(seconds: 2),curve: Curves.easeOut,);},),),
loadingBuilder比frameBuilder控制的力度更细,可以获取图片加载的进度。示例:

Container(width: double.infinity,height: 200,color: Colors.grey,child: Image.network('https://images.h128.com/upload/202012/20/202012201302083641.jpg',fit: BoxFit.contain,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,),);},),),
封装图片loading
import 'package:flutter/material.dart';class LoadingImage extends StatelessWidget {final src; //图片路径final isNetwork; //是否网络图片final fit; //图片填充方式LoadingImage(this.src, {key,this.isNetwork: true,this.fit: BoxFit.fill,}) : super(key: key);@overrideWidget build(BuildContext context) {if (this.isNetwork == true) {return Image.network(this.src,fit: this.fit,loadingBuilder: (context, child, progress) {if (progress == null) return child;return Container(color: Colors.white,child: Center(child: CircularProgressIndicator(value: progress.expectedTotalBytes != null? progress.cumulativeBytesLoaded / progress.expectedTotalBytes: null,),),);},);} else {return Container();}}}
centerSlice用于拉伸图片的特定区域,centerSlice设置的区域(Rect)就是拉伸的区域。通常用于控件大小、宽高比不固定的场景,比如聊天背景图片等。Container(width: 250,height: 300,decoration: BoxDecoration(image: DecorationImage(centerSlice: Rect.fromLTWH(20, 20, 10, 10),image: AssetImage('assets/images/abc.jpg'),fit: BoxFit.fill,),),),

上面为原图,下面为拉伸的图片。
在使用时大概率会出现如下异常:
这是由于图片比组件的尺寸大,如果使用centerSlice属性,图片必须比组件的尺寸小,一般情况下,图的尺寸都非常小。
Image缓存
Flutter框架对加载过的图片是有缓存的(内存),默认最大缓存数量是1000,最大缓存空间为100M。
应用例子
圆形图片

Row(children: [Container(width: 50.0,height: 50.0,decoration: BoxDecoration(image: DecorationImage(image: NetworkImage('https://www.bugclose.com/oss/27/42/b9/001e190a04c6.jpg')),borderRadius: BorderRadius.circular(50),),),Container(width: 50.0,height: 50.0,decoration: BoxDecoration(image: DecorationImage(image: NetworkImage('https://www.bugclose.com/oss/27/42/b9/001e190a04c6.jpg')),shape: BoxShape.circle, //circle 圆形; rectangle 矩形),),Container(child: ClipOval(child: Image.network('https://www.bugclose.com/oss/27/42/b9/001e190a04c6.jpg',width: 50.0,height: 50.0,),),),Container(width: 50.0,height: 50.0,child: CircleAvatar(backgroundImage: NetworkImage('https://www.bugclose.com/oss/27/42/b9/001e190a04c6.jpg'),),),],),
聊天气泡
背景图
Container(width: double.infinity,height: 200,decoration: BoxDecoration(color: Colors.grey,image: DecorationImage(centerSlice: Rect.fromLTWH(20, 20, 1, 1),image: NetworkImage('https://lstatic.zuimeia.com/common/image/2020/12/24/3596c39c-1e69-4be1-a8b1-24ba27a89515_57_80.png'),fit: BoxFit.fill,),),child: Text('人人都知道的,我国被卡脖子最严重的领域是芯片。我发现,非常多的人低估了这个挑战的难度,经常可以见到许多错误理解。例如,有人认为中国在两年内就可以造出高端芯片实际上,这是个大大过头的估计。目前我们没法给出个时间表。如果我们能在十年内造出现在最高水平的芯片,就是很大的成功了。又如,????????????????????????经常有人说“小小的芯片”,难道我们连小小的芯片都搞不定吗?'),),
Chip 徽章
https://api.flutter.dev/flutter/material/Chip-class.html
标签,一个Material widget。 它可以将一个复杂内容实体展现在一个小块中,如联系人。
Chip(avatar: CircleAvatar(backgroundColor: Colors.green,child: Text('A'),),label: Text('我是 A'),),
Icon
Flutter中,可以像Web开发一样使用iconfont,iconfont即“字体图标”,它是将图标做成字体文件,然后通过指定不同的字符而显示不同的图片。
在字体文件中,每一个字符都对应一个位码,而每一个位码对应一个显示字形,不同的字体就是指字形不同,即字符对应的字形是不同的。而在iconfont中,只是将位码对应的字形做成了图标,所以不同的字符最终就会渲染成不同的图标。
在Flutter开发中,iconfont和图片相比有如下优势:
- 体积小:可以减小安装包大小。
- 矢量的:iconfont都是矢量图标,放大不会影响其清晰度。
- 可以应用文本样式:可以像文本一样改变字体图标的颜色、大小对齐等。
- 可以通过TextSpan和文本混用。
使用 Material Design 字体图标
Flutter默认包含了一套Material Design的字体图标,在pubspec.yaml文件中的配置如下:
flutter:uses-material-design: true
Material Design所有图标可以在其官网查看:https://material.io/tools/icons/
我们看一个简单的例子:
String icons = "";// accessible:  or 0xE914 or E914icons += "\uE914";// error:  or 0xE000 or E000icons += " \uE000";// fingerprint:  or 0xE90D or E90Dicons += " \uE90D";Text(icons,style: TextStyle(fontFamily: "MaterialIcons",fontSize: 24.0,color: Colors.green),);
运行效果如图3-21所示:
通过这个示例可以看到,使用图标就像使用文本一样,但是这种方式需要我们提供每个图标的码点,这并对开发者不友好,所以,Flutter封装了IconData和Icon来专门显示字体图标,上面的例子也可以用如下方式实现:
Row(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.account_balance),Icon(Icons.star, color: Colors.blue),Icon(Icons.camera_enhance, size: 60),],);

Icons类中包含了所有Material Design图标的IconData静态变量定义。
使用阿里矢量图标库 iconfont
我们也可以使用自定义字体图标。iconfont.cn上有很多字体图标素材,我们可以选择自己需要的图标打包下载后,会生成一些不同格式的字体文件,在Flutter中,我们使用ttf格式即可。
假设我们项目中需要使用一个书籍图标和微信图标,我们打包下载后导入:
1、导入字体图标文件;这一步和导入字体文件相同,假设我们的字体图标文件保存在项目根目录下,路径为”fonts/iconfont.ttf”:
flutter:fonts:- family: myIcon #指定一个字体名fonts:- asset: assets/fonts/iconfont.ttf
2、定义一个MyIcons类,功能和Icons类一样:将字体文件中的所有图标都定义成静态变量:
class MyIcons{// message_allread 图标static const IconData message_allread = const IconData(0xe6e2,fontFamily: 'myIcon',matchTextDirection: true,);// 微信图标static const IconData wechat = const IconData(0xec7d,fontFamily: 'myIcon',matchTextDirection: true);}
3、使用
Row(mainAxisAlignment: MainAxisAlignment.center,children: [// 使用 Material design 图标Icon(Icons.account_balance),Icon(Icons.star, color: Colors.blue),Icon(Icons.camera_enhance, size: 60),// 引用 MyIcons 类里封装好的Icon(MyIcons.message_allread),// 直接使用 16进制的图标码点Icon(IconData(0xe6e2, fontFamily: 'myIcon')),],);

使用 fontawesome
中文网:http://www.fontawesome.com.cn/
英文网:https://fontawesome.com/
除了Icons,还有国外比较流行了 fontawesome 可以使用,已经有人做了个包了,依赖一下font_awesome_flutter这个包,使用也比较简单:
icon: new Icon(FontAwesomeIcons.gamepad),
