欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Flutter 三方库 image_picker 的 OpenHarmony 鸿蒙化适配实践

引言

在移动应用开发中,图片和视频选择是一个非常常见的需求。image_picker 作为 Flutter 官方推荐的媒体选择插件,提供了从相册选择图片/视频、拍照、录制视频等功能。随着 OpenHarmony 生态的发展,如何在 Flutter-OH 项目中正确集成和使用 image_picker 成为开发者关注的重点。本文将详细介绍 image_picker 在 OpenHarmony 平台上的适配实践,包括环境配置、权限申请、功能实现以及平台特定的注意事项。

一、环境准备与项目初始化

1.1 创建 Flutter-OH 项目

flutter create --platforms=ohos flutter_image_picker_oh
cd flutter_image_picker_oh

二、集成 image_picker 依赖

2.1 添加依赖到 pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  image_picker: ^1.0.7

2.2 获取依赖

flutter pub get

三、OpenHarmony 平台权限配置

3.1 配置媒体访问权限

ohos/entry/src/main/module.json5 中添加权限声明:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone", "tablet"],
    "permissions": [
      {
        "name": "ohos.permission.READ_IMAGEVIDEO"
      },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO"
      },
      {
        "name": "ohos.permission.CAMERA"
      }
    ],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntrance": "./ets/entryability/EntryAbility.ets",
        "description": "$string:entry_ability_desc",
        "icon": "$media:app_icon",
        "label": "$string:entry_ability_label",
        "type": "page",
        "launchType": "standard"
      }
    ]
  }
}

3.2 动态权限申请

在 Dart 代码中动态申请权限:

import 'package:permission_handler/permission_handler.dart';

Future<bool> _requestPermissions() async {
  final cameraStatus = await Permission.camera.request();
  final photosStatus = await Permission.photos.request();
  
  return cameraStatus.isGranted && photosStatus.isGranted;
}

四、image_picker 核心功能实践

4.1 从相册选择单张图片

import 'package:image_picker/image_picker.dart';

final ImagePicker _picker = ImagePicker();

Future<void> _pickImageFromGallery() async {
  final XFile? image = await _picker.pickImage(
    source: ImageSource.gallery,
    imageQuality: 80,
    maxWidth: 800,
    maxHeight: 800,
  );
  
  if (image != null) {
    // 处理图片
    print('图片路径: ${image.path}');
  }
}

4.2 拍照功能

Future<void> _pickImageFromCamera() async {
  final XFile? image = await _picker.pickImage(
    source: ImageSource.camera,
    imageQuality: 80,
    preferredCameraDevice: CameraDevice.rear,
  );
  
  if (image != null) {
    // 处理图片
  }
}

4.3 选择多张图片

Future<void> _pickMultipleImages() async {
  final List<XFile> images = await _picker.pickMultiImage(
    imageQuality: 80,
    maxWidth: 800,
    maxHeight: 800,
  );
  
  if (images.isNotEmpty) {
    for (var image in images) {
      print('图片路径: ${image.path}');
    }
  }
}

4.4 选择视频

Future<void> _pickVideoFromGallery() async {
  final XFile? video = await _picker.pickVideo(
    source: ImageSource.gallery,
    maxDuration: const Duration(seconds: 60),
  );
  
  if (video != null) {
    print('视频路径: ${video.path}');
  }
}

4.5 录制视频

Future<void> _pickVideoFromCamera() async {
  final XFile? video = await _picker.pickVideo(
    source: ImageSource.camera,
    maxDuration: const Duration(seconds: 30),
  );
  
  if (video != null) {
    print('视频路径: ${video.path}');
  }
}

五、完整示例应用

5.1 主页面实现

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

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

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final ImagePicker _picker = ImagePicker();
  XFile? _selectedImage;
  List<XFile> _selectedImages = [];

  Future<void> _pickSingleImage(ImageSource source) async {
    try {
      final XFile? image = await _picker.pickImage(
        source: source,
        imageQuality: 80,
      );
      if (image != null) {
        setState(() {
          _selectedImage = image;
          _selectedImages = [];
        });
      }
    } catch (e) {
      _showError(e.toString());
    }
  }

  Future<void> _pickMultiple() async {
    try {
      final List<XFile> images = await _picker.pickMultiImage(
        imageQuality: 80,
      );
      if (images.isNotEmpty) {
        setState(() {
          _selectedImages = images;
          _selectedImage = null;
        });
      }
    } catch (e) {
      _showError(e.toString());
    }
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('ImagePicker OH Demo')),
      body: Column(
        children: [
          _buildImagePreview(),
          _buildActionButtons(),
        ],
      ),
    );
  }

  Widget _buildImagePreview() {
    return Expanded(
      child: _selectedImage != null
          ? Image.file(Image.fileFromPath(_selectedImage!.path))
          : _selectedImages.isNotEmpty
              ? GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 3,
                  ),
                  itemCount: _selectedImages.length,
                  itemBuilder: (context, index) => Image.file(
                    Image.fileFromPath(_selectedImages[index].path),
                    fit: BoxFit.cover,
                  ),
                )
              : const Center(child: Text('选择图片')),
    );
  }

  Widget _buildActionButtons() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          onPressed: () => _pickSingleImage(ImageSource.gallery),
          child: const Text('相册'),
        ),
        const SizedBox(width: 20),
        ElevatedButton(
          onPressed: () => _pickSingleImage(ImageSource.camera),
          child: const Text('拍照'),
        ),
        const SizedBox(width: 20),
        ElevatedButton(
          onPressed: _pickMultiple,
          child: const Text('多选'),
        ),
      ],
    );
  }
}

六、OpenHarmony 平台特殊适配

6.1 文件路径处理

在 OpenHarmony 上,image_picker 返回的文件路径格式如下:

// 图片路径示例
/storage/media/Images/2024/01/01/image.jpg

// 获取文件大小
final file = File(image.path);
final size = await file.length();
print('文件大小: ${size / 1024} KB');

6.2 图片压缩与质量控制

final XFile? image = await _picker.pickImage(
  source: ImageSource.gallery,
  imageQuality: 70,  // 图片质量 0-100
  maxWidth: 1024,    // 最大宽度
  maxHeight: 1024,   // 最大高度
);

6.3 错误处理与异常捕获

Future<void> _pickImage() async {
  try {
    final XFile? image = await _picker.pickImage(
      source: ImageSource.gallery,
    );
    
    if (image == null) {
      // 用户取消选择
      return;
    }
    
    // 成功处理
  } on PlatformException catch (e) {
    // 平台相关错误
    print('平台错误: ${e.message}');
  } catch (e) {
    // 其他错误
    print('未知错误: $e');
  }
}

七、性能优化建议

7.1 图片懒加载

ListView.builder(
  itemCount: _imageFiles.length,
  itemBuilder: (context, index) {
    return FutureBuilder<FileImage>(
      future: _loadImage(index),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Image(image: snapshot.data!);
        }
        return const CircularProgressIndicator();
      },
    );
  },
);

7.2 内存管理


void dispose() {
  // 清理图片资源
  _selectedImage = null;
  _selectedImages.clear();
  super.dispose();
}

7.3 异步处理

Future<void> _processImages(List<XFile> images) async {
  for (var image in images) {
    await _processSingleImage(image);
  }
}

Future<void> _processSingleImage(XFile image) async {
  // 异步处理单张图片
  final file = File(image.path);
  // 处理逻辑...
}

八、常见问题与解决方案

8.1 问题:权限被拒绝

原因:用户拒绝了权限请求

解决方案

final status = await Permission.camera.status;
if (status.isDenied) {
  // 引导用户去设置页面开启权限
  openAppSettings();
}

8.2 问题:图片选择后不显示

原因:状态更新不及时或路径错误

解决方案

// 确保调用 setState 更新 UI
setState(() {
  _selectedImage = image;
});

8.3 问题:拍照后图片旋转

原因:图片 EXIF 信息中的方向标记

解决方案

// 使用 image 库处理图片方向
import 'package:image/image.dart' as img;

final imageBytes = await File(path).readAsBytes();
final decodedImage = img.decodeImage(imageBytes);
final orientedImage = img.bakeOrientation(decodedImage!);

九、运行验证

9.1 构建命令

flutter build ohos

9.2 测试功能清单

  • 从相册选择单张图片
  • 拍照功能
  • 选择多张图片
  • 选择视频
  • 录制视频
  • 图片预览显示
  • 权限申请流程

十、总结

image_picker 作为 Flutter 生态中最常用的媒体选择插件,在 OpenHarmony 平台上的适配相对顺畅。开发者需要关注以下几点:

  1. 权限配置:正确配置相册、相机等权限
  2. 异步处理:合理处理异步操作,避免阻塞UI
  3. 错误处理:捕获并处理各种异常情况
  4. 性能优化:注意图片压缩和内存管理

通过本文的实践,开发者可以快速掌握 image_picker 在 Flutter-OH 项目中的使用方法,实现完整的图片和视频选择功能。

本文仓库地址:https://atomgit.com/your_username/flutter_image_picker_oh


参考文献

  • image_picker 官方文档:https://pub.dev/packages/image_picker
  • OpenHarmony 权限文档:https://gitee.com/openharmony/docs
  • Flutter for OpenHarmony 文档:https://gitee.com/openharmony-sig/flutter
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐