基础入门 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. 图片上传:结合网络请求实现图片上传功能

相关推荐
早點睡3902 小时前
基础入门 Flutter for OpenHarmony:FloatingActionButton 浮动按钮详解
flutter·harmonyos
哈__2 小时前
基础入门 Flutter for OpenHarmony:file_selector 文件选择详解
flutter
左手厨刀右手茼蒿2 小时前
Flutter for OpenHarmony 实战:DartX — 极致简练的开发超能力集
android·flutter·ui·华为·harmonyos
空白诗2 小时前
基础入门 Flutter for OpenHarmony:TabBar 标签栏组件详解
flutter·harmonyos
早點睡3902 小时前
基础入门 Flutter for OpenHarmony:RefreshIndicator 下拉刷新详解
flutter·harmonyos
哈__2 小时前
基础入门 Flutter for OpenHarmony:path_provider 目录路径获取详解
flutter
不爱吃糖的程序媛2 小时前
Flutter-OH 3.35.7 环境配置与插件开发指南
flutter
空白诗2 小时前
基础入门 Flutter for OpenHarmony:Chip 标签组件详解
flutter·harmonyos
加农炮手Jinx3 小时前
Flutter for OpenHarmony 实战:Injectable — 自动化依赖注入大师
网络·flutter·华为·harmonyos·鸿蒙