在这里插入图片描述

📋 前言

react-native-image-picker 是 React Native 社区最流行的图片选择库,提供了一套完整的图片和视频选择解决方案。它支持从相册选择、相机拍照、视频录制等多种媒体获取方式,提供了丰富的配置选项和回调处理,并且完全兼容 Android、iOS 和 HarmonyOS 三端。

内容由熊哈哈O_o大佬指导创作:https://blog.csdn.net/qq_61024956?type=blog

🎯 库简介

基本信息

  • 库名称: @react-native-ohos/react-native-image-picker
  • 版本信息:
    • 7.0.4: 支持 RN 0.72 版本
    • 8.2.2: 支持 RN 0.77 版本
  • 官方仓库: https://github.com/react-native-oh-library/react-native-image-picker
  • 主要功能:
    • 从相册选择图片/视频
    • 拍照功能(暂未适配)
    • 支持多选(最多50张)
    • 支持图片压缩
    • 支持获取图片元数据
    • 兼容 Android、iOS 和 HarmonyOS
  • 兼容性验证:
    • RNOH: 0.72.96; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.858; ROM: 6.0.0.112;
    • RNOH: 0.72.33; SDK: HarmonyOS NEXT B1; IDE: DevEco Studio: 5.0.3.900; ROM: Next.0.0.71;
    • RNOH: 0.77.18; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.858; ROM: 6.0.0.112;

为什么需要这个库?

  • 功能完整: 提供完整的图片选择解决方案
  • 跨平台: 在三端提供一致的体验
  • 易于集成: API 简单直观
  • 灵活配置: 支持多种配置选项
  • 性能优异: 原生实现,高效稳定

📦 安装步骤

1. 使用 npm 安装

根据您的 RN 版本选择对应的包名:

npm install @react-native-ohos/react-native-image-picker@7.0.4-rc.1

2. 验证安装

安装完成后,检查 package.json 文件,应该能看到新增的依赖:

{
  "dependencies": {
    "@react-native-ohos/react-native-image-picker": "^7.0.4",
    // ... 其他依赖
  }
}

🔧 HarmonyOS 平台配置 ⭐

由于 HarmonyOS 暂不支持 AutoLink,需要手动配置原生端代码。本文采用方法一:通过 har 包引入的方式。

1. 在工程根目录的 oh-package.json5 添加 overrides 字段

首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 harmony

打开 harmony/oh-package.json5,添加以下配置:

{
  ...
  "overrides": {
    "@rnoh/react-native-openharmony": "^0.72.90"
  }
}

2. 引入原生端代码

方法一:通过 har 包引入(不推荐)

[!TIP] har 包位于三方库安装路径的 harmony 文件夹下。

打开 entry/oh-package.json5,添加以下依赖:

"dependencies": {
    "@rnoh/react-native-openharmony": "0.72.90",
    "@react-native-ohos/react-native-image-picker": "file:../../node_modules/@react-native-ohos/react-native-image-picker/harmony/image_picker.har"
}

点击右上角的 sync 按钮

或者在终端执行:

cd entry
ohpm install
方法二:直接链接源码(我用的这个)

目前 DevEco Studio 不支持通过源码引入外部 module,我们推荐使用直接链接源码的方式,将源码通过操作改成 harmony 工程的内部模块。

步骤 1: 把 <RN工程>/node_modules/@react-native-ohos/react-native-image-picker/harmony 目录下的源码 image_picker 复制到 harmony(鸿蒙壳工程)工程根目录下。

步骤 2: 在 harmony 工程根目录的 build-profile.template.json5(若存在)和 build-profile.json5 添加以下模块:

modules: [
  ...
  {
    name: '<xxx>',
    srcPath: './<xxx>',
  },
  {
    name: 'image_picker',
    srcPath: './image_picker',
  }
]

步骤 3: 打开 image_picker/oh-package.json5,修改 react-native-openharmony 和项目的版本一致。

步骤 4: 打开 entry/oh-package.json5,添加以下依赖:

"dependencies": {
  "@rnoh/react-native-openharmony": "0.72.90",
  "@react-native-ohos/react-native-image-picker": "file:../image_picker"
}

步骤 5: 点击 DevEco Studio 右上角的 sync 按钮

3. 配置 CMakeLists 和引入 ImagePickerViewPackage

若使用的是 <= 7.0.3-0.1.8 版本,请跳过本章。

打开 entry/src/main/cpp/CMakeLists.txt,添加:

project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")
set(LOG_VERBOSITY_LEVEL 1)
set(CMAKE_ASM_FLAGS "-Wno-error:unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,noexecstack,-s -fPIE -pie")
set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
add_compile_definitions(WITH_HITRACE_SYSTRACE)

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# RNOH_BEGIN: manual_package_linking_1
add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package)
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-image-picker/src/main/cpp" ./image_picker)
# RNOH_END: manual_package_linking_1

file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")

add_library(rnoh_app SHARED
    ${GENERATED_CPP_FILES}
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)

# RNOH_BEGIN: manual_package_linking_2
target_link_libraries(rnoh_app PUBLIC rnoh_sample_package)
+ target_link_libraries(rnoh_app PUBLIC rnoh_image_picker)
# RNOH_END: manual_package_linking_2

打开 entry/src/main/cpp/PackageProvider.cpp,添加:

#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
#include "SamplePackage.h"
+ #include "RNImagePickerPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
      std::make_shared<RNOHGeneratedPackage>(ctx),
      std::make_shared<SamplePackage>(ctx),
+     std::make_shared<RNImagePickerPackage>(ctx)
    };
}

4. 在 ArkTs 侧引入 ImagePickerViewPackage

打开 entry/src/main/ets/RNPackagesFactory.ts,添加:

+ import { ImagePickerViewPackage } from '@react-native-ohos/react-native-image-picker/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new SamplePackage(ctx),
+   new ImagePickerViewPackage(ctx)
  ];
}

5. 运行

点击右上角的 sync 按钮

或者在终端执行:

cd entry
ohpm install

然后编译、运行即可。

💻 完整代码示例

下面是一个完整的示例,展示了 react-native-image-picker 的各种使用场景:

import React, { useState } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Image,
  ScrollView,
  Alert,
} from 'react-native';
import {
  launchImageLibrary,
  launchCamera,
  type ImagePickerResponse,
  type Asset,
  type MediaType,
} from 'react-native-image-picker';

function ImagePickerDemo() {
  const [selectedImages, setSelectedImages] = useState<Asset[]>([]);
  const [selectedImage, setSelectedImage] = useState<Asset | null>(null);
  const [mediaType, setMediaType] = useState<MediaType>('photo');
  const [selectionLimit, setSelectionLimit] = useState<number>(1);
  const [includeBase64, setIncludeBase64] = useState<boolean>(false);
  const [cameraType, setCameraType] = useState<'back' | 'front'>('back');

  // 从相册选择图片/视频
  const pickFromLibrary = () => {
    launchImageLibrary(
      {
        mediaType: mediaType,
        selectionLimit: selectionLimit,
        includeBase64: includeBase64,
      },
      (response: ImagePickerResponse) => {
        if (response.didCancel) {
          Alert.alert('取消', '用户取消了选择');
        } else if (response.errorMessage) {
          Alert.alert('错误', response.errorMessage);
        } else if (response.assets) {
          if (selectionLimit === 1) {
            setSelectedImage(response.assets[0]);
          } else {
            setSelectedImages(response.assets);
          }
        }
      }
    );
  };

  // 打开相机拍照
  const openCamera = () => {
    launchCamera(
      {
        mediaType: mediaType,
        cameraType: cameraType,
        includeBase64: includeBase64,
      },
      (response: ImagePickerResponse) => {
        if (response.didCancel) {
          Alert.alert('取消', '用户取消了拍照');
        } else if (response.errorMessage) {
          Alert.alert('错误', response.errorMessage);
        } else if (response.errorMessage) {
          Alert.alert('错误', '相机功能暂未适配 HarmonyOS');
        } else if (response.assets) {
          setSelectedImage(response.assets[0]);
        }
      }
    );
  };

  // 清空选择
  const clearSelection = () => {
    setSelectedImage(null);
    setSelectedImages([]);
  };

  // 切换媒体类型
  const changeMediaType = (type: MediaType) => {
    setMediaType(type);
  };

  // 切换选择数量
  const toggleMultiSelect = () => {
    setSelectionLimit(selectionLimit === 1 ? 0 : 1);
  };

  // 切换相机方向
  const toggleCameraType = () => {
    setCameraType(cameraType === 'back' ? 'front' : 'back');
  };

  // 切换 Base64
  const toggleBase64 = () => {
    setIncludeBase64(!includeBase64);
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.content}>
        <Text style={styles.title}>图片选择器示例</Text>

        {/* 单选结果展示 */}
        {selectedImage && (
          <View style={styles.resultSection}>
            <Text style={styles.sectionTitle}>单选结果</Text>
            <Image
              source={{ uri: selectedImage.uri }}
              style={styles.image}
            />
            <View style={styles.infoRow}>
              <Text style={styles.infoLabel}>文件名:</Text>
              <Text style={styles.infoText}>{selectedImage.fileName}</Text>
            </View>
            <View style={styles.infoRow}>
              <Text style={styles.infoLabel}>尺寸:</Text>
              <Text style={styles.infoText}>
                {selectedImage.width} x {selectedImage.height}
              </Text>
            </View>
            <View style={styles.infoRow}>
              <Text style={styles.infoLabel}>文件大小:</Text>
              <Text style={styles.infoText}>
                {(selectedImage.fileSize! / 1024).toFixed(2)} KB
              </Text>
            </View>
            <View style={styles.infoRow}>
              <Text style={styles.infoLabel}>类型:</Text>
              <Text style={styles.infoText}>{selectedImage.type}</Text>
            </View>
            {selectedImage.base64 && (
              <View style={styles.infoRow}>
                <Text style={styles.infoLabel}>Base64:</Text>
                <Text style={styles.infoText} numberOfLines={1}>
                  {selectedImage.base64.substring(0, 50)}...
                </Text>
              </View>
            )}
          </View>
        )}

        {/* 多选结果展示 */}
        {selectedImages.length > 0 && (
          <View style={styles.resultSection}>
            <Text style={styles.sectionTitle}>多选结果 ({selectedImages.length})</Text>
            <View style={styles.imageGrid}>
              {selectedImages.map((asset, index) => (
                <Image
                  key={index}
                  source={{ uri: asset.uri }}
                  style={styles.gridImage}
                />
              ))}
            </View>
          </View>
        )}

        {/* 媒体类型选择 */}
        <Text style={styles.sectionTitle}>媒体类型</Text>
        <View style={styles.buttonRow}>
          {['photo', 'video', 'mixed'].map((type) => (
            <TouchableOpacity
              key={type}
              style={[
                styles.button,
                mediaType === type && styles.buttonActive,
              ]}
              onPress={() => changeMediaType(type as MediaType)}
            >
              <Text style={styles.buttonText}>
                {type === 'photo' ? '图片' : type === 'video' ? '视频' : '混合'}
              </Text>
            </TouchableOpacity>
          ))}
        </View>

        {/* 多选开关 */}
        <Text style={styles.sectionTitle}>多选模式</Text>
        <TouchableOpacity
          style={[
            styles.button,
            selectionLimit === 0 && styles.buttonActive,
          ]}
          onPress={toggleMultiSelect}
        >
          <Text style={styles.buttonText}>
            {selectionLimit === 1 ? '单选' : '多选'}
          </Text>
        </TouchableOpacity>

        {/* 相机方向 */}
        <Text style={styles.sectionTitle}>相机方向</Text>
        <TouchableOpacity
          style={styles.button}
          onPress={toggleCameraType}
        >
          <Text style={styles.buttonText}>
            {cameraType === 'back' ? '后置' : '前置'}
          </Text>
        </TouchableOpacity>

        {/* Base64 开关 */}
        <Text style={styles.sectionTitle}>包含 Base64</Text>
        <TouchableOpacity
          style={[
            styles.button,
            includeBase64 && styles.buttonActive,
          ]}
          onPress={toggleBase64}
        >
          <Text style={styles.buttonText}>
            {includeBase64 ? '开启' : '关闭'}
          </Text>
        </TouchableOpacity>

        {/* 操作按钮 */}
        <Text style={styles.sectionTitle}>操作</Text>
        <View style={styles.buttonRow}>
          <TouchableOpacity
            style={[styles.button, styles.buttonPrimary]}
            onPress={pickFromLibrary}
          >
            <Text style={styles.buttonText}>从相册选择</Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.button, styles.buttonWarning]}
            onPress={openCamera}
          >
            <Text style={styles.buttonText}>打开相机</Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.button, styles.buttonDanger]}
            onPress={clearSelection}
          >
            <Text style={styles.buttonText}>清空</Text>
          </TouchableOpacity>
        </View>

        {/* 说明 */}
        <View style={styles.noteSection}>
          <Text style={styles.noteTitle}>注意事项:</Text>
          <Text style={styles.noteText}>
            • 相机拍照功能暂未适配 HarmonyOS
          </Text>
          <Text style={styles.noteText}>
            • 多选模式最多支持选择 50 张图片
          </Text>
          <Text style={styles.noteText}>
            • 包含 Base64 会增加内存占用,谨慎使用
          </Text>
          <Text style={styles.noteText}>
            • 需要在应用权限中申请读取相册权限
          </Text>
        </View>
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  content: {
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
    color: '#333',
  },
  resultSection: {
    marginBottom: 20,
    padding: 15,
    backgroundColor: '#fff',
    borderRadius: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
    marginTop: 20,
    color: '#333',
  },
  image: {
    width: '100%',
    height: 200,
    resizeMode: 'contain',
    marginBottom: 10,
  },
  infoRow: {
    flexDirection: 'row',
    marginBottom: 5,
  },
  infoLabel: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#666',
    width: 80,
  },
  infoText: {
    fontSize: 14,
    color: '#333',
    flex: 1,
  },
  imageGrid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 10,
  },
  gridImage: {
    width: 80,
    height: 80,
    borderRadius: 8,
  },
  buttonRow: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 10,
    marginBottom: 10,
  },
  button: {
    paddingHorizontal: 15,
    paddingVertical: 12,
    backgroundColor: '#42a5f5',
    borderRadius: 8,
    minWidth: 100,
  },
  buttonActive: {
    backgroundColor: '#66bb6a',
  },
  buttonPrimary: {
    backgroundColor: '#42a5f5',
  },
  buttonWarning: {
    backgroundColor: '#ffa726',
  },
  buttonDanger: {
    backgroundColor: '#ef5350',
  },
  buttonText: {
    color: '#fff',
    fontSize: 14,
    fontWeight: '500',
    textAlign: 'center',
  },
  noteSection: {
    marginTop: 20,
    padding: 15,
    backgroundColor: '#fff3cd',
    borderRadius: 8,
    borderLeftWidth: 4,
    borderLeftColor: '#ffc107',
  },
  noteTitle: {
    fontSize: 14,
    fontWeight: 'bold',
    marginBottom: 8,
    color: '#856404',
  },
  noteText: {
    fontSize: 13,
    color: '#856404',
    marginBottom: 4,
  },
});

export default ImagePickerDemo;

🎨 实际应用场景

react-native-image-picker 可以应用于以下实际场景:

  1. 用户头像设置: 从相册选择或拍照上传用户头像
  2. 图片上传: 发布动态、商品图片等
  3. 视频上传: 发布短视频、教程视频等
  4. 证件上传: 身份证、银行卡等证件照片
  5. 相册管理: 批量选择图片进行编辑或分享

⚠️ 注意事项与最佳实践

1. 权限配置

在使用 image-picker 之前,需要在 HarmonyOS 的 module.json5 中配置权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:read_imagevideo_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

2. 错误处理

launchImageLibrary(options, (response) => {
  if (response.didCancel) {
    // 用户取消
    return;
  }
  if (response.errorMessage) {
    // 处理错误
    Alert.alert('错误', response.errorMessage);
    return;
  }
  if (response.assets) {
    // 处理选中的图片
  }
});

3. 性能优化

  • 避免选择过多图片(建议不超过 10 张)
  • 关闭 includeBase64 选项,除非确实需要
  • 使用 selectionLimit 限制选择数量
  • 对大图片进行压缩处理

4. HarmonyOS 特殊处理

在 HarmonyOS 上,需要注意:

  • 相机拍照功能暂未适配
  • 多选模式最多支持 50 张图片
  • 需要手动配置权限
  • 文件 URI 格式可能与其他平台不同

5. 最佳实践

// ✅ 推荐:使用类型安全
import type { ImagePickerResponse, Asset } from 'react-native-image-picker';

// ✅ 推荐:添加错误处理
try {
  const response = await launchImageLibrary(options);
  if (response.assets) {
    // 处理图片
  }
} catch (error) {
  console.error('图片选择失败:', error);
}

// ✅ 推荐:限制选择数量
launchImageLibrary({
  mediaType: 'photo',
  selectionLimit: 5, // 最多选择 5 张
});

// ❌ 不推荐:不设置 selectionLimit
launchImageLibrary({
  mediaType: 'photo',
  // selectionLimit 未设置,默认为 1
});

🧪 测试验证

1. Android 平台测试

npm run android

测试要点:

  • 测试从相册选择图片
  • 测试多选功能
  • 测试视频选择
  • 验证图片元数据

2. iOS 平台测试

npm run ios

测试要点:

  • 测试从相册选择图片
  • 测试相机拍照
  • 测试多选功能
  • 验证图片质量

3. HarmonyOS 平台测试

npm run harmony

测试要点:

  • 验证图片选择功能
  • 测试多选功能
  • 检查权限请求
  • 验证图片显示

4. 常见问题排查

问题 1: 无法打开相册

  • 检查权限配置
  • 确认权限已授予
  • 验证包名配置

问题 2: 图片显示失败

  • 检查 URI 格式
  • 确认文件存在
  • 验证 Image 组件配置

问题 3: 多选功能不工作

  • 检查 selectionLimit 配置
  • 确认 HarmonyOS 版本支持
  • 验证相册权限

📊 对比:原生选择器 vs react-native-image-picker

特性 原生选择器 react-native-image-picker
跨平台一致性 ✅ 完全一致
API 简洁性 ⚠️ 复杂 ✅ 简洁
功能丰富度 ✅ 完整 ✅ 完整
多选支持 ✅ 支持 ✅ 支持(最多50张)
图片压缩 ✅ 支持 ⚠️ 部分
视频支持 ✅ 支持 ✅ 支持
自定义能力 ⚠️ 受限 ✅ 灵活

📝 总结

通过集成 react-native-image-picker,我们为项目添加了强大的图片选择能力。这个库提供了完整的图片和视频选择解决方案,支持多种选择方式和配置选项,并且完全跨平台兼容。

关键要点回顾

  • 安装依赖: npm install @react-native-ohos/react-native-image-picker
  • 配置平台: 通过 har 包或直接链接源码,配置 CMakeLists.txt、PackageProvider.cpp、RNPackagesFactory.ts
  • 集成代码: 使用 launchImageLibrarylaunchCamera 方法
  • 支持功能: 相册选择、多选、图片元数据、视频选择等
  • 重要: 相机拍照功能暂未适配 HarmonyOS

实际效果

  • Android: 原生相册选择体验
  • iOS: 高质量的图片选择
  • HarmonyOS: 一致的选择体验

已知限制

  • ❌ 相机拍照功能暂未适配 HarmonyOS
  • ⚠️ 部分高级配置项未实现
  • ⚠️ 图片压缩功能有限

希望这篇教程能帮助你顺利集成 react-native-image-picker,构建出色的图片选择体验!


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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐