高级进阶 React Native 鸿蒙跨平台开发:react-native-image-crop-picker 图片裁剪与选择
是 React Native 生态中最流行的图片选择与裁剪库,提供了强大的图片选择、相机拍照、裁剪编辑等功能。该库支持从相册选择图片、调用相机拍照、图片裁剪、压缩等多种操作,广泛应用于头像上传、图片编辑、相册管理等场景,是实现图片处理功能的首选库。库名称版本信息RN 0.72:(0.40.5)RN 0.77:(0.50.2)RN 0.82:(0.51.2)官方仓库主要功能从相册选择单张或多张图片调

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
📋 前言
react-native-image-crop-picker 是 React Native 生态中最流行的图片选择与裁剪库,提供了强大的图片选择、相机拍照、裁剪编辑等功能。该库支持从相册选择图片、调用相机拍照、图片裁剪、压缩等多种操作,广泛应用于头像上传、图片编辑、相册管理等场景,是实现图片处理功能的首选库。
🎯 库简介
基本信息
- 库名称: react-native-image-crop-picker
- 版本信息:
- RN 0.72:
@react-native-ohos/react-native-image-crop-picker(0.40.5) - RN 0.77:
@react-native-ohos/react-native-image-crop-picker(0.50.2) - RN 0.82:
@react-native-ohos/react-native-image-crop-picker(0.51.2)
- RN 0.72:
- 官方仓库: https://gitcode.com/openharmony-sig/rntpc_react-native-image-crop-picker
- 主要功能:
- 从相册选择单张或多张图片
- 调用相机拍照
- 图片裁剪编辑
- 图片压缩与质量调整
- 支持 Base64 格式返回
- 支持多种媒体类型(照片、视频)
- 支持自由裁剪、圆形裁剪等
- 自定义裁剪界面文本和颜色
- 支持多选和最大数量限制
- 兼容性验证:
- 支持 HarmonyOS NEXT
- 支持相机和相册权限管理
- 完整的图片处理功能
为什么需要这个库?
- 功能完整: 集成图片选择、拍照、裁剪于一体
- 易于使用: API 简洁,集成方便
- 高度可定制: 支持丰富的配置选项
- 性能优异: 图片处理效率高
- 跨平台: Android、iOS、HarmonyOS 完全兼容
- 灵活裁剪: 支持多种裁剪模式和自定义样式
📦 安装步骤
1. 使用 npm 安装
npm install @react-native-ohos/react-native-image-crop-picker@0.40.5-rc.1
2. 使用 yarn 安装
yarn add @react-native-ohos/react-native-image-crop-picker
3. 验证安装
安装完成后,检查 package.json 文件,应该能看到新增的依赖:
{
"dependencies": {
"@react-native-ohos/react-native-image-crop-picker": "0.40.5-rc.1",
// ... 其他依赖
}
}
🔧 HarmonyOS 平台配置 ⭐
重要说明
- Autolink 支持: 版本 0.40.5 支持 Autolink(RN 0.72),版本 0.50.2 和 0.51.2 不支持 Autolink(RN 0.77 和 RN 0.82)
- 导入库名: 使用
react-native-image-crop-picker导入,编辑器不会报错 - 权限要求: 需要在 module.json5 中配置相机和相册权限
手动配置步骤
1. 配置 overrides 字段
在工程根目录的 oh-package.json5 添加 overrides 字段:
{
"overrides": {
"@rnoh/react-native-openharmony": "./react_native_openharmony"
}
}
2. 引入原生端代码
方法一:通过 har 包引入(不推荐)
打开 entry/oh-package.json5,添加以下依赖:
"dependencies": {
"@rnoh/react-native-openharmony": "file:../react_native_openharmony",
"@react-native-ohos/react-native-image-crop-picker": "file:../../node_modules/@react-native-ohos/react-native-image-crop-picker/harmony/react_native_image_crop_picker.har"
}
点击右上角的 sync 按钮或在终端执行:
cd entry
ohpm install
方法二:直接链接源码
把 <RN工程>/node_modules/@react-native-ohos/react-native-image-crop-picker/harmony 目录下的源码 image_crop_picker 复制到 harmony 工程根目录下。
在 harmony 工程根目录的 build-profile.json5 添加模块:
modules: [
...
{
name: 'image_crop_picker',
srcPath: './image_crop_picker',
}
]
打开 image_crop_picker/oh-package.json5,修改 react-native-openharmony 版本与项目一致。
打开 entry/oh-package.json5,添加依赖:
"dependencies": {
"@rnoh/react-native-openharmony": "0.72.90",
"@react-native-ohos/react-native-image-crop-picker": "file:../image_crop_picker"
}
点击 DevEco Studio 右上角的 sync 按钮。
3. 配置 CMakeLists 和引入 ImageCropPickerPackage
打开 entry/src/main/cpp/CMakeLists.txt,添加:
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-image-crop-picker/src/main/cpp" ./image_crop_picker)
# RNOH_END: manual_package_linking_1
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_image_crop_picker)
# RNOH_END: manual_package_linking_2
打开 entry/src/main/cpp/PackageProvider.cpp,添加:
+ #include "ImageCropPickerPackage.h"
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<ImageCropPickerPackage>(ctx),
};
}
4. 在 ArkTs 侧引入 ImageCropPickerPackage
打开 entry/src/main/ets/RNPackagesFactory.ts,添加:
+ import {ImageCropPickerPackage} from '@react-native-ohos/react-native-image-crop-picker/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [
new SamplePackage(ctx),
+ new ImageCropPickerPackage(ctx)
];
}
5. 配置权限
打开 entry/src/main/module.json5,添加相机和相册权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:read_media_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:write_media_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
}
}
6. 配置必要的 ImageEditAbility ⭐
[!TIP] 该模块的内容无法通过 autolink 自动生成,始终需要手动配置。
(1) 创建 ImageEditAbility.ets
在 entry/src/main/ets/entryability 目录下创建 ImageEditAbility.ets 文件:
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import { BusinessError } from "@ohos.base";
const TAG = 'ImageEditAbility';
export default class ImageEditAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
this.setWindowOrientation(windowStage, window.Orientation.PORTRAIT);
windowStage.loadContent('pages/ImageEdit', (err, data) => {
let windowClass: window.Window = windowStage.getMainWindowSync();
let isLayoutFullScreen = true;
windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {
console.info('Succeeded in setting the window layout to full-screen mode.');
}).catch((err: BusinessError) => {
console.error(`Failed to set the window layout to full-screen mode. Code is ${err.code}, message is ${err.message}`);
});
let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR;
let avoidArea = windowClass.getWindowAvoidArea(type);
let bottomRectHeight = avoidArea.bottomRect.height; // 获取到导航区域的高度
AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
type = window.AvoidAreaType.TYPE_SYSTEM;
avoidArea = windowClass.getWindowAvoidArea(type);
let topRectHeight = avoidArea.topRect.height; // 获取状态栏区域高度
AppStorage.setOrCreate('topRectHeight', topRectHeight);
windowClass.on('avoidAreaChange', (data) => {
if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
let topRectHeight = data.area.topRect.height;
AppStorage.setOrCreate('topRectHeight', topRectHeight);
} else if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
let bottomRectHeight = data.area.bottomRect.height;
AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
}
});
if (err.code) {
console.info(TAG, 'Failed to load the content. Cause: %{public}s',
JSON.stringify(err) ?? '');
return;
}
console.info(TAG, 'Succeeded in loading the content');
});
try {
windowStage.getMainWindowSync().setWindowLayoutFullScreen(true, (err) => {
if (err.code) {
console.error('Failed to enable the full-screen mode. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in enabling the full-screen mode.');
});
} catch (exception) {
console.error('Failed to set the system bar to be invisible. Cause: ' + JSON.stringify(exception));
}
}
setWindowOrientation(stage: window.WindowStage, orientation: window.Orientation): void {
console.info(TAG, "into setWindowOrientation :");
if (!stage || !orientation) {
return;
}
stage.getMainWindow().then(windowInstance => {
windowInstance.setPreferredOrientation(orientation);
});
}
onBackground() {
this.context.terminateSelf();
}
}
(2) 在 module.json5 中注册 ImageEditAbility
打开 entry/src/main/module.json5,在 abilities 数组中添加:
{
"module": {
"abilities": [
// ... 其他 abilities
{
"name": "ImageEditAbility",
"srcEntry": "./ets/entryability/ImageEditAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"removeMissionAfterTerminate": true
}
]
}
}
(3) 创建 ImageEdit.ets 页面
在 entry/src/main/ets/pages 目录下创建 ImageEdit.ets 文件:
import { ImageEditInfo } from '@react-native-ohos/react-native-image-crop-picker';
@Entry
@Component
struct ImageEdit {
@State cropperCircleOverlay: boolean = false;
aboutToAppear(): void {
this.cropperCircleOverlay = AppStorage.Get('cropperCircleOverlay') || false;
}
build() {
Row() {
Column() {
if (!this.cropperCircleOverlay) {
ImageEditInfo();
}
}
.width('100%');
}
.height('100%');
}
}
(4) 在 main_pages.json 中添加页面配置
打开 entry/src/main/resources/base/profile/main_pages.json,在 src 数组中添加:
{
"src": [
"pages/Index",
"pages/ImageEdit"
]
}
7. 配置字符串资源
由于权限配置中使用了 $string 引用,需要在字符串资源文件中添加对应的字符串定义。
打开 entry/src/main/resources/base/element/string.json,添加以下内容:
{
"string": [
{
"name": "camera_permission_reason",
"value": "需要使用相机拍照功能,用于选择和编辑图片"
},
{
"name": "read_media_permission_reason",
"value": "需要读取相册中的图片,用于图片选择和编辑"
},
{
"name": "write_media_permission_reason",
"value": "需要保存编辑后的图片到相册"
}
]
}
如果项目还支持其他语言(如英文),还需要在对应的语言资源文件中添加相同的字符串定义。例如,打开 entry/src/main/resources/en/element/string.json,添加:
{
"string": [
{
"name": "camera_permission_reason",
"value": "Need to use camera to take photos for image selection and editing"
},
{
"name": "read_media_permission_reason",
"value": "Need to read images from gallery for image selection and editing"
},
{
"name": "write_media_permission_reason",
"value": "Need to save edited images to gallery"
}
]
}
💻 完整代码示例
下面展示了 react-native-image-crop-picker 的完整使用场景,包括从相册选择、拍照、裁剪等多种功能:
import React, { useState } from 'react';
import {
StyleSheet,
ScrollView,
View,
Text,
TouchableOpacity,
Image,
Alert,
SafeAreaView,
} from 'react-native';
import ImagePicker, {
Image as ImageType,
Options,
} from 'react-native-image-crop-picker';
/**
* react-native-image-crop-picker 图片选择与裁剪示例
* 功能演示:
* 1. 从相册选择单张图片
* 2. 从相册选择多张图片
* 3. 调用相机拍照
* 4. 裁剪指定图片
* 5. 清理缓存图片
*/
const ImageCropPickerExample = () => {
const [selectedImages, setSelectedImages] = useState<ImageType[]>([]);
const [cropping, setCropping] = useState<boolean>(true);
const [multiple, setMultiple] = useState<boolean>(false);
const [maxFiles, setMaxFiles] = useState<number>(9);
const [includeBase64, setIncludeBase64] = useState<boolean>(false);
/**
* 从相册选择图片
*/
const openPicker = async () => {
try {
const options: Options = {
cropping: cropping,
multiple: multiple,
maxFiles: maxFiles,
includeBase64: includeBase64,
mediaType: 'photo',
compressImageQuality: 0.9,
compressImageMaxWidth: 1920,
compressImageMaxHeight: 1920,
cropperToolbarTitle: '编辑图片',
cropperChooseText: '确定',
cropperCancelText: '取消',
cropperChooseColor: '#2196F3',
cropperCancelColor: '#FF5252',
showCropGuidelines: true,
showCropFrame: true,
hideBottomControls: false,
enableRotationGesture: true,
};
const images = await ImagePicker.openPicker(options);
setSelectedImages(Array.isArray(images) ? images : [images]);
Alert.alert('成功', `选择了 ${Array.isArray(images) ? images.length : 1} 张图片`);
} catch (error: any) {
if (error.code !== 'E_PICKER_CANCELLED') {
Alert.alert('错误', error.message || '选择图片失败');
}
}
};
/**
* 调用相机拍照
*/
const openCamera = async () => {
try {
const options: Options = {
cropping: cropping,
includeBase64: includeBase64,
mediaType: 'photo',
compressImageQuality: 0.9,
compressImageMaxWidth: 1920,
compressImageMaxHeight: 1920,
cropperToolbarTitle: '编辑图片',
cropperChooseText: '确定',
cropperCancelText: '取消',
cropperChooseColor: '#2196F3',
cropperCancelColor: '#FF5252',
showCropGuidelines: true,
showCropFrame: true,
enableRotationGesture: true,
useFrontCamera: false,
};
const image = await ImagePicker.openCamera(options);
setSelectedImages([image]);
Alert.alert('成功', '拍照成功');
} catch (error: any) {
if (error.code !== 'E_PICKER_CANCELLED') {
Alert.alert('错误', error.message || '拍照失败');
}
}
};
/**
* 裁剪指定图片
*/
const openCropper = async (imagePath: string) => {
try {
const image = await ImagePicker.openCropper({
path: imagePath,
mediaType: 'photo',
width: 300,
height: 400,
cropping: true,
cropperToolbarTitle: '裁剪图片',
cropperChooseText: '确定',
cropperCancelText: '取消',
cropperChooseColor: '#2196F3',
cropperCancelColor: '#FF5252',
showCropGuidelines: true,
showCropFrame: true,
enableRotationGesture: true,
freeStyleCropEnabled: false,
cropperCircleOverlay: false,
});
const updatedImages = selectedImages.map(img =>
img.path === imagePath ? image : img
);
setSelectedImages(updatedImages);
Alert.alert('成功', '裁剪成功');
} catch (error: any) {
if (error.code !== 'E_PICKER_CANCELLED') {
Alert.alert('错误', error.message || '裁剪失败');
}
}
};
/**
* 清理缓存图片
*/
const cleanImages = async () => {
try {
await ImagePicker.clean();
setSelectedImages([]);
Alert.alert('成功', '缓存已清理');
} catch (error: any) {
Alert.alert('错误', error.message || '清理失败');
}
};
/**
* 删除单张图片
*/
const removeImage = (index: number) => {
const updatedImages = [...selectedImages];
updatedImages.splice(index, 1);
setSelectedImages(updatedImages);
};
/**
* 清理单张图片缓存
*/
const cleanSingleImage = async (imagePath: string) => {
try {
await ImagePicker.cleanSingle(imagePath);
const updatedImages = selectedImages.filter(img => img.path !== imagePath);
setSelectedImages(updatedImages);
Alert.alert('成功', '图片缓存已清理');
} catch (error: any) {
Alert.alert('错误', error.message || '清理失败');
}
};
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.scrollView}>
<View style={styles.header}>
<Text style={styles.headerTitle}>图片选择与裁剪</Text>
<Text style={styles.headerSubtitle}>
react-native-image-crop-picker
</Text>
</View>
{/* 设置区域 */}
<View style={styles.settingsContainer}>
<Text style={styles.sectionTitle}>设置选项</Text>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>启用裁剪</Text>
<TouchableOpacity
style={[
styles.toggleButton,
cropping ? styles.toggleButtonActive : styles.toggleButtonInactive,
]}
onPress={() => setCropping(!cropping)}
>
<Text
style={[
styles.toggleButtonText,
cropping ? styles.toggleButtonTextActive : styles.toggleButtonTextInactive,
]}
>
{cropping ? '开' : '关'}
</Text>
</TouchableOpacity>
</View>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>多选模式</Text>
<TouchableOpacity
style={[
styles.toggleButton,
multiple ? styles.toggleButtonActive : styles.toggleButtonInactive,
]}
onPress={() => setMultiple(!multiple)}
>
<Text
style={[
styles.toggleButtonText,
multiple ? styles.toggleButtonTextActive : styles.toggleButtonTextInactive,
]}
>
{multiple ? '开' : '关'}
</Text>
</TouchableOpacity>
</View>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>包含 Base64</Text>
<TouchableOpacity
style={[
styles.toggleButton,
includeBase64 ? styles.toggleButtonActive : styles.toggleButtonInactive,
]}
onPress={() => setIncludeBase64(!includeBase64)}
>
<Text
style={[
styles.toggleButtonText,
includeBase64 ? styles.toggleButtonTextActive : styles.toggleButtonTextInactive,
]}
>
{includeBase64 ? '开' : '关'}
</Text>
</TouchableOpacity>
</View>
{multiple && (
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>最大数量: {maxFiles}</Text>
<TouchableOpacity
style={styles.counterButton}
onPress={() => setMaxFiles(Math.max(1, maxFiles - 1))}
>
<Text style={styles.counterButtonText}>-</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.counterButton}
onPress={() => setMaxFiles(Math.min(9, maxFiles + 1))}
>
<Text style={styles.counterButtonText}>+</Text>
</TouchableOpacity>
</View>
)}
</View>
{/* 操作按钮区域 */}
<View style={styles.buttonsContainer}>
<TouchableOpacity style={styles.button} onPress={openPicker}>
<Text style={styles.buttonText}>📷 从相册选择</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={openCamera}>
<Text style={styles.buttonText}>📸 拍照</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.buttonSecondary]}
onPress={cleanImages}
>
<Text style={[styles.buttonText, styles.buttonSecondaryText]}>
🗑️ 清理缓存
</Text>
</TouchableOpacity>
</View>
{/* 图片展示区域 */}
<View style={styles.galleryContainer}>
<Text style={styles.sectionTitle}>
已选择图片 ({selectedImages.length})
</Text>
{selectedImages.length === 0 ? (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>暂无图片</Text>
<Text style={styles.emptyStateHint}>
点击上方按钮选择或拍照
</Text>
</View>
) : (
<View style={styles.gallery}>
{selectedImages.map((image, index) => (
<View key={index} style={styles.imageItem}>
<Image
source={{ uri: image.path }}
style={styles.galleryImage}
resizeMode="cover"
/>
<View style={styles.imageActions}>
<TouchableOpacity
style={styles.imageActionButton}
onPress={() => openCropper(image.path)}
>
<Text style={styles.imageActionText}>✂️</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.imageActionButton}
onPress={() => removeImage(index)}
>
<Text style={styles.imageActionText}>❌</Text>
</TouchableOpacity>
</View>
<Text style={styles.imageInfo}>
{image.width}x{image.height}
</Text>
</View>
))}
</View>
)}
</View>
{/* 使用说明 */}
<View style={styles.tipsContainer}>
<Text style={styles.sectionTitle}>使用说明</Text>
<Text style={styles.tipText}>• 从相册选择: 支持单选和多选</Text>
<Text style={styles.tipText}>• 拍照功能: 支持前后摄像头切换</Text>
<Text style={styles.tipText}>• 图片裁剪: 支持自由裁剪和圆形裁剪</Text>
<Text style={styles.tipText}>• 图片压缩: 自动压缩,优化存储</Text>
<Text style={styles.tipText}>• 缓存管理: 支持清理单张或全部缓存</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
scrollView: {
flex: 1,
},
header: {
backgroundColor: '#2196F3',
paddingVertical: 24,
paddingHorizontal: 20,
alignItems: 'center',
},
headerTitle: {
fontSize: 24,
fontWeight: 'bold',
color: '#FFFFFF',
marginBottom: 4,
},
headerSubtitle: {
fontSize: 14,
color: 'rgba(255, 255, 255, 0.9)',
},
settingsContainer: {
backgroundColor: '#FFFFFF',
margin: 16,
borderRadius: 12,
padding: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#333333',
marginBottom: 12,
},
settingRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
settingLabel: {
fontSize: 16,
color: '#333333',
},
toggleButton: {
paddingHorizontal: 20,
paddingVertical: 8,
borderRadius: 20,
minWidth: 60,
alignItems: 'center',
},
toggleButtonActive: {
backgroundColor: '#2196F3',
},
toggleButtonInactive: {
backgroundColor: '#E0E0E0',
},
toggleButtonText: {
fontSize: 14,
fontWeight: '600',
},
toggleButtonTextActive: {
color: '#FFFFFF',
},
toggleButtonTextInactive: {
color: '#666666',
},
counterButton: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#2196F3',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 8,
},
counterButtonText: {
fontSize: 18,
fontWeight: 'bold',
color: '#FFFFFF',
},
buttonsContainer: {
paddingHorizontal: 16,
marginBottom: 16,
},
button: {
backgroundColor: '#2196F3',
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
marginBottom: 12,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
buttonSecondary: {
backgroundColor: '#FFFFFF',
borderWidth: 2,
borderColor: '#2196F3',
},
buttonText: {
fontSize: 16,
fontWeight: '600',
color: '#FFFFFF',
},
buttonSecondaryText: {
color: '#2196F3',
},
galleryContainer: {
backgroundColor: '#FFFFFF',
margin: 16,
borderRadius: 12,
padding: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
emptyState: {
alignItems: 'center',
paddingVertical: 40,
},
emptyStateText: {
fontSize: 16,
color: '#999999',
marginBottom: 8,
},
emptyStateHint: {
fontSize: 14,
color: '#CCCCCC',
},
gallery: {
flexDirection: 'row',
flexWrap: 'wrap',
marginHorizontal: -6,
},
imageItem: {
width: '48%',
aspectRatio: 1,
margin: '1%',
borderRadius: 8,
overflow: 'hidden',
backgroundColor: '#F0F0F0',
},
galleryImage: {
width: '100%',
height: '100%',
},
imageActions: {
position: 'absolute',
top: 8,
right: 8,
flexDirection: 'row',
},
imageActionButton: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 8,
},
imageActionText: {
fontSize: 16,
},
imageInfo: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
color: '#FFFFFF',
fontSize: 12,
padding: 4,
textAlign: 'center',
},
tipsContainer: {
backgroundColor: '#FFFFFF',
margin: 16,
borderRadius: 12,
padding: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
marginBottom: 32,
},
tipText: {
fontSize: 14,
color: '#666666',
lineHeight: 22,
marginBottom: 4,
},
});
export default ImageCropPickerExample;
🎨 实际应用场景
react-native-image-crop-picker 可以应用于以下实际场景:
- 头像上传: 用户选择或拍摄头像,支持裁剪
- 图片编辑: 裁剪、压缩图片,优化存储
- 相册管理: 多选图片,批量操作
- 证件上传: 拍摄证件照,自动裁剪
- 商品图片: 电商应用中商品图片选择和编辑
- 社交分享: 选择图片分享到社交平台
⚠️ 注意事项与最佳实践
1. 权限配置
// ✅ 推荐: 在 module.json5 中正确配置权限
{
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "需要使用相机拍照功能",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "需要读取相册中的图片",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "需要保存编辑后的图片",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
2. 图片选择
// ✅ 推荐: 从相册选择图片
const pickImage = async () => {
try {
const image = await ImagePicker.openPicker({
mediaType: 'photo',
cropping: true,
compressImageQuality: 0.9,
cropperToolbarTitle: '编辑图片',
cropperChooseText: '确定',
cropperCancelText: '取消',
});
console.log('选择的图片:', image.path);
} catch (error) {
console.error('选择图片失败:', error);
}
};
3. 多选图片
// ✅ 推荐: 多选图片
const pickMultipleImages = async () => {
try {
const images = await ImagePicker.openPicker({
multiple: true,
maxFiles: 9,
mediaType: 'photo',
compressImageQuality: 0.9,
});
console.log('选择的图片数量:', images.length);
} catch (error) {
console.error('选择图片失败:', error);
}
};
4. 相机拍照
// ✅ 推荐: 调用相机拍照
const takePhoto = async () => {
try {
const image = await ImagePicker.openCamera({
mediaType: 'photo',
cropping: true,
useFrontCamera: false,
compressImageQuality: 0.9,
cropperToolbarTitle: '编辑图片',
});
console.log('拍照成功:', image.path);
} catch (error) {
console.error('拍照失败:', error);
}
};
5. 图片裁剪
// ✅ 推荐: 裁剪指定图片
const cropImage = async (imagePath: string) => {
try {
const image = await ImagePicker.openCropper({
path: imagePath,
width: 300,
height: 400,
cropping: true,
cropperCircleOverlay: false,
freeStyleCropEnabled: false,
});
console.log('裁剪成功:', image.path);
} catch (error) {
console.error('裁剪失败:', error);
}
};
6. 缓存管理
// ✅ 推荐: 清理缓存
const cleanCache = async () => {
try {
// 清理所有缓存
await ImagePicker.clean();
// 清理单张图片缓存
await ImagePicker.cleanSingle(imagePath);
} catch (error) {
console.error('清理缓存失败:', error);
}
};
7. HarmonyOS 特殊处理
在 HarmonyOS 上使用时,需要注意:
- Autolink 支持: 版本 0.40.5 支持(RN 0.72),版本 0.50.2 和 0.51.2 不支持(RN 0.77/0.82)
- 权限配置: 必须在 module.json5 中配置相机和相册权限
- 导入库名: 使用
react-native-image-crop-picker导入 - 需要 Codegen: 使用前需要执行 Codegen 生成桥接代码
8. 最佳实践
// ✅ 推荐: 封装图片选择工具类
class ImagePickerHelper {
// 选择单张图片
static async pickSingleImage(options?: Options): Promise<ImageType | null> {
try {
const defaultOptions: Options = {
mediaType: 'photo',
cropping: true,
compressImageQuality: 0.9,
cropperToolbarTitle: '编辑图片',
cropperChooseText: '确定',
cropperCancelText: '取消',
...options,
};
const image = await ImagePicker.openPicker(defaultOptions);
return image;
} catch (error) {
console.error('选择图片失败:', error);
return null;
}
}
// 选择多张图片
static async pickMultipleImages(maxFiles: number = 9): Promise<ImageType[]> {
try {
const images = await ImagePicker.openPicker({
multiple: true,
maxFiles: maxFiles,
mediaType: 'photo',
compressImageQuality: 0.9,
});
return Array.isArray(images) ? images : [images];
} catch (error) {
console.error('选择图片失败:', error);
return [];
}
}
// 拍照
static async takePhoto(options?: Options): Promise<ImageType | null> {
try {
const defaultOptions: Options = {
mediaType: 'photo',
cropping: true,
useFrontCamera: false,
compressImageQuality: 0.9,
cropperToolbarTitle: '编辑图片',
...options,
};
const image = await ImagePicker.openCamera(defaultOptions);
return image;
} catch (error) {
console.error('拍照失败:', error);
return null;
}
}
// 裁剪图片
static async cropImage(imagePath: string, width: number = 300, height: number = 400): Promise<ImageType | null> {
try {
const image = await ImagePicker.openCropper({
path: imagePath,
mediaType: 'photo',
width: width,
height: height,
cropping: true,
});
return image;
} catch (error) {
console.error('裁剪失败:', error);
return null;
}
}
}
🧪 测试验证
1. Android 平台测试
npm run android
测试要点:
- 测试相册选择
- 验证相机拍照
- 测试图片裁剪
- 检查多选功能
- 验证缓存清理
2. iOS 平台测试
npm run ios
测试要点:
- 测试基本功能
- 验证权限请求
- 检查裁剪效果
- 测试图片压缩
3. HarmonyOS 平台测试
npm run harmony
测试要点:
- 验证 Codegen 配置
- 测试权限配置
- 检查基本功能
- 验证裁剪效果
4. 常见问题排查
问题 1: 无法打开相册
- 检查是否配置了
ohos.permission.READ_IMAGEVIDEO权限 - 确认权限的 usedScene 配置正确
- 验证应用是否已授予权限
问题 2: 无法调用相机
- 检查是否配置了
ohos.permission.CAMERA权限 - 确认相机权限的 reason 描述
- 验证设备是否有摄像头
问题 3: 裁剪功能异常
- 检查 cropping 参数是否设置为 true
- 确认裁剪相关的配置参数
- 验证图片路径是否正确
问题 4: 多选功能不生效
- 检查 multiple 参数是否设置为 true
- 确认 maxFiles 参数配置
- 验证返回的数据类型
📊 API 参考
核心方法
| 方法 | 描述 | 参数类型 | 返回类型 |
|---|---|---|---|
| openPicker() | 打开相册选择 | Options | Promise <Image | Image[]> |
| openCamera() | 打开相机拍照 | Options | Promise <Image> |
| openCropper() | 打开裁剪编辑 | Options | Promise <Image> |
| clean() | 清理所有缓存 | - | Promise <void> |
| cleanSingle() | 清理单张图片缓存 | string | Promise <void> |
Options 配置项
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| mediaType | string | ‘any’ | 媒体类型:photo/video |
| cropping | boolean | false | 是否启用裁剪 |
| multiple | boolean | false | 是否支持多选 |
| maxFiles | number | 5 | 最大选择数量 |
| minFiles | number | 1 | 最小选择数量 |
| includeBase64 | boolean | false | 是否包含 Base64 数据 |
| compressImageQuality | number | 1 | 图片压缩质量(0-1) |
| compressImageMaxWidth | number | - | 最大宽度 |
| compressImageMaxHeight | number | - | 最大高度 |
| cropperToolbarTitle | string | - | 裁剪标题栏文本 |
| cropperChooseText | string | - | 确定按钮文本 |
| cropperCancelText | string | - | 取消按钮文本 |
| cropperChooseColor | string | - | 确定按钮颜色 |
| cropperCancelColor | string | - | 取消按钮颜色 |
| showCropGuidelines | boolean | true | 显示裁剪辅助线 |
| showCropFrame | boolean | true | 显示裁剪框 |
| freeStyleCropEnabled | boolean | false | 自由裁剪 |
| cropperCircleOverlay | boolean | false | 圆形裁剪 |
| enableRotationGesture | boolean | true | 启用旋转手势 |
| useFrontCamera | boolean | false | 是否使用前置相机 |
Image 返回数据
| 属性 | 类型 | 描述 |
|---|---|---|
| path | string | 图片文件路径 |
| width | number | 图片宽度 |
| height | number | 图片高度 |
| size | number | 文件大小 |
| mime | string | MIME 类型 |
| data | string | Base64 数据 |
| modificationDate | string | 修改日期 |
| source | string | 图片来源 |
📝 总结
通过集成 react-native-image-crop-picker,我们为项目添加了完整的图片选择与裁剪功能。这个库功能强大、易于使用,支持从相册选择、相机拍照、图片裁剪等多种操作,广泛应用于头像上传、图片编辑、相册管理等场景,是实现图片处理功能的首选库。
关键要点回顾
- ✅ 安装依赖:
npm install @react-native-ohos/react-native-image-crop-picker - ✅ 配置平台: 版本 0.40.5 支持 Autolink(RN 0.72),版本 0.50.2 和 0.51.2 不支持(RN 0.77/0.82)
- ✅ Codegen: 需要执行生成三方库桥接代码
- ✅ 导入库名: 使用
react-native-image-crop-picker导入 - ✅ 权限配置: 必须在 module.json5 中配置相机和相册权限
- ✅ 功能完整: 支持相册选择、相机拍照、图片裁剪、缓存管理
- ✅ 高度可定制: 支持丰富的配置选项
实际效果
- Android: 完整支持,功能丰富
- iOS: 完整支持,功能丰富
- HarmonyOS: 完整支持,需要配置权限
更多推荐


所有评论(0)