<
>

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

2023-02-13 15:06:59 来源:易采站长站 作者:

目录正文使用网络图片把网络图片缓存到磁盘使用assets图片适配浅色与深色模式在不同的设备使用不同分辨率的图片关于设备dpr不完全匹配的处理忽略dpr信息使用相册图片使用相机拍摄的图片使用...

目录
正文
使用网络图片
把网络图片缓存到磁盘
使用 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!)
      ],
    );
  }
}

maxWidthmaxHeight 最好设置一下,从源头上控制一下图片的大小,提高效率。如果这里没控制大小,就必须让 Image.file 加上 cacheWidthcacheHeight参数,因为用 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 图片开发的资料请关注我们其它相关文章!

暂时禁止评论

微信扫一扫

易采站长站微信账号