基础入门 Flutter for OpenHarmony:image_picker 图片选择详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 image_picker 图片选择组件的使用方法,带你全面掌握从相册选择图片、拍摄照片、选择视频等功能。


一、image_picker 组件概述

在 Flutter for OpenHarmony 应用开发中,image_picker 是一个非常实用的插件,用于选择图片和视频。它支持从相册选择、相机拍摄等多种方式,为开发者提供了统一的 API,屏蔽了不同平台的差异。

📋 image_picker 组件特点

特点 说明
跨平台支持 支持 Android、iOS、Linux、macOS、Windows、OpenHarmony
统一 API 不同平台使用相同的 API 进行图片选择
多种来源 支持相机、相册等多种来源
图片/视频 支持选择图片和视频
多选支持 支持选择多张图片
质量控制 支持调整图片质量和尺寸
异步操作 所有选择操作都是异步的

💡 使用场景:用户头像上传、图片分享、相册浏览、视频选择等需要选择图片或视频的场景。


二、OpenHarmony 平台适配说明

2.1 兼容性信息

本项目基于 image_picker@1.1.2 开发,适配 Flutter 3.27.5-ohos-1.0.4。

2.2 支持的功能

在 OpenHarmony 平台上,image_picker 支持以下功能:

功能 说明 OpenHarmony 支持
pickImage() 选择单张图片 ✅ yes
pickMultiImage() 选择多张图片 ✅ yes
pickMedia() 选择图片或视频 ✅ yes
pickMultipleMedia() 选择多个图片或视频 ✅ yes
pickVideo() 选择视频 ✅ yes
retrieveLostData() 检索丢失的数据 ❌ no
supportsImageSource() 检查是否支持图片来源 ❌ no

三、项目配置与安装

3.1 添加依赖配置

首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 image_picker 依赖。

打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter

  # 添加 image_picker 依赖(OpenHarmony 适配版本)
  image_picker:
    git:
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/image_picker

配置说明:

  • 使用 git 方式引用开源鸿蒙适配的 flutter_packages 仓库
  • url:指定 GitCode 托管的仓库地址
  • path:指定 image_picker 包的具体路径
  • 本项目基于 image_picker@1.1.2 开发,适配 Flutter 3.27.5-ohos-1.0.4

⚠️ 重要:对于 OpenHarmony 平台,必须使用 git 方式引用适配版本,不能直接使用 pub.dev 的版本号。

3.2 下载依赖

配置完成后,需要在项目根目录执行以下命令下载依赖:

bash 复制代码
flutter pub get

执行成功后,你会看到类似以下的输出:

复制代码
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!

3.3 依赖自动配置说明

执行 flutter pub get 后,OpenHarmony 平台的依赖会自动配置到 ohos/entry/oh-package.json5 文件中。你不需要手动配置 OpenHarmony 端的依赖,Flutter 构建系统会自动处理。


四、image_picker 基础用法

4.1 导入包

在使用 image_picker 之前,首先需要导入相关包:

dart 复制代码
import 'package:image_picker/image_picker.dart';

4.2 创建 ImagePicker 实例

dart 复制代码
final ImagePicker _picker = ImagePicker();

4.3 选择单张图片 - pickImage()

dart 复制代码
// 从相机拍照
final XFile? photo = await _picker.pickImage(
  source: ImageSource.camera,
);

// 从相册选择
final XFile? image = await _picker.pickImage(
  source: ImageSource.gallery,
);

// 设置图片质量
final XFile? image = await _picker.pickImage(
  source: ImageSource.gallery,
  imageQuality: 80, // 0-100
);

// 设置最大宽度和高度
final XFile? image = await _picker.pickImage(
  source: ImageSource.gallery,
  maxWidth: 1920,
  maxHeight: 1080,
);

4.4 选择多张图片 - pickMultiImage()

dart 复制代码
// 选择多张图片
final List<XFile> images = await _picker.pickMultiImage();

// 设置图片质量和尺寸
final List<XFile> images = await _picker.pickMultiImage(
  imageQuality: 80,
  maxWidth: 1920,
  maxHeight: 1080,
);

4.5 选择视频 - pickVideo()

dart 复制代码
// 从相机录制视频
final XFile? video = await _picker.pickVideo(
  source: ImageSource.camera,
);

// 从相册选择视频
final XFile? video = await _picker.pickVideo(
  source: ImageSource.gallery,
);

// 设置最大录制时长
final XFile? video = await _picker.pickVideo(
  source: ImageSource.camera,
  maxDuration: const Duration(seconds: 30),
);

4.6 选择图片或视频 - pickMedia()

dart 复制代码
// 选择图片或视频
final XFile? media = await _picker.pickMedia();

// 选择多张图片或视频
final List<XFile> media = await _picker.pickMultipleMedia();

五、完整示例代码

下面是一个完整的示例应用,展示了 image_picker 的各种用法:

dart 复制代码
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

void main() {
  runApp(const ImagePickerDemo());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Image Picker 示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ImagePickerPage(),
    );
  }
}

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

  @override
  State<ImagePickerPage> createState() => _ImagePickerPageState();
}

class _ImagePickerPageState extends State<ImagePickerPage> {
  final ImagePicker _picker = ImagePicker();
  List<XFile> _imageFiles = [];
  XFile? _videoFile;
  String? _error;

  Future<void> _pickImageFromCamera() async {
    try {
      final XFile? image = await _picker.pickImage(
        source: ImageSource.camera,
        imageQuality: 80,
      );
      if (image != null) {
        setState(() {
          _imageFiles = [image];
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '拍照失败: $e';
      });
    }
  }

  Future<void> _pickImageFromGallery() async {
    try {
      final XFile? image = await _picker.pickImage(
        source: ImageSource.gallery,
        imageQuality: 80,
      );
      if (image != null) {
        setState(() {
          _imageFiles = [image];
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '选择图片失败: $e';
      });
    }
  }

  Future<void> _pickMultiImage() async {
    try {
      final List<XFile> images = await _picker.pickMultiImage(
        imageQuality: 80,
      );
      if (images.isNotEmpty) {
        setState(() {
          _imageFiles = images;
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '选择多张图片失败: $e';
      });
    }
  }

  Future<void> _pickVideoFromCamera() async {
    try {
      final XFile? video = await _picker.pickVideo(
        source: ImageSource.camera,
        maxDuration: const Duration(seconds: 30),
      );
      if (video != null) {
        setState(() {
          _videoFile = video;
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '录制视频失败: $e';
      });
    }
  }

  Future<void> _pickVideoFromGallery() async {
    try {
      final XFile? video = await _picker.pickVideo(
        source: ImageSource.gallery,
      );
      if (video != null) {
        setState(() {
          _videoFile = video;
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '选择视频失败: $e';
      });
    }
  }

  Future<void> _pickMedia() async {
    try {
      final XFile? media = await _picker.pickMedia();
      if (media != null) {
        setState(() {
          if (media.path.endsWith('.mp4') || media.path.endsWith('.mov')) {
            _videoFile = media;
          } else {
            _imageFiles = [media];
          }
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '选择媒体失败: $e';
      });
    }
  }

  void _clearAll() {
    setState(() {
      _imageFiles = [];
      _videoFile = null;
      _error = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Picker 示例'),
        actions: [
          if (_imageFiles.isNotEmpty || _videoFile != null)
            IconButton(
              icon: const Icon(Icons.clear),
              onPressed: _clearAll,
              tooltip: '清除所有',
            ),
        ],
      ),
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Colors.blue.shade50,
              Colors.purple.shade50,
            ],
          ),
        ),
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 图片选择卡片
              _buildSectionCard(
                title: '图片选择',
                icon: Icons.photo_library,
                color: Colors.blue,
                child: Column(
                  children: [
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _pickImageFromCamera,
                            icon: const Icon(Icons.camera_alt),
                            label: const Text('拍照'),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: OutlinedButton.icon(
                            onPressed: _pickImageFromGallery,
                            icon: const Icon(Icons.photo),
                            label: const Text('相册'),
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton.icon(
                        onPressed: _pickMultiImage,
                        icon: const Icon(Icons.photo_library),
                        label: const Text('选择多张图片'),
                      ),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 16),

              // 视频选择卡片
              _buildSectionCard(
                title: '视频选择',
                icon: Icons.videocam,
                color: Colors.purple,
                child: Row(
                  children: [
                    Expanded(
                      child: ElevatedButton.icon(
                        onPressed: _pickVideoFromCamera,
                        icon: const Icon(Icons.videocam),
                        label: const Text('录制视频'),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: OutlinedButton.icon(
                        onPressed: _pickVideoFromGallery,
                        icon: const Icon(Icons.movie),
                        label: const Text('选择视频'),
                      ),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 16),

              // 媒体选择卡片
              _buildSectionCard(
                title: '混合选择',
                icon: Icons.perm_media,
                color: Colors.orange,
                child: SizedBox(
                  width: double.infinity,
                  child: ElevatedButton.icon(
                    onPressed: _pickMedia,
                    icon: const Icon(Icons.select_all),
                    label: const Text('选择图片或视频'),
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // 显示错误信息
              if (_error != null)
                Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: Colors.red.shade50,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: Colors.red.shade200),
                  ),
                  child: Row(
                    children: [
                      const Icon(Icons.error, color: Colors.red),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          _error!,
                          style: const TextStyle(color: Colors.red),
                        ),
                      ),
                    ],
                  ),
                ),

              const SizedBox(height: 16),

              // 显示选中的图片
              if (_imageFiles.isNotEmpty)
                _buildSectionCard(
                  title: '选中的图片 (${_imageFiles.length})',
                  icon: Icons.image,
                  color: Colors.green,
                  child: GridView.builder(
                    shrinkWrap: true,
                    physics: const NeverScrollableScrollPhysics(),
                    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 3,
                      crossAxisSpacing: 8,
                      mainAxisSpacing: 8,
                    ),
                    itemCount: _imageFiles.length,
                    itemBuilder: (context, index) {
                      return Stack(
                        children: [
                          Positioned.fill(
                            child: ClipRRect(
                              borderRadius: BorderRadius.circular(8),
                              child: Image.file(
                                File(_imageFiles[index].path),
                                fit: BoxFit.cover,
                              ),
                            ),
                          ),
                          Positioned(
                            top: 4,
                            right: 4,
                            child: Container(
                              padding: const EdgeInsets.all(4),
                              decoration: BoxDecoration(
                                color: Colors.black54,
                                borderRadius: BorderRadius.circular(12),
                              ),
                              child: Text(
                                '${index + 1}',
                                style: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 12,
                                ),
                              ),
                            ),
                          ),
                        ],
                      );
                    },
                  ),
                ),

              // 显示选中的视频
              if (_videoFile != null)
                _buildSectionCard(
                  title: '选中的视频',
                  icon: Icons.video_library,
                  color: Colors.red,
                  child: Container(
                    padding: const EdgeInsets.all(16),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade100,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Column(
                      children: [
                        const Icon(Icons.play_circle_outline, size: 48),
                        const SizedBox(height: 8),
                        Text(
                          _videoFile!.name,
                          style: const TextStyle(fontWeight: FontWeight.w500),
                        ),
                      ],
                    ),
                  ),
                ),

              const SizedBox(height: 32),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildSectionCard({
    required String title,
    required IconData icon,
    required Color color,
    required Widget child,
  }) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: color.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    icon,
                    color: color,
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Text(
                  title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }
}

六、API 参数详解

6.1 ImageSource(图片来源)

枚举值 说明 OpenHarmony 支持
ImageSource.camera 相机拍照/录制 ✅ yes
ImageSource.gallery 相册选择 ✅ yes

6.2 pickImage() 参数

参数 说明 类型 默认值 OpenHarmony 支持
source 图片来源 ImageSource - ✅ yes
maxWidth 最大宽度 double? null ✅ yes
maxHeight 最大高度 double? null ✅ yes
imageQuality 图片质量(0-100) int? null ✅ yes
preferredCameraDevice 首选摄像头 CameraDevice CameraDevice.rear ✅ yes
requestFullMetadata 请求完整元数据 bool true ✅ yes

6.3 pickVideo() 参数

参数 说明 类型 默认值 OpenHarmony 支持
source 视频来源 ImageSource - ✅ yes
maxDuration 最大录制时长 Duration? null ✅ yes
preferredCameraDevice 首选摄像头 CameraDevice CameraDevice.rear ✅ yes

七、常见问题与最佳实践

7.1 常见问题

Q1: 为什么选择图片失败?

A: 检查以下几点:

  • 确认设备有可用的相机或相册
  • 使用 try-catch 捕获异常信息
Q2: 如何获取图片的文件大小?

A: 使用 File 类获取文件信息:

dart 复制代码
import 'dart:io';

final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
  final File file = File(image.path);
  final int size = await file.length();
  print('文件大小: ${size / 1024} KB');
}
Q3: 如何压缩图片?

A: 使用 imageQuality 参数:

dart 复制代码
final XFile? image = await _picker.pickImage(
  source: ImageSource.gallery,
  imageQuality: 80, // 80% 质量
);

7.2 最佳实践

1. 始终处理异常
dart 复制代码
try {
  final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
  if (image != null) {
    // 处理图片
  }
} catch (e) {
  print('选择图片失败: $e');
  // 提示用户
}
2. 检查返回值
dart 复制代码
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
  // 用户选择了图片
} else {
  // 用户取消了选择
}
3. 设置合理的图片质量
dart 复制代码
// 头像上传 - 高质量
final XFile? avatar = await _picker.pickImage(
  source: ImageSource.gallery,
  imageQuality: 90,
);

// 缩略图 - 低质量
final XFile? thumbnail = await _picker.pickImage(
  source: ImageSource.gallery,
  imageQuality: 60,
  maxWidth: 800,
  maxHeight: 800,
);

八、总结

恭喜你!通过这篇文章的学习,你已经掌握了 Flutter 中 image_picker 图片选择的全面知识。

🎯 核心要点

  1. 基础用法 :使用 pickImage()pickMultiImage()pickVideo() 选择图片和视频
  2. 多种来源:支持相机和相册两种来源
  3. 质量控制 :通过 imageQualitymaxWidthmaxHeight 控制图片质量
  4. 异步操作 :所有选择操作都是异步的,必须使用 await 等待

📚 使用场景指南

场景 推荐方法 参数建议
用户头像 pickImage() imageQuality: 90
缩略图 pickImage() imageQuality: 60, maxWidth: 800
瀑布流 pickMultiImage() imageQuality: 80
视频选择 pickVideo() maxDuration: 30s

🚀 进阶方向

掌握了 image_picker 后,你还可以探索以下方向:

  1. 图片处理:学习使用 image_editor 进行图片编辑
  2. 图片压缩:学习使用 flutter_image_compress 进行高级压缩
  3. 视频播放:学习使用 video_player 播放视频
  4. 图片上传:结合网络请求实现图片上传功能

相关推荐
钛态10 小时前
Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南
服务器·驱动开发·安全·flutter·华为·单元测试·harmonyos
念格13 小时前
Flutter 弹窗 UI 不刷新?用 StatefulBuilder 解决
flutter
程序员老刘15 小时前
2026春招Flutter岗位为何变少?我看到的3个招聘逻辑变化
flutter·ai编程·客户端
念格16 小时前
Flutter 实现点击任意位置收起键盘的最佳实践
flutter
念格16 小时前
Flutter ListView Physics 滚动物理效果详解
flutter
国医中兴16 小时前
ClickHouse的数据模型设计:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
国医中兴19 小时前
ClickHouse数据导入导出最佳实践:从性能到可靠性
flutter·harmonyos·鸿蒙·openharmony
国医中兴19 小时前
大数据处理的性能优化技巧:从理论到实践
flutter·harmonyos·鸿蒙·openharmony
●VON20 小时前
Flutter 入门指南:从基础组件到状态管理核心机制
前端·学习·flutter·von
西西学代码21 小时前
Flutter---SingleChildScrollView
前端·javascript·flutter