阅读视图

发现新文章,点击刷新页面。

Flutter-使用Gal展示和保存图片资源

Gal 是 Flutter 生态中一款轻量、高性能的图片管理与预览插件,专为简化 Flutter 应用中图片选择、预览、保存等核心场景设计。它封装了原生平台的图片处理能力,提供统一的 API 接口,让开发者无需关注 iOS/Android 底层差异,快速实现专业级的图片交互体验。

1. Gal 插件核心功能

Gal 插件的核心价值在于跨平台一致性易用性,主要覆盖以下场景:

  1. 图片预览:支持单张/多张图片的沉浸式预览,包含缩放、滑动切换、手势返回等交互;
  2. 相册操作:读取设备相册、筛选图片/视频、获取图片元信息(尺寸、路径、创建时间);
  3. 图片保存:将网络图片/本地图片保存到系统相册,自动处理权限申请;
  4. 权限管理:封装相册读写权限的申请与状态检测,适配 iOS/Android 权限机制差异;
  5. 性能优化:内置图片懒加载、内存缓存策略,避免大图集加载时的卡顿问题。

2. 核心 API 与属性详解

2.1. 基础配置

使用 Gal 前需先完成初始化,并配置权限相关参数(pubspec.yaml 配置):

使用最新版本:

dependencies:
  gal: ^2.1.0 # 建议使用最新稳定版

Android:


# Android 权限配置(android/app/src/main/AndroidManifest.xml)
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

iOS:

# iOS 权限配置(ios/Runner/Info.plist)
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择/保存图片</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要写入权限以保存图片到相册</string>

2.2. 核心 API 列表

API 方法 功能描述 参数说明 返回值
Gal.requestPermission() 申请相册读写权限 type: 权限类型(PermissionType.read/write Future<bool>: 是否授权成功
Gal.getPhotos() 获取相册图片列表 limit: 加载数量(默认全部)
albumId: 指定相册 ID(可选)
Future<List<GalPhoto>>: 图片信息列表
Gal.preview() 预览图片 photos: 图片列表
initialIndex: 初始预览索引
backgroundColor: 预览背景色
Future<void>
Gal.saveImage() 保存图片到相册 path: 图片本地路径/网络 URL
albumName: 自定义相册名称(可选)
Future<bool>: 是否保存成功
Gal.getAlbums() 获取设备相册列表 - Future<List<GalAlbum>>: 相册信息列表

2.3. 关键数据模型

GalPhoto(图片信息模型)

class GalPhoto {
  final String id; // 图片唯一标识
  final String path; // 本地路径
  final String? url; // 网络图片 URL(可选)
  final int width; // 图片宽度
  final int height; // 图片高度
  final DateTime createTime; // 创建时间
  final String mimeType; // 图片类型(image/jpeg 等)
}

GalAlbum(相册信息模型)

class GalAlbum {
  final String id; // 相册唯一标识
  final String name; // 相册名称
  final int count; // 相册内图片数量
  final String? coverPath; // 相册封面路径
}

3. 图片选择与预览功能Demo

以下是一个完整的 Demo,实现「获取相册图片 → 列表展示 → 点击预览 → 保存图片」的核心流程。

3.1 完整代码

import 'package:flutter/material.dart';
import 'package:gal/gal.dart';
import 'package:permission_handler/permission_handler.dart';

void main() => runApp(const GalDemoApp());

class GalDemoApp extends StatelessWidget {
  const GalDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gal 插件 Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const GalDemoPage(),
    );
  }
}

class GalDemoPage extends StatefulWidget {
  const GalDemoPage({super.key});

  @override
  State<GalDemoPage> createState() => _GalDemoPageState();
}

class _GalDemoPageState extends State<GalDemoPage> {
  List<GalPhoto> _photos = [];
  bool _isLoading = false;

  // 申请相册权限
  Future<bool> _requestPermission() async {
    final status = await Permission.photos.request();
    return status.isGranted;
  }

  // 加载相册图片
  Future<void> _loadPhotos() async {
    setState(() => _isLoading = true);
    try {
      final hasPermission = await _requestPermission();
      if (!hasPermission) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('请授予相册访问权限')),
          );
        }
        return;
      }

      // 获取相册图片(限制加载20张,避免性能问题)
      final photos = await Gal.getPhotos(limit: 20);
      setState(() => _photos = photos);
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('加载图片失败:$e')),
        );
      }
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 预览图片
  void _previewPhoto(int index) async {
    await Gal.preview(
      photos: _photos,
      initialIndex: index,
      backgroundColor: Colors.black,
    );
  }

  // 保存示例图片到相册
  Future<void> _saveSampleImage() async {
    const sampleImageUrl = 'https://picsum.photos/800/600';
    try {
      final success = await Gal.saveImage(
        sampleImageUrl,
        albumName: 'Gal Demo', // 自定义相册名称
      );
      if (success) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('图片保存成功')),
          );
          // 保存后重新加载图片列表
          _loadPhotos();
        }
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('保存失败:$e')),
        );
      }
    }
  }

  @override
  void initState() {
    super.initState();
    // 页面初始化时加载图片
    _loadPhotos();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gal 图片管理 Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.save),
            onPressed: _saveSampleImage,
            tooltip: '保存示例图片',
          ),
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }
    if (_photos.isEmpty) {
      return const Center(child: Text('暂无图片,请检查权限或相册内容'));
    }
    // 网格展示图片
    return GridView.builder(
      padding: const EdgeInsets.all(8),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3, // 每行3crossAxisSpacing: 4,
        mainAxisSpacing: 4,
        childAspectRatio: 1, // 宽高比1:1
      ),
      itemCount: _photos.length,
      itemBuilder: (context, index) {
        final photo = _photos[index];
        return GestureDetector(
          onTap: () => _previewPhoto(index),
          child: Image.file(
            File(photo.path),
            fit: BoxFit.cover,
            errorBuilder: (context, error, stackTrace) {
              return const Icon(Icons.broken_image, color: Colors.grey);
            },
          ),
        );
      },
    );
  }
}

3.2. 代码说明

  1. 权限处理:结合 permission_handler 插件申请相册权限,这是使用 Gal 的前提;
  2. 图片加载:通过 Gal.getPhotos() 获取相册图片,限制加载数量避免卡顿;
  3. 图片展示:使用 GridView 展示图片列表,点击图片调用 Gal.preview() 实现沉浸式预览;
  4. 图片保存:调用 Gal.saveImage() 将网络图片保存到自定义相册,保存成功后刷新列表。

3.3. 运行效果

  1. 首次打开应用会弹出权限申请弹窗,授权后加载相册前20张图片;
  2. 图片以网格形式展示,点击任意图片进入全屏预览模式,支持滑动切换、双指缩放;
  3. 点击右上角「保存」按钮,可将示例网络图片保存到「Gal Demo」相册,保存后列表自动刷新。

4. 注意事项

  1. 权限适配

    1. Android 13+ 需单独申请 READ_MEDIA_IMAGES 权限,Android 10 需配置 android:requestLegacyExternalStorage="true"
    2. iOS 14+ 支持精确相册权限(仅允许选择部分图片),Gal 已适配该特性。
  2. 性能优化

    1. 加载大量图片时,务必设置 limit 参数分页加载,避免一次性加载全部图片导致内存溢出;
    2. 预览图片时,建议使用 CachedNetworkImage 缓存网络图片。
  3. 异常处理

    1. 所有 Gal API 均为异步操作,需添加 try/catch 捕获权限拒绝、文件不存在等异常;
    2. 保存网络图片时,需先判断网络状态,避免无网络时保存失败。

5. 总结

  1. Gal 插件是 Flutter 中高效的图片管理工具,核心覆盖「权限申请、图片读取、预览、保存」四大核心场景,API 设计简洁且跨平台一致;
  2. 使用 Gal 的关键步骤:配置权限 → 申请权限 → 调用核心 API → 异常处理;
  3. 实战中需注意性能优化(分页加载、缓存)和平台适配(不同系统的权限/路径差异),确保体验一致性。

通过 Gal 插件,开发者可以摆脱原生图片处理的繁琐逻辑,快速实现媲美原生应用的图片交互体验,是 Flutter 图片类应用的优选插件。

源码:传送门


本次分享就到这儿啦,我是鹏多多,深耕前端的技术创作者,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

PS:在本页按F12,在console中输入document.getElementsByClassName('panel-btn')[0].click();有惊喜哦~

往期文章

❌