Flutter 图片开发核心技能快速掌握教程

2023-12-07 0 897
目录
  • 正文
  • 使用网络图片
    • 把网络图片缓存到磁盘
  • 使用 assets 图片
    • 适配浅色与深色模式
    • 在不同的设备使用不同分辨率的图片
    • 关于设备 dpr 不完全匹配的处理
    • 忽略 dpr 信息
  • 使用相册图片
    • 使用相机拍摄的图片
      • 使用内存图片
        • 图片用做装饰
          • 图片预加载
            • centerSlice
              • centerSlice 只能放大,不能缩小。
            • 全局缓存 ImageCache 的设置
              • 图片类之间的关系
                • ImageProvider
                • obtainKey(ImageConfiguration) 方法
                • resolve(ImageConfiguration)方法

              正文

              在 Flutter 中使用图片是最基础能力之一。作为春节开工后的第一篇文章,17 做了精心准备,满满的都是干货!本文

              介绍如何在 Flutter 中使用图片,尽量详细,示例完整,包会!

              使用网络图片

              使用网络图片超级简单,直接给出网络地址就行,本例运行后,显示的是一张猫头鹰的图片。

              Flutter 图片开发核心技能快速掌握教程

              完整代码,贴到 main.dart 就能用。后面的代码只给出 image 相关的。

              import \’package:flutter/material.dart\’;
              void main() {
              runApp(const MyApp());
              }
              class MyApp extends StatelessWidget {
              const MyApp({super.key});
              final imageSrc =
              \’https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/04ec6088c3c544a2b9459582e335483c~tplv-k3u1fbpfcp-watermark.image?\’;
              @override
              Widget build(BuildContext context) {
              return MaterialApp(
              home: Scaffold(
              body: Center(child: Image.network(imageSrc)),
              ));
              }
              }

              图片加载成功后,不管 http 请求头如何,都会被缓存起来,下次请求这个图片会直接从内存中读取。

              一般我们需要指定图片的宽度和高度,让它以指定的尺寸显示,避免图片过大撑破布局。

              Image.network(imageSrc,width: 100,height: 100,)

              如果提供了 cacheWidth 或 cacheHeight,则指示引擎应以指定大小解码图像。无论这些参数如何,图像都将根据约束进行渲染。cacheWidth 和 cacheHeight 主要是为了减少 ImageCache 的内存使用。

              cacheWidth 和 cacheHeight 是为了优化内存用的,如果你能确定网络图片的尺寸都是合适的尺寸,就不用设置这两个参数。如果不能保证来源图片的尺寸,比如可能有大尺寸的图片,最好设置这两个参数。这两个参数只能优化内存占用,对下载和解码没有帮助。如果要优化下载,需要把图片缓存在磁盘上,下次直接从磁盘读取,就像 web 缓存那样。

              把网络图片缓存到磁盘

              我们可以用 cached_network_image 这个插件实现把网络图片缓存到磁盘这个功能。

              安装插件

              flutter pub add cached_network_image

              必须的参数只有一个 imageUrl。

              MaterialApp(
              home: Scaffold(
              body: Center(child: CachedNetworkImage(
              imageUrl: imageSrc,
              )),
              ));

              cached_network_image 自带 fadeIn 的效果,在图片加载过程中显示 placeholder,出现错误,显示 errorWidget。

              CachedNetworkImage(
              imageUrl: imageSrc,
              placeholder: (context, url) => CircularProgressIndicator(),
              errorWidget: (context, url, error) => Icon(Icons.error),
              )

              有时我们需要把图片应用到其它 widget ,比如用在 BoxDecoration 中,这时需要提供 imageProvider。

              CachedNetworkImage(
              imageUrl: imageSrc,
              imageBuilder: (context, imageProvider) => Container(
              decoration: BoxDecoration(
              image: DecorationImage(
              image: imageProvider,
              fit: BoxFit.cover,
              colorFilter:
              ColorFilter.mode(Colors.red, BlendMode.colorBurn)),
              ),
              ),
              placeholder: (context, url) => CircularProgressIndicator(),
              errorWidget: (context, url, error) => Icon(Icons.error),
              ),

              还有很多参数,可以 在文档中查看

              使用 assets 图片

              assets 也可以叫做资源。资源是与您的应用程序一起捆绑和部署的文件,可在运行时访问。常见的资源类型包括静态数据(例如 JSON 文件)、配置文件、图标和图像(JPEG、WebP、GIF、动画 WebP/GIF、PNG、BMP 和 WBMP)。

              每个资源都由资源文件所在的显式路径(相对于 pubspec.yaml 文件)标识。声明资源的顺序无关紧要。包含资源的目录名称无关紧要。

              在构建过程中,Flutter 将资源放入一个名为资源包的特殊存档中,应用程序会在运行时从中读取。

              Flutter 使用位于项目根目录的 pubspec.yaml 文件来识别应用程序所需的资源。

              资源文件夹的名称是随意的,我们可以把资源文件夹放在和 lib 平级的根目录下面,为图片建立文件夹 images,把上面示例中的猫头鹰图片放入其中。

              Flutter 图片开发核心技能快速掌握教程

              修改 pubspec.yaml 的配置。

              flutter:
              assets:
              – images/owl.png

              注意空格

              在代码中可以通过 images/owl.png 使用图片。

              Image.asset(
              \’images/owl.png\’,
              width: 200,
              height: 200,
              );

              运行,成功显示了猫头鹰的图片。当你发布应用程序的时候,pubspec.yaml 中配置的图片会和代码一起打包发布。

              如果有很多图片,这样一张一张注册很是麻烦,我们可以直接指定文件夹。比如我们可以一次性注册 images 文件夹下面的所有图片。

              flutter:
              assets:
              – images/

              适配浅色与深色模式

              正常情况下,我们用的是浅色模式,在弱光环境下,打开深色模式可获得出色的视觉体验。

              构建过程支持资源变体的概念:可以在不同上下文中显示资源的不同版本。当在 pubspec.yaml 中指定资源路径时,构建过程会在相邻子目录中查找任何具有相同名称的文件。然后,此类文件与指定资源一起包含在资源包中。

              在 images 下面增加 dark 文件夹,增加在深色模式下使用的与浅色文件同名的图片。17 的电脑屏幕截图:

              Flutter 图片开发核心技能快速掌握教程

              适配浅色与深色模式的工作就完成了!

              images/owl.png 和 images/dark/owl.png 都包含在您的资源包中。前者被视为主要资源,而后者被视为变体。在浅色模式下,Flutter 为我们显示显示 images/owl.png ,在深色模式下显示 images/dark/owl.png。

              在不同的设备使用不同分辨率的图片

              Flutter 可以根据当前设备像素比加载分辨率合适的图像。

              在 image 文件夹下增加 2.0x,3.0x文件夹,放入同名的高分辨率的图片。17 的电脑屏幕的截图:

              Flutter 图片开发核心技能快速掌握教程

              1.5x 文件夹也是合法的。

              适合不同设备分辨率的工作就完成了!

              images/2.0x/owl.png 和 images/3.0x/owl.png 都包含在您的资源包中,都被视为变体。 flutter 会自动为我们在 dpr 为 2 的设备上使用 images/2.0x/owl.png 在 dpr 为 3 的设备上使用 images/3.0x/owl.png,在 dpr 为 1 设备上使用 images/owl.png。 images/owl.png 相当于是 images/1.0x/owl.png。

              dpr 为设备分辨率(device pixel ratio)英文单词的首字母

              还是一样的代码,现在可以适配不同 dpr 的设备!

              Image.asset(
              \’images/owl.png\’,
              width: 200,
              height: 200,
              );

              关于设备 dpr 不完全匹配的处理

              Flutter 以 dpr 2.0 为界,采用不同的处理方案,目的是为了得到更好的体验。

              • 2.0 以下的设备匹配分辨率更高的图片
              • 2.0 以上的设备匹配分辨率最接近的图片

              比如有一个 dpr 为 1.25 的设备,会采用 2.0 的图片,而不是 1.0的图片。再比如有一个 dpr 为 2.25 的设备,会采用 2.0 的图片,而不会采用 3.0 的图片。

              忽略 dpr 信息

              如果要忽略 dpr 信息直接读取主资源(就是不带 x.0路径的那个),用 ExactAssetImage。可以指定 scale,默认为 1.0。

              如果 scale 为 2.0,则意味着每个逻辑像素对应四个图像像素。看下实际的效果就明白了。

              Flutter 图片开发核心技能快速掌握教程

              Column(
              mainAxisSize: MainAxisSize.min,
              children: [
              Image(image: ExactAssetImage(\’images/owl.png\’, scale: 5)),
              Image(image: ExactAssetImage(\’images/owl.png\’, scale: 10)),
              ],
              )

              scale 越大,图片显示的越小,因为 scale 越大,每个逻辑像素对应的图像像素就越多。

              逻辑像素,也叫设备独立像素(device independent pixels),简称dip,与具体设备无关。

              使用相册图片

              先安装插件

              flutter pub add image_picker

              使用相册图片需要两步

              • 使用 image_picker 插件从相册中读取图片
              • 使用 Image.file 展示图片

              class MyWidget extends StatefulWidget {
              const MyWidget({super.key});
              @override
              State<MyWidget> createState() => _MyWidgetState();
              }
              class _MyWidgetState extends State<MyWidget> {
              final _picker = ImagePicker();
              File? _file;
              @override
              Widget build(BuildContext context) {
              return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
              ElevatedButton(
              onPressed: () async {
              var xfile = await _picker.pickImage(
              source: ImageSource.gallery,
              maxWidth: 200,
              maxHeight: 300,
              requestFullMetadata: false);
              if (xfile != null) {
              setState(() {
              _file = File(xfile.path);
              });
              }
              },
              child: const Text(\’从相册中选择图片\’)),
              if (_file != null) Image.file(_file!)
              ],
              );
              }
              }

              maxWidth 和 maxHeight 最好设置一下,从源头上控制一下图片的大小,提高效率。如果这里没控制大小,就必须让 Image.file 加上 cacheWidth,cacheHeight参数,因为用 Image.file 显示的图片也会缓存起来,需要控制缓存的图片大小,减少内存消耗。

              requestFullMetadata: false 是为了避免 ios 闪退。requestFullMetadata: false 表示要请求完整的 metadata,需要在 info.plist( ios / Runner 下面 )中申请权限。

              打开 info.plist 的 dict 中加入如下内容就可以了。

              <key>NSPhotoLibraryUsageDescription</key>
              <string>APP需要您的同意,才能使用相册,以便于上传,发布照片</string>

              android 不需要申请权限。

              使用相机拍摄的图片

              和使用相册图片步骤一样,有两点不同

              • source: ImageSource.gallery 修改为 source: ImageSource.camera
              • 申请相机的权限

              android 不需要申请权限,直接可以使用相机,ios 需要 打开 info.plist 在 dict 中加入如下内容

              <key>NSCameraUsageDescription</key>
              <string>APP需要您的同意,才能使用摄像头,以便于相机拍摄,上传、发布照片</string>

              使用内存图片

              Image.memory 的必选参数 bytes 是 Uint8List 类型,base64Decode 的返回值正好是 Uint8List,我们用 Image.memory 展示一下 base64 格式的图片。

              我们得到的 base64格式的图片可能是这样的

              imageString = \’image/jpeg;base64,/9j/4AA…\’

              把 image/jpeg;base64, 删除,只保留后面的数据,这样才能正常显示。

              引用 dart:convert 把 imageString 用 base64Decode 转成 Uint8List 类型,Image.memory 就能显示了。

              import \’dart:convert\’;
              Image.memory(base64Decode(imageString));

              图片用做装饰

              DecoratedBox 是专门用来做装饰的 widget

              DecoratedBox(
              decoration: BoxDecoration(
              image: DecorationImage(image: AssetImage(\’images/owl.png\’)),
              ),
              child: SizedBox(
              width: 100,
              height: 100,
              ),
              )

              更多时候,我们可以用 Container。

              Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
              image: DecorationImage(image: AssetImage(\’images/owl.png\’))
              ),
              )

              DecorationImage 的 image 参数类型是 ImageProvider,ImageProvider 的子类都可以用作参数。除了 AssetImage,还可以用 FileImage,MemoryImage,NetworkImage。

              图片预加载

              在网页中的轮播图中我们一般都会做图片的预加载,用 js 预加载图片,避免图片在轮播时无法显示。Flutter 中也有轮播图,我们也可以做类似的事情。

              和 js 预加载一样,Flutter 预加载图片也是很简单的。

              preload(BuildContext context) {
              var configuration = createLocalImageConfiguration(context);
              for (var src in [\’图片地址1\’, \’图片地址2\’, \’图片地址13\’]) {
              NetworkImage(src).resolve(configuration);
              }
              }

              图片什么时候加载完成不用管。Flutter 会用 NetWorkImage 做 key,缓存图片,下次用 NetworkImage 加载同样的图片,无论是否加载完成,都不会再次加载。

              resolve 的作用就是把加载的工作提前执行。

              判断两个 NetWorkImage 相同,需要 url,scale 都相同,所以如果如果 scale 不同,会触发重新加载。

              bool operator ==(Object other) {
              if (other.runtimeType != runtimeType) {
              return false;
              }
              return other is NetworkImage
              && other.url == url
              && other.scale == scale;
              }

              centerSlice

              centerSlice 用来切 9图的。比如下面这张图片,我们用 9 图的方式来切图。

              Flutter 图片开发核心技能快速掌握教程

              这张图是300 x 300 的图,把它显示成 400 x 400。我们把它分成 9 个区域。

              • 区域 5 会被垂直水平拉伸
              • 区域 4,6 被垂直拉伸
              • 区域 2,8 被水平拉伸
              • 1,3,7,9 保持原样

              Flutter 图片开发核心技能快速掌握教程

              Column(
              mainAxisSize: MainAxisSize.min,
              children: [
              Image.asset(\”images/btn.png\”,width: 300,height: 300,fit: BoxFit.fill,),
              Image.asset(\”images/btn.png\”,
              centerSlice: Rect.fromLTRB(100, 100, 200,200),width: 400,height:400,scale: 1,)
              ],
              );

              根据 9 图的特性,我们可以把按钮的背景图做 9 图,用来容纳可变化的字数,也可以把聊天用的气泡做成 9 图。

              centerSlice 只能放大,不能缩小。

              原图是 300 x 300, centerSlice 处理后的图只能是宽比 300 大,高也比300 大,否则报错。

              全局缓存 ImageCache 的设置

              ImageCache 有两个属性

              • maximumSize 可以获取和设置可以缓存的图片的最大数量,默认 1000 张。
              • maximumSizeBytes 可以获取和设置可以缓存的图片的最大容量,默认 100M。

              既然这两个属性可以让我们设置,就说明在有的时候,这两个属性的默认值是不合适的。我们可以通过 PaintingBinding.instance.imageCache 拿到全局 ImageCache 的实例,通过PaintingBinding.instance.imageCache.currentSize 监控一下当前已经缓存的图片数量,如果经常达到最大值,说明默认值太小,可以设置的更大些。

              maximumSize 设置的数量调高需要考虑到硬件的承受能力。如果硬件条件差,反而会适得其反。这个时候非但不能加大设置,还需要减小设置。

              图片类之间的关系

              如果只是想会用 Flutter Image,上面的内容就够用了,可以跳过后面的内容。

              上面讲了很多,都是零散的,涉及到的类很多,难免有混乱之感,所以需要对他们之间的关系梳理一下。

              Image widget 是直接给我们用的。抛开那些命名构造函数,如果我们直接用 Image,只有一个必须的参数 image,image 类型是 ImageProvider。

              ImageProvider

              abstract class ImageProvider<T> {
              ImageStream resolve(ImageConfiguration configuration) {
              // 省略
              }
              Future<bool> evict({ ImageCache cache,
              ImageConfiguration configuration = ImageConfiguration.empty }) async {
              // 省略
              }
              Future<T> obtainKey(ImageConfiguration configuration);
              @protected
              ImageStreamCompleter load(T key); // 需子类实现
              }

              obtainKey(ImageConfiguration) 方法

              该接口主要是为了配合实现图片缓存,不同的 key 代表不同的图片数据缓存。ImageProvider 从数据源加载完数据后,会在全局的 ImageCache 中缓存图片。

              resolve(ImageConfiguration)方法

              ImageStream resolve(ImageConfiguration configuration) {
              … //省略
              final ImageStream stream = ImageStream();
              T obtainedKey; //
              //定义错误处理函数
              Future<void> handleError(dynamic exception, StackTrace stack) async {
              … //省略
              stream.setCompleter(imageCompleter);
              imageCompleter.setError(…);
              }
              // 创建一个新Zone,为了当发生错误时不会干扰 MainZone
              final Zone dangerZone = Zone.current.fork(…);
              dangerZone.runGuarded(() {
              Future<T> key;
              // 先验证是否已经有缓存
              try {
              // 生成缓存key,后面会根据此key来检测是否有缓存
              key = obtainKey(configuration);
              } catch (error, stackTrace) {
              handleError(error, stackTrace);
              return;
              }
              key.then<void>((T key) {
              obtainedKey = key;
              // 缓存的处理逻辑
              final ImageStreamCompleter completer = PaintingBinding.instance
              .imageCache.putIfAbsent(key, () => load(key), onError: handleError);
              if (completer != null) {
              stream.setCompleter(completer);
              }
              }).catchError(handleError);
              });
              return stream;
              }

              resolve 是 ImageProvider 对外暴露的主要方法,我们可以调用这个方法来加载图片,Image Widget 也是调用这个方法加载图片。

              要从 ImageProvider 获取 ImageStream,调用 resolve 并向其传递一个 ImageConfiguration 对象。 ImageProvider 通过 obtainKey 获得 Key 并使用全局的 imageCache 缓存图片。

              类型参数 T 是用于表示已解析配置的对象的类型。这也是图像缓存中用于键的类型。它应该是不可变的并实现 == 运算符和 hashCode getter。

              AssetBundleImageProvider,FileImage,MemoryImage,NetworkImage

              这四个都是 ImageProvider 的子类,AssetBundleImageProvider 又有两个子类,AssetImage 和 ExactAssetImage,这两个和 FileImage,MemoryImage,NetworkImage 都可以直接给 image 参数赋值。

              比如我们要读取 owl.png

              Image(image: AssetImage(\”image/owl.png\”),);
              Image.asset(\”image/owl.png\”);

              这两种都能显示 owl.png。那么有什么区别呢?我们看下 Image.asset 构造函数的源码

              Image.asset(
              String name, {
              省略…
              }) : image = ResizeImage.resizeIfNeeded(
              cacheWidth,
              cacheHeight,
              scale != null
              ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
              : AssetImage(name, bundle: bundle, package: package),
              ),
              省略…
              ;

              Image.asset 构造函数为我们创建了 ImageProvider。

              • 如果 scale 不为空,创建 ExactAssetImage,否则创建 AssetImage
              • 用 ResizeImage.resizeIfNeeded 包起来。

              ResizeImage.resizeIfNeeded 执行下面的逻辑:如果 cacheWidth,cacheHeight 同时为空,直接返回原 ImageProvider,否则返回 ResizeImage。

              static ImageProvider<Object> resizeIfNeeded(int? cacheWidth, int? cacheHeight, ImageProvider<Object> provider) {
              if (cacheWidth != null || cacheHeight != null) {
              return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
              }
              return provider;
              }

              ResizeImage 在放进缓存之前,会根据 cacheWidth,cacheHeight 对图片做优化,这对于减少内存开销有帮助。

              从以上可以看出,没有特殊需要,我们都使用 Image.asset、Image.network、Image.file、age.memory,这四个命名构造函数。

              ImageProvider 的子类还有一个 ScrollAwareImageProvider, RawImage 会调用他避免在快速滚动时加载图像。我们一般不需要直接使用他。

              以上就是Flutter 图片开发核心技能快速掌握教程的详细内容,更多关于Flutter 图片开发的资料请关注悠久资源网其它相关文章!

              您可能感兴趣的文章:

              • Flutter加载图片流程MultiFrameImageStreamCompleter解析
              • Flutter加载图片流程之ImageCache源码示例解析
              • Flutter加载图片的多样玩法汇总
              • 详解Flutter网络图片本地缓存的实现
              • flutter图片组件核心类源码解析
              • Flutter图片与文件选择器使用实例
              • flutter中的资源和图片加载示例详解
              • Flutter图片缓存管理ImageCache原理分析
              • Flutter加载图片流程之ImageProvider源码示例解析

              收藏 (0) 打赏

              感谢您的支持,我会继续努力的!

              打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
              点赞 (0)

              悠久资源 Dart Flutter 图片开发核心技能快速掌握教程 https://www.u-9.cn/biancheng/dart/117301.html

              常见问题

              相关文章

              发表评论
              暂无评论
              官方客服团队

              为您解决烦忧 - 24小时在线 专业服务