UNPKG

@ohmi/react-native-image-crop-picker

Version:

Select single or multiple images, with cropping option

1,092 lines (880 loc) 55.1 kB
>模板版本:v0.3.0 <p align="center"> <h1 align="center"> <code>@ohmi/react-native-image-crop-picker</code> </h1> </p> ## 说明 本项目基于 [react-native-image-crop-picker@0.40.3](https://github.com/ivpusic/react-native-image-crop-picker/tree/v0.40.3) 开发。 ## 1.安装与使用 进入到工程目录并输入以下命令: <!-- tabs:start --> #### **npm** ```bash npm install @ohmi/react-native-image-crop-picker ``` #### **yarn** ```bash yarn add @ohmi/react-native-image-crop-picker ``` <!-- tabs:end --> 下面的代码展示了这个库的基本使用场景: > [!TIP] 使用时 import 的库名不变。 ```jsx import ImagePicker from 'react-native-image-crop-picker'; import { openPicker } from 'react-native-image-crop-picker'; import React from 'react'; import { Text, StyleSheet, TextInput, View, Button, ScrollView, Switch } from 'react-native'; const ImageCropPickDemo = () => { const TAG: string = 'ImageCropPickerTurboModule'; const [maxFiles, setMaxFiles] = React.useState(''); const [imageQuality, setImageQuality] = React.useState(''); const [imagePath, setImagePath] = React.useState(''); const [clearImagePath, setClearImagePath] = React.useState(''); const [cropperTitle, setCropperTitle] = React.useState(''); const [chooseText, setChooseText] = React.useState(''); const [chooseColor, setChooseColor] = React.useState(''); const [cancelText, setCancelText] = React.useState(''); const [cancelColor, setCancelColor] = React.useState(''); const [cropperRotate, setCropperRotate] = React.useState(false); const [showCropGuidelines, setShowCropGuidelines] = React.useState(true); const [showCropFrame, setShowCropFrame] = React.useState(true); const [multiple, setMultiple] = React.useState(false); const [includeExif, setIncludeExif] = React.useState(false); const [avoidEmptySpace, setAvoidEmptySpace] = React.useState(false); const [writeTempFile, setTempFile] = React.useState(true); const [includeBase64, setBase64] = React.useState(false); const [freeStyleCropEnabled, setFreeStyleCropEnabled] = React.useState(false); const [forceJpg, setForceJpg] = React.useState(false); const [showsSelectedCount, setShowsSelectedCount] = React.useState(true); const [selectedButton, setSelectedButton] = React.useState('any'); const [useFrontCamera, setUseFrontCamera] = React.useState(false); const [croppingCamera, setCroppingCamera] = React.useState(false); const [writeTempFileCamera, setTempFileCamera] = React.useState(true); const [includeBase64Camera, setBase64Camera] = React.useState(false); const [includeExifCamera, setIncludeExifCamera] = React.useState(false); const [avoidEmptySpaceCamera, setAvoidEmptySpaceCamera] = React.useState(false); const [freeStyleCropEnabledCamera, setFreeStyleCropEnabledCamera] = React.useState(false); const [forceJpgCamera, setForceJpgCamera] = React.useState(false); const [mediaTypeCamera, setMediaTypeCamera] = React.useState('any'); const [imageQualityCamera, setImageQualityCamera] = React.useState(''); const [cropperTitleCamera, setCropperTitleCamera] = React.useState(''); const [chooseTextCamera, setChooseTextCamera] = React.useState(''); const [chooseColorCamera, setChooseColorCamera] = React.useState(''); const [cancelTextCamera, setCancelTextCamera] = React.useState(''); const [cancelColorCamera, setCancelColorCamera] = React.useState(''); const [cropperRotateCamera, setCropperRotateCamera] = React.useState(false); const [showCropGuidelinesCamera, setShowCropGuidelinesCamera] = React.useState(true); const [showCropFrameCamera, setShowCropFrameCamera] = React.useState(true); const [writeTempFileCropper, setTempFileCropper] = React.useState(true); const [forceJpgCropper, setForceJpgCropper] = React.useState(false); const [includeBase64Cropper, setBase64Cropper] = React.useState(false); const [includeExifCropper, setIncludeExifCropper] = React.useState(false); const [avoidEmptySpaceCropper, setAvoidEmptySpaceCropper] = React.useState(false); const [freeStyleCropEnabledCropper, setFreeStyleCropEnabledCropper] = React.useState(false); const [imageQualityCropper, setimageQualityCropper] = React.useState(''); const handleButtonPress = (buttonName) => { setSelectedButton(buttonName); }; const handleMediaType = (buttonName) => { setMediaTypeCamera(buttonName); }; return ( <ScrollView style={styles.container}> <Text style={styles.title}>相机、图库、裁剪功能:</Text> <View style={styles.container}> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>multiple:</Text> <Button title={`${multiple}`} onPress={() => multiple ? setMultiple(false) : setMultiple(true)} /> <Text style={styles.inputLable}>writeTempFile:</Text> <Button title={`${writeTempFile}`} onPress={() => writeTempFile ? setTempFile(false) : setTempFile(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>includeBase64:</Text> <Button title={`${includeBase64}`} onPress={() => includeBase64 ? setBase64(false) : setBase64(true)} /> <Text style={styles.inputLable}>includeExif:</Text> <Button title={`${includeExif}`} onPress={() => includeExif ? setIncludeExif(false) : setIncludeExif(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>avoidEmptySpaceAroundImage :</Text> <Button title={`${avoidEmptySpace}`} onPress={() => avoidEmptySpace ? setAvoidEmptySpace(false) : setAvoidEmptySpace(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>freeStyleCropEnabled :</Text> <Button title={`${freeStyleCropEnabled}`} onPress={() => freeStyleCropEnabled ? setFreeStyleCropEnabled(false) : setFreeStyleCropEnabled(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>forceJpg:</Text> <Button title={`${forceJpg}`} onPress={() => forceJpg ? setForceJpg(false) : setForceJpg(true)} /> <Text style={styles.inputLable}>showsSelectedCount:</Text> <Button title={`${showsSelectedCount}`} onPress={() => showsSelectedCount ? setShowsSelectedCount(false) : setShowsSelectedCount(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>mediaType:</Text> <Button title='photo' onPress={() => handleButtonPress('photo')} accessibilityState={{ selected: selectedButton === 'photo' }} /> <Button title='video' onPress={() => handleButtonPress('video')} accessibilityState={{ selected: selectedButton === 'video' }} /> <Button title='any' onPress={() => handleButtonPress('any')} accessibilityState={{ selected: selectedButton === 'any' }} /> </View> <View style={styles.TextInputBox}> <Text style={{color:'red'}}>mediaType is {selectedButton}</Text> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>minFiles:</Text> <Text style={styles.inputLable}>1</Text> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>maxFiles:</Text> <TextInput keyboardType="numeric" maxLength={1} style={styles.numberInput} onChangeText={setMaxFiles} value={maxFiles} /> <Text style={styles.lableType}>(number)</Text> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>compressImageQuality:</Text> <TextInput style={styles.numberInput} onChangeText={setImageQuality} value={imageQuality} /> <Text style={styles.lableType}>(0.1 到 1)</Text> </View> <View > <View style={styles.buttonSty}> <Button title='openPicker(打开图库)' onPress={() => { openPicker({ multiple: multiple, writeTempFile: writeTempFile, includeBase64: includeBase64, includeExif: includeExif, avoidEmptySpaceAroundImage: avoidEmptySpace, freeStyleCropEnabled: freeStyleCropEnabled, forceJpg: forceJpg, showsSelectedCount: showsSelectedCount, mediaType: selectedButton, minFiles: 1, maxFiles: maxFiles, compressImageQuality: imageQuality, }).then(image => { console.log(TAG + ' openPicker result ' + JSON.stringify(image)) }); }} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>cropping:</Text> <Button title={`${croppingCamera}`} onPress={() => croppingCamera ? setCroppingCamera(false) : setCroppingCamera(true)} /> <Text style={styles.inputLable}>writeTempFile:</Text> <Button title={`${writeTempFileCamera}`} onPress={() => writeTempFileCamera ? setTempFileCamera(false) : setTempFileCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>includeBase64:</Text> <Button title={`${includeBase64Camera}`} onPress={() => includeBase64Camera ? setBase64Camera(false) : setBase64Camera(true)} /> <Text style={styles.inputLable}>includeExif:</Text> <Button title={`${includeExifCamera}`} onPress={() => includeExifCamera ? setIncludeExifCamera(false) : setIncludeExifCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>avoidEmptySpaceAroundImage :</Text> <Button title={`${avoidEmptySpaceCamera}`} onPress={() => avoidEmptySpaceCamera ? setAvoidEmptySpaceCamera(false) : setAvoidEmptySpaceCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>freeStyleCropEnabled :</Text> <Button title={`${freeStyleCropEnabledCamera}`} onPress={() => freeStyleCropEnabledCamera ? setFreeStyleCropEnabledCamera(false) : setFreeStyleCropEnabledCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>useFrontCamera:</Text> <Button title={`${useFrontCamera}`} onPress={() => useFrontCamera ? setUseFrontCamera(false) : setUseFrontCamera(true)} /> <Text style={styles.inputLable}>forceJpg:</Text> <Button title={`${forceJpgCamera}`} onPress={() => forceJpgCamera ? setForceJpgCamera(false) : setForceJpgCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>mediaType:</Text> <Button title='photo' onPress={() => handleMediaType('photo')} accessibilityState={{ selected: mediaTypeCamera === 'photo' }} /> <Button title='video' onPress={() => handleMediaType('video')} accessibilityState={{ selected: mediaTypeCamera === 'video' }} /> <Button title='any' onPress={() => handleMediaType('any')} accessibilityState={{ selected: mediaTypeCamera === 'any' }} /> </View> <View style={styles.TextInputBox}> <Text style={{ color: 'red' }}>mediaType is {mediaTypeCamera}</Text> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>compressImageQuality:</Text> <TextInput style={styles.numberInput} onChangeText={setImageQualityCamera} value={imageQualityCamera} /> <Text style={styles.lableType}>(0.1 到 1)</Text> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperToolbarTitle:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={5} onChangeText={(value) => setCropperTitleCamera(value)} value={cropperTitleCamera} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperChooseText:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={5} onChangeText={(value) => setChooseTextCamera(value)} value={chooseTextCamera} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperChooseColor:例如 #FF0000</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={7} onChangeText={(value) => setChooseColorCamera(value)} value={chooseColorCamera} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperCancelText:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={5} onChangeText={(value) => setCancelTextCamera(value)} value={cancelTextCamera} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperCancelColor:例如 #FF0000</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={7} onChangeText={(value) => setCancelColorCamera(value)} value={cancelColorCamera} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>cropperRotateButtonsHidden:</Text> <Button title={`${cropperRotateCamera}`} onPress={() => cropperRotateCamera ? setCropperRotateCamera(false) : setCropperRotateCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>showCropGuidelines:</Text> <Button title={`${showCropGuidelinesCamera}`} onPress={() => showCropGuidelinesCamera ? setShowCropGuidelinesCamera(false) : setShowCropGuidelinesCamera(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>showCropFrame:</Text> <Button title={`${showCropFrameCamera}`} onPress={() => showCropFrameCamera ? setShowCropFrameCamera(false) : setShowCropFrameCamera(true)} /> </View> <View style={styles.buttonSty}> <Button title="openCamera (打开相机)" onPress={() => { ImagePicker.openCamera({ cropping: croppingCamera, writeTempFile: writeTempFileCamera, includeBase64: includeBase64Camera, includeExif: includeExifCamera, avoidEmptySpaceAroundImage: avoidEmptySpaceCamera, freeStyleCropEnabled: freeStyleCropEnabledCamera, useFrontCamera: useFrontCamera, forceJpg: forceJpgCamera, mediaType: mediaTypeCamera, compressImageQuality: imageQualityCamera, cropperToolbarTitle: cropperTitleCamera, cropperChooseText: chooseTextCamera, cropperChooseColor: chooseColorCamera, cropperCancelText: cancelTextCamera, cropperCancelColor: cancelColorCamera, cropperRotateButtonsHidden: cropperRotateCamera, showCropGuidelines: showCropGuidelinesCamera, showCropFrame: showCropFrameCamera, }).then(image => { console.log(TAG + ' openCamera result ' + JSON.stringify(image)) }); }} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>请输入需要裁剪的图片地址:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} onChangeText={(value) => setImagePath(value)} value={imagePath} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>writeTempFile:</Text> <Button title={`${writeTempFileCropper}`} onPress={() => writeTempFileCropper ? setTempFileCropper(false) : setTempFileCropper(true)} /> <Text style={styles.inputLable}>forceJpg:</Text> <Button title={`${forceJpgCropper}`} onPress={() => forceJpgCropper ? setForceJpgCropper(false) : setForceJpgCropper(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>includeBase64:</Text> <Button title={`${includeBase64Cropper}`} onPress={() => includeBase64Cropper ? setBase64Cropper(false) : setBase64Cropper(true)} /> <Text style={styles.inputLable}>includeExif:</Text> <Button title={`${includeExifCropper}`} onPress={() => includeExifCropper ? setIncludeExifCropper(false) : setIncludeExifCropper(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>avoidEmptySpaceAroundImage :</Text> <Button title={`${avoidEmptySpaceCropper}`} onPress={() => avoidEmptySpaceCropper ? setAvoidEmptySpaceCropper(false) : setAvoidEmptySpaceCropper(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>freeStyleCropEnabled :</Text> <Button title={`${freeStyleCropEnabledCropper}`} onPress={() => freeStyleCropEnabledCropper ? setFreeStyleCropEnabledCropper(false) : setFreeStyleCropEnabledCropper(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>compressImageQuality:</Text> <TextInput style={styles.numberInput} onChangeText={setimageQualityCropper} value={imageQualityCropper} /> <Text style={styles.lableType}>(0.1 到 1)</Text> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperToolbarTitle:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={5} onChangeText={(value) => setCropperTitle(value)} value={cropperTitle} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperChooseText:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={5} onChangeText={(value) => setChooseText(value)} value={chooseText} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperChooseColor:例如 #FF0000</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={7} onChangeText={(value) => setChooseColor(value)} value={chooseColor} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperCancelText:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={5} onChangeText={(value) => setCancelText(value)} value={cancelText} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>cropperCancelColor:例如 #FF0000</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} maxLength={7} onChangeText={(value) => setCancelColor(value)} value={cancelColor} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>cropperRotateButtonsHidden:</Text> <Button title={`${cropperRotate}`} onPress={() => cropperRotate ? setCropperRotate(false) : setCropperRotate(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>showCropGuidelines:</Text> <Button title={`${showCropGuidelines}`} onPress={() => showCropGuidelines ? setShowCropGuidelines(false) : setShowCropGuidelines(true)} /> </View> <View style={styles.TextInputBox}> <Text style={styles.inputLable}>showCropFrame:</Text> <Button title={`${showCropFrame}`} onPress={() => showCropFrame ? setShowCropFrame(false) : setShowCropFrame(true)} /> </View> <View style={styles.buttonSty}> <Button title='openCropper(打开裁剪)' onPress={() => { ImagePicker.openCropper({ path: imagePath, width: 300, height: 400, writeTempFile: writeTempFileCropper, includeBase64: includeBase64Cropper, includeExif: includeExifCropper, avoidEmptySpaceAroundImage: avoidEmptySpaceCropper, freeStyleCropEnabled: freeStyleCropEnabledCropper, compressImageQuality: imageQualityCropper, forceJpg: forceJpgCropper, cropperToolbarTitle: cropperTitle, cropperChooseText: chooseText, cropperChooseColor: chooseColor, cropperCancelText: cancelText, cropperCancelColor: cancelColor, cropperRotateButtonsHidden: cropperRotate, showCropGuidelines: showCropGuidelines, showCropFrame: showCropFrame, }).then((image => { console.log(TAG + ' openCropper result ' + JSON.stringify(image)) })) }} /> </View> </View> </View> <Text style={styles.title}>清除文件:</Text> <View style={styles.buttonBox}> <View style={styles.buttonSty}> <Button title='clean (清除所有文件)' onPress={() => { ImagePicker.clean({}).then(image => { console.log(TAG + ' clean result ' + JSON.stringify(image)) }); }} /> </View> <View style={styles.TextInputBox}> <Text style={styles.textLable}>请输入需要清除图片地址:</Text> </View> <View style={styles.TextInputBox}> <TextInput style={styles.textInput} onChangeText={(value) => setClearImagePath(value)} value={clearImagePath} /> </View> <View style={styles.buttonSty}> <Button title='cleanSingle (清除单个文件)' onPress={() => { console.log(TAG + " cleanSingle path " + clearImagePath) ImagePicker.cleanSingle('/data/storage/el2/base/haps/entry/temp/rn_image_crop_picker_lib_temp_' + clearImagePath).then(image => { }) }} /> </View> <View style={styles.emptyView}></View> </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { }, TextInputBox: { flexDirection: 'row', alignItems: 'center', margin: 10, }, textLable: { width: '100%' }, emptyView: { width: 50, height: 500 }, inputLable: { width: 'auto' }, lableType: { width: '18%' }, numberInput: { width: 50, height: 30, color: 'black', borderColor: 'gray', borderWidth: 1 }, textInput: { width: '50%', height: 36, color: 'black', borderColor: 'gray', bordeWidth: 1 }, switchType: { width: 60, height: 36 }, buttonBox: { marginTop: 20, }, buttonSty: { marginTop: 0, marginRight: 60, marginBottom: 20, marginLeft: 60, textAlign: 'center' }, title: { fontWeight: '500', fontSize: 20, marginTop: 10, } }); export default ImageCropPickDemo; ``` ## 2. Manual Link 此步骤为手动配置原生依赖项的指导。 首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 `harmony`。 ### 2.1. Overrides RN SDK 为了让工程依赖同一个版本的 RN SDK,需要在工程根目录的 `oh-package.json5` 添加 overrides 字段,指向工程需要使用的 RN SDK 版本。替换的版本既可以是一个具体的版本号,也可以是一个模糊版本,还可以是本地存在的 HAR 包或源码目录。 关于该字段的作用请阅读[官方说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-oh-package-json5-V5#zh-cn_topic_0000001792256137_overrides) ```json { "overrides": { "@rnoh/react-native-openharmony": "^0.72.38" // ohpm 在线版本 // "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // 指向本地 har 包的路径 // "@rnoh/react-native-openharmony" : "./react_native_openharmony" // 指向源码路径 } } ``` ### 2.2. 引入原生端代码 目前有两种方法: - 通过 har 包引入(推荐) - 直接链接源码。 方法一:通过 har 包引入 > [!TIP] har 包位于三方库安装路径的 `harmony` 文件夹下。 打开 `entry/oh-package.json5`,添加以下依赖 ```json "dependencies": { "@rnoh/react-native-openharmony": "file:../react_native_openharmony", "@ohmi/react-native-image-crop-picker": "file:../../node_modules/@ohmi/react-native-image-crop-picker/harmony/image_crop_picker.har" } ``` 点击右上角的 `sync` 按钮 或者在终端执行: ``` cd entry ohpm install ``` 方法二:直接链接源码 > [!TIP] 如需使用直接链接源码,请参考[直接链接源码说明](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/link-source-code.md) ### 2.3. 配置 CMakeLists 和引入 ImageCropPickerPackage > [!TIP] 版本 `0.40.4` 及以上需要 打开 `entry/src/main/cpp/CMakeLists.txt`,添加: ```diff project(rnapp) cmake_minimum_required(VERSION 3.4.1) set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp") 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}/@ohmi/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} + ${IMAGE_CROP_PICKER_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_crop_picker) # RNOH_END: manual_package_linking_2 ``` 打开 `entry/src/main/cpp/PackageProvider.cpp`,添加: ```diff #include "RNOH/PackageProvider.h" #include "SamplePackage.h" + #include "ImageCropPickerPackage.h" using namespace rnoh; std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) { return { std::make_shared<SamplePackage>(ctx), + std::make_shared<ImageCropPickerPackage>(ctx), }; } ``` ### 2.4. 在 ArkTs 侧引入 ImageCropPickerPackage 打开 `entry/src/main/ets/RNPackagesFactory.ts`,添加: ```diff ... + import { ImageCropPickerPackage } from '@ohmi/react-native-image-crop-picker/ts'; export function createRNPackages(ctx: RNPackageContext): RNPackage[] { return [ new SamplePackage(ctx), + new ImageCropPickerPackage(ctx), ]; } ``` ### 2.5. 配置Entry **(1)在 entry/src/main/ets/entryability 下创建 ImageEditAbility.ets** ``` import UIAbility from '@ohos.app.ability.UIAbility' import window from '@ohos.window' 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) => { 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)在 entry/src/main/module.json5 注册 ImageEditAbility** ```diff "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)在 entry/src/main/ets/pages 下创建 ImageEdit.ets** ``` import { ImageEditInfo } from '@ohmi/react-native-image-crop-picker'; @Entry @Component struct ImageEdit { build() { Row(){ Column(){ ImageEditInfo(); } .width('100%') } .height('100%') } } ``` **(4)在 entry/src/main/resources/base/profile/main_pages.json 添加配置** ``` { "src": [ "pages/Index", "pages/ImageEdit" ] } ``` ### 2.6. 运行 点击右上角的 `sync` 按钮 或者在终端执行: ``` cd entry ohpm install ``` 然后编译、运行即可。 ## 3. 约束与限制 ### 3.1. 兼容性 要使用此库,需要使用正确的 React-Native 和 RNOH 版本。另外,还需要使用配套的 DevEco Studio 和 手机 ROM。 请到三方库相应的 Releases 发布地址查看 Release 配套的版本信息:[@ohmi/react-native-image-crop-picker Releases ](https://gitee.com/rn-mi/react-native-image-crop-picker/releases) ## 4. API > [!TIP] "Platform"列表示该属性在原三方库上支持的平台。 > [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性;no 则表示不支持;partially 表示部分支持。使用方法跨平台一致,效果对标 iOS 或 Android 的效果。 | Name | Description | Type | Required | Platform | HarmonyOS Support | | ----------- | ------------------------------------------------------------ | -------- | -------- | ----------- | ----------------- | | openPicker | Call single image picker with cropping | function | no | iOS/Android | yes | | clean | Module is creating tmp images which are going to be cleaned up automatically somewhere in the future. If you want to force cleanup, you can use `clean` to clean all tmp files, or `cleanSingle(path)` to clean single tmp file. | function | no | iOS/Android | yes | | openCropper | Crop image and rotate, | function | no | iOS/Android | yes | | cleanSingle | Delete a single cache file | function | no | iOS/Android | yes | | openCamera | Select from camera | function | no | iOS/Android | yes | ## 5. 属性 > [!TIP] "Platform"列表示该属性在原三方库上支持的平台。 > [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性;no 则表示不支持;partially 表示部分支持。使用方法跨平台一致,效果对标 iOS 或 Android 的效果。 **cropData** | Name | Type | Description | Required | Platform | HarmonyOS Support | | ----------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | -------- | -------- | | cropping | bool (default false) | Enable or disable cropping | no | All | yes | | width | number | Width of result image when used with `cropping` option | no | All | yes | | height | number | Height of result image when used with `cropping` option | no | All | yes | | multiple | bool (default false) | Enable or disable multiple image selection | no | All | yes | | writeTempFile (iOS only) | bool (default true) | When set to false, does not write temporary files for the selected images. This is useful to improve performance when you are retrieving file contents with the `includeBase64` option and don't need to read files from disk. | no | iOS | yes | | includeBase64 | bool (default false) | When set to true, the image file content will be available as a base64-encoded string in the `data` property. Hint: To use this string as an image source, use it like: `<Image source={{uri: `data:image.mime;base64,𝑖𝑚𝑎𝑔𝑒.𝑚𝑖𝑚𝑒;𝑏𝑎𝑠𝑒64,{image.data}`}} />` | no | All | yes | | includeExif | bool (default false) | Include image exif data in the response | no | All | yes | | avoidEmptySpaceAroundImage (iOS only) | bool (default true) | When set to true, the image will always fill the mask space. | no | iOS | no | | cropperActiveWidgetColor (Android only) | string (default `"#424242"`) | When cropping image, determines ActiveWidget color. | no | Android | no | | cropperStatusBarColor (Android only) | string (default `#424242`) | When cropping image, determines the color of StatusBar. | no | Android | no | | cropperToolbarColor (Android only) | string (default `#424242`) | When cropping image, determines the color of Toolbar. | no | Android | no | | cropperToolbarWidgetColor (Android only) | string (default `darker orange`) | When cropping image, determines the color of Toolbar text and buttons. | no | Android | no | | freeStyleCropEnabled | bool (default false) | Enables user to apply custom rectangle area for cropping | no | All | yes | | cropperToolbarTitle | string (default `Edit Photo`) | When cropping image, determines the title of Toolbar. | no | All | yes | | cropperCircleOverlay | bool (default false) | Enable or disable circular cropping mask. | no | All | no | | disableCropperColorSetters (Android only) | bool (default false) | When cropping image, disables the color setters for cropping library. | no | Android | no | | minFiles (iOS only) | number (default 1) | Min number of files to select when using `multiple` option | no | iOS | no | | maxFiles (iOS only) | number (default 5) | Max number of files to select when using `multiple` option | no | iOS | yes | | waitAnimationEnd (iOS only) | bool (default true) | Promise will resolve/reject once ViewController `completion` block is called | no | iOS | no | | smartAlbums (iOS only) | array ([supported values](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fivpusic%2Freact-native-image-crop-picker%2Fblob%2Fmaster%2FREADME.md%23smart-album-types-ios)) (default ['UserLibrary', 'PhotoStream', 'Panoramas', 'Videos', 'Bursts']) | List of smart albums to choose from | no | iOS | no | | useFrontCamera | bool (default false) | Whether to default to the front/'selfie' camera when opened. Please note that not all Android devices handle this parameter, see [issue #1058](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fivpusic%2Freact-native-image-crop-picker%2Fissues%2F1058) | no | All | yes | | compressVideoPreset (iOS only) | string (default MediumQuality) | Choose which preset will be used for video compression | no | iOS | no | | compressImageMaxWidth | number (default none) | Compress image with maximum width | no | All | no | | compressImageMaxHeight | number (default none) | Compress image with maximum height | no | All | no | | compressImageQuality | number (default 1 (Android)/0.8 (iOS)) | Compress image with quality (from 0 to 1, where 1 is best quality). On iOS, values larger than 0.8 don't produce a noticeable quality increase in most images, while a value of 0.8 will reduce the file size by about half or less compared to a value of 1. | no | All | yes | | loadingLabelText (iOS only) | string (default "Processing assets...") | Text displayed while photo is loading in picker | no | iOS | no | | mediaType | string (default any) | Accepted mediaType for image selection, can be one of: 'photo', 'video', or 'any' | no | All | yes | | showsSelectedCount (iOS only) | bool (default true) | Whether to show the number of selected assets | no | iOS | no | | sortOrder (iOS only) | string (default 'none', supported values: 'asc', 'desc', 'none') | Applies a sort order on the creation date on how media is displayed within the albums/detail photo views when opening the image picker | no | iOS | no | | forceJpg (iOS only) | bool (default false) | Whether to convert photos to JPG. This will also convert any Live Photo into its JPG representation | no | iOS | yes | | showCropGuidelines (Android only) | bool (default true) | Whether to show the 3x3 grid on top of the image during cropping | no | Android | yes | | showCropFrame (Android only) | bool (default true) | Whether to show crop frame during cropping | no | Android | yes | | hideBottomControls (Android only) | bool (default false) | Whether to display bottom controls | no | Android | no | | enableRotationGesture (Android only) | bool (default false) | Whether to enable rotating the image by hand gesture | no | Android | yes | | cropperChooseText (iOS only) | string (default choose) | Choose button text | no | iOS | yes | | cropperChooseColor (iOS only) | string (default `#FFCC00`) | HEX format color for the Choose button. [Default color is controlled by TOCropViewController](https://gitee