Flutter之图片相关 2024-06-25 周二

1.本地图片

  • 需要在和lib平级的assets目录下建立相应的目录,分为1倍,2倍,3倍三个不同的区域


    图片资源目录
  • 图片资源选Android的,分为mdpi(1倍),xhdpi(2倍),xxhdpi(3倍)三种;下载之后重命名成相同的文件名(包括扩展名),然后分别放到不同的文件夹。


    资源下载
  • 然后用Android Studio的插件Flutter img sync在R文件中生产一下,就会在pubspec.yaml文件中完成注册,并且把字符串转化为全局变量。

  • 在使用的地方采用组件Image.asset()进行显示

              child: Image.asset(
                R.assetsImgAppleLogin,
                width: 42.w,
                height: 42.w,
                fit: BoxFit.cover,
              ),

其中资源全局变量的内容为static final String assetsImgAppleLogin = 'assets/img/apple_login.png';

2. 网络图片

  • Flutter系统提供的Image.network()可以用,但是很不好用,一般不会用它

  • 一般都会用插件cached_network_image来显示网络图片,点赞数很高

    图片插件

  • 这个图片插件会吃尽Flutter的内存,导致闪退,所以它推荐了配合使用的缓存插件flutter_cache_manager点赞数也是较高的。

    图片缓存插件

  • 由于用的地方很多,所以最好封装成一个组件,方便使用。并且url不合法的时候,会产生异常,导致页面白屏,所以在组件中增加对url合法性判断。

class NetworkImageWidget extends StatelessWidget {
  final String? url;
  final String? placeholder;
  final double? width;
  final double? height;
  final BoxFit fit;

  const NetworkImageWidget({
    super.key,
    this.url,
    this.width,
    this.height,
    this.placeholder,
    this.fit = BoxFit.cover,
  });

  @override
  Widget build(BuildContext context) {
    String actualUrl;
    bool isUrlExist;
    String actualPlaceholder;

    /// 判断url是否存在
    if ((url != null) && url!.isNotEmpty && (url!.length > 6)) {
      isUrlExist = true;
      actualUrl = url!;
    } else {
      isUrlExist = false;
      actualUrl = '';
    }

    /// url有可能以//开头,缺少https
    if (isUrlExist) {
      if (!actualUrl.startsWith('http')) {
        actualUrl = 'https:$actualUrl';
      }
    }

    /// 判断url是否有效
    bool isUrlValidate = false;
    if (isUrlExist) {
      Uri? uri = Uri.tryParse(actualUrl);
      if (uri != null) {
        var host = uri.host;
        if (host.isNotEmpty) {
          isUrlValidate = true;
        }
      }
    }

    /// 如果没有指定占位图,默认给一个
    actualPlaceholder = placeholder ?? R.assetsImg001;

    if (isUrlValidate) {
      return CachedNetworkImage(
        cacheManager: CacheManager(
          Config(
            "CachedNetworkImageKey",
            stalePeriod: const Duration(days: 3),
            maxNrOfCacheObjects: 200,
          ),
        ),
        imageUrl: actualUrl,
        placeholder: (context, url) => _buildPlaceholderImage(actualPlaceholder),
        errorWidget: (context, url, error) => _buildPlaceholderImage(actualPlaceholder),
        width: width,
        height: height,
        fit: fit,
        fadeOutDuration: const Duration(milliseconds: 100),
        fadeInDuration: const Duration(milliseconds: 100),
        // //占位符,根据加载的进度显示进度条
        placeholderFadeInDuration: const Duration(seconds: 3),
      );
    } else {
      return _buildPlaceholderImage(actualPlaceholder);
    }
  }

  /// 占位图
  Widget _buildPlaceholderImage(String name) {
    return Image.asset(
      name,
      width: width,
      height: height,
      fit: fit,
    );
  }
}

3. 从相册读取照片

通常在设置页面,从本地相册读取图片,上传到后台,作为头像。从相册读取照片,有一个插件file_picker,点赞数还是很多的。

读取相册照片

使用还是很方便的,就是提哦啊用一个方法,返回图片文件的本地路径

  void onPhotoClick() async {
    try {
      FilePickerResult? result = await FilePicker.platform.pickFiles(
        type: FileType.image,
        allowMultiple: false,
      );
      if (result != null) {
        List paths = result.paths;
        String imgPath = paths[0];
        if (imgPath.isNotEmpty) {
          LogUtil.log("图片路径:$imgPath");
          RouteUtils.openChangeAvatar(imgPath);
        }
      }
    } catch (e) {
      ToastUtil.showText(text: "读取图片文件出错:${e.toString()}");
    }
  }

会弹出从相册取照片的弹窗:


取照片弹窗

返回的是一个数组,是本地图片的路径,这次只取一张,所以取第1个元素就可以了。

/Users/zxs/Library/Developer/CoreSimulator/Devices/915800E9-32EF-49CF-9410-EDD34A1A7BA6/data/Containers/Data/Application/114A6865-58C6-48BF-B088-3554C295780F/tmp/IMG_0005-1719389214783.jpeg

得到这个图片路径之后,将这个字符串的path转化为图片File类型,然后使用Image.file组件在本地显示。

    /// 本地图片path转化为File结构
    imgPath = Get.arguments?["imgPath"] ?? "";
    if ((imgPath != null) && imgPath.isNotEmpty) {
      imgFile = File(imgPath);
      isDefaultAvatar = false;
      update();
    }

                     /// 在view中用 Image.file在本地显示图片
                     Image.file(
                        logic.imgFile,
                        width: ScreenUtil().screenWidth,
                        fit: BoxFit.cover,
                      ),

选择的照片可以大图显示,等待用户确认:


显示图片文件

4. 上传图片到阿里云

用户确认图片无误,点击“更换头像”按钮之后,接下来的操作应该是将本地图片上传到阿里云,然后后端返回用户新头像的URL,在设置页面显示用户的新头像。图片上传一般有两种做法:

  • 方案1:通过Dio的表单数据的方式上传图片文件,然后由后端转存阿里云
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
  'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
});
final response = await dio.post('/info', data: formData);

以前大多是这么做的,不过现在一般不这么做了,通过数据接口Post表单来传文件本身就不好,然后传到后端之后还要再传一次到阿里云,浪费严重。

  • 方案2:阿里云图片上传插件将照片上传到阿里云,得到图片的url,然后调用接口告诉后端图片的URL。
    上传图片到阿里云要用到相应的插件flutter_oss_aliyun,虽然点赞数很少,但是还是得用它。
    step1: 调用后台接口1,获得阿里云访问的临时参数,有accessKeyId,accessKeySecret,securityToken等等
    step2:调用插件的Client().putObjectFile的方法,将本地图片文件上传到阿里云,成功后会返回图片在阿里云的URL
    step3:调用后台接口2,高度后端图片文件在阿里云的url,完成前后端的信息同步。

5. 拍照片

从相册读取照片和用照相机拍摄照片,都可以用同一个插件image_picker;这个插件的点赞数也是很多的

拍照

取照片和拍照可以通过参数区分,可以封装成工具类。返回的结果XFile中有字符串的path属性,就是本地图片文件的路径,和上一个插件的是一样的。

class ImageUtil {
  /*
  * 从相机取图片
  */
  static Future getCameraImage() async {
    return await ImagePicker().pickImage(source: ImageSource.camera);
  }

  /*
  * 从相册取图片
  */
  static Future getGalleryImage() async {
    return await ImagePicker().pickImage(source: ImageSource.gallery);
  }
}

6. 二维码扫描

这个也有相应的插件qr_code_scanner; 点赞数也是很多的

二维码扫描

使用也很简单,它有现成的扫描界面,可以直接继承到页面中:

          child: QRView(
              key: qrKey,
              onQRViewCreated: _onQRViewCreated,
            ),

扫描后的响应写在自定义的监听函数中:

  void _onQRViewCreated(QRViewController controller) {
    this.controller = controller;
    controller.scannedDataStream.listen((scanData) {
      setState(() {
        result = scanData;
      });
    });
  }

7. 大图展示

普通的图片,使用Image.asset组件基本差不多了。不过还有一类需求,就是大图展示,放大缩小,平移等操作,自己写就比较麻烦了。这里有个插件photo_view就可以直接用。点赞数也是比较多的

大图展示

用的比较多的就是显示一组照片,可以做成一个工具组件:

import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';

@override
Widget build(BuildContext context) {
  return Container(
    child: PhotoViewGallery.builder(
      scrollPhysics: const BouncingScrollPhysics(),
      builder: (BuildContext context, int index) {
        return PhotoViewGalleryPageOptions(
          imageProvider: AssetImage(widget.galleryItems[index].image),
          initialScale: PhotoViewComputedScale.contained * 0.8,
          heroAttributes: PhotoViewHeroAttributes(tag: galleryItems[index].id),
        );
      },
      itemCount: galleryItems.length,
      loadingBuilder: (context, event) => Center(
        child: Container(
          width: 20.0,
          height: 20.0,
          child: CircularProgressIndicator(
            value: event == null
                ? 0
                : event.cumulativeBytesLoaded / event.expectedTotalBytes,
          ),
        ),
      ),
      backgroundDecoration: widget.backgroundDecoration,
      pageController: widget.pageController,
      onPageChanged: onPageChanged,
    )
  );
}

8. 保存图片到相册

从网络下载图片,并保存到相册,这个需求虽然不常见,但是也会有。这里有一个插件image_gallery_saver可以做这个事,虽然点赞数不是很多,但是确实有用

保存图片到相册

一般结合Dio一起使用,从网络下载图片,然后保存到相册:

  _saveNetworkImage() async {
    var response = await Dio().get(
        "https://ss0.baidu.com/94o3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=a62e824376d98d1069d40a31113eb807/838ba61ea8d3fd1fc9c7b6853a4e251f94ca5f46.jpg",
        options: Options(responseType: ResponseType.bytes));
    final result = await ImageGallerySaver.saveImage(
        Uint8List.fromList(response.data),
        quality: 60,
        name: "hello");
    print(result);
  }

9. 图片压缩

特别是iOS的照片,数据量很大,上传阿里云OSS之前最好压缩一下。阿里云默认限制最大的图片是20M,一般图片不要超过4M,不然的话就太大了,图片存储代价太高。
所以,压缩图片在某些时候还是需要的,这里有个现成的插件flutter_image_compress可以直接用。点赞数还是比较多的

图片压缩

使用也很简单:

  // 1. compress file and get Uint8List
  Future<Uint8List> testCompressFile(File file) async {
    var result = await FlutterImageCompress.compressWithFile(
      file.absolute.path,
      minWidth: 2300,
      minHeight: 1500,
      quality: 80,
    );
    print(file.lengthSync());
    print(result.length);
    return result;
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容