Flutter-使用Gal展示和保存图片资源
2026年1月26日 19:51
Gal 是 Flutter 生态中一款轻量、高性能的图片管理与预览插件,专为简化 Flutter 应用中图片选择、预览、保存等核心场景设计。它封装了原生平台的图片处理能力,提供统一的 API 接口,让开发者无需关注 iOS/Android 底层差异,快速实现专业级的图片交互体验。
1. Gal 插件核心功能
Gal 插件的核心价值在于跨平台一致性和易用性,主要覆盖以下场景:
- 图片预览:支持单张/多张图片的沉浸式预览,包含缩放、滑动切换、手势返回等交互;
- 相册操作:读取设备相册、筛选图片/视频、获取图片元信息(尺寸、路径、创建时间);
- 图片保存:将网络图片/本地图片保存到系统相册,自动处理权限申请;
- 权限管理:封装相册读写权限的申请与状态检测,适配 iOS/Android 权限机制差异;
- 性能优化:内置图片懒加载、内存缓存策略,避免大图集加载时的卡顿问题。
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: 图片本地路径/网络 URLalbumName: 自定义相册名称(可选) |
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, // 每行3列
crossAxisSpacing: 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. 代码说明
-
权限处理:结合
permission_handler插件申请相册权限,这是使用 Gal 的前提; -
图片加载:通过
Gal.getPhotos()获取相册图片,限制加载数量避免卡顿; -
图片展示:使用
GridView展示图片列表,点击图片调用Gal.preview()实现沉浸式预览; -
图片保存:调用
Gal.saveImage()将网络图片保存到自定义相册,保存成功后刷新列表。
3.3. 运行效果
- 首次打开应用会弹出权限申请弹窗,授权后加载相册前20张图片;
- 图片以网格形式展示,点击任意图片进入全屏预览模式,支持滑动切换、双指缩放;
- 点击右上角「保存」按钮,可将示例网络图片保存到「Gal Demo」相册,保存后列表自动刷新。
4. 注意事项
-
权限适配:
- Android 13+ 需单独申请
READ_MEDIA_IMAGES权限,Android 10 需配置android:requestLegacyExternalStorage="true"; - iOS 14+ 支持精确相册权限(仅允许选择部分图片),Gal 已适配该特性。
- Android 13+ 需单独申请
-
性能优化:
- 加载大量图片时,务必设置
limit参数分页加载,避免一次性加载全部图片导致内存溢出; - 预览图片时,建议使用
CachedNetworkImage缓存网络图片。
- 加载大量图片时,务必设置
-
异常处理:
- 所有 Gal API 均为异步操作,需添加
try/catch捕获权限拒绝、文件不存在等异常; - 保存网络图片时,需先判断网络状态,避免无网络时保存失败。
- 所有 Gal API 均为异步操作,需添加
5. 总结
- Gal 插件是 Flutter 中高效的图片管理工具,核心覆盖「权限申请、图片读取、预览、保存」四大核心场景,API 设计简洁且跨平台一致;
- 使用 Gal 的关键步骤:配置权限 → 申请权限 → 调用核心 API → 异常处理;
- 实战中需注意性能优化(分页加载、缓存)和平台适配(不同系统的权限/路径差异),确保体验一致性。
通过 Gal 插件,开发者可以摆脱原生图片处理的繁琐逻辑,快速实现媲美原生应用的图片交互体验,是 Flutter 图片类应用的优选插件。
源码:传送门
本次分享就到这儿啦,我是鹏多多,深耕前端的技术创作者,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~
PS:在本页按F12,在console中输入document.getElementsByClassName('panel-btn')[0].click();有惊喜哦~
往期文章
- flutter使用package_info_plus库获取应用信息的教程
- Flutter下拉刷新上拉加载侧拉刷新插件:easy_refresh全面使用指南
- flutter-使用EventBus实现组件间数据通信
- Flutter输入框TextField的属性与实战用法全面解析+示例
- Flutter自定义日历table_calendar完全指南+案例
- flutter-屏幕自适应插件flutter_screenutil教程全指南
- flutter-使用url_launcher打开链接/应用/短信/邮件和评分跳转等
- flutter图片选择库multi_image_picker_plus和image_picker的对比和使用解析
- 解锁flutter弹窗新姿势:dialog-flutter_smart_dialog插件解读+案例
- flutter-切换状态显示不同组件10种实现方案全解析
- flutter-详解控制组件显示的两种方式Offstage与Visibility
- flutter-使用AnimatedDefaultTextStyle实现文本动画
- flutter-使用SafeArea组件处理各机型的安全距离
- flutter-实现渐变色边框背景以及渐变色文字
- flutter-使用confetti制作炫酷纸屑爆炸粒子动画