更新记录

1.0.2(2022-11-25)

更新依赖 适配 Android12 和 Android13

1.0.1(2022-03-11)

原生 Android IOS 图片裁剪,支持比例裁剪和头像裁剪,支持自定义UI等。解决目前市场裁剪的痛点。


平台兼容性

Android Android CPU类型 iOS
适用版本区间:5.0 - 11.0 armeabi-v7a:支持,arm64-v8a:支持,x86:未测试 适用版本区间:9 - 15

原生插件通用使用流程:

  1. 购买插件,选择该插件绑定的项目。
  2. 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
  3. 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
  4. 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
  5. 开发完毕后正式云打包

付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios

注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择


配置说明

如果aspect_ratio_presets没有你想要的比例可以设置ratio_x和ratio_y来配置你的裁剪比例
常用预设比例
    [ "original"(1x1), "square"(原始), "ratio3x2", "ratio5x3", "ratio4x3", "ratio5x4", "ratio7x5", "ratio16x9"]

{   
    source_path: "", //文件路径 代码会判断文件是否存在
    max_width: 10, //裁剪宽度 不能小于 10
    max_height: 10, //裁剪高度 不能小于 10
    crop_style: "circle", //裁剪类型 circle or rectangle
    ratio_x: 4, //自定义裁剪比例 x 正方形 int类型
    ratio_y: 1, //自定义裁剪比例 y 正方形 int类型
    compress_format: "png", //压缩格式 jpg or png
    compress_quality: "90", //压缩质量 0 - 100 默认90 非法报错哦
    aspect_ratio_presets: ["square", "ratio3x2", "original", "ratio4x3", "ratio16x9"], //底部菜单栏裁剪比率预览
    android_ui: { //android 界面配置
        toolbar_title: "裁剪", //标题
        toolbar_color: "#FF4400", //标题栏背景
        statusbar_color: "#FF4400", //状态栏背景
        toolbar_widget_color: "#FFFFFF", //标题栏字体图标颜色
        background_color: "#000000", //裁剪栏背景颜色1 默认黑色
        active_controls_widget_color: "#000000", //菜单栏选中颜色
        dimmed_layer_color: "#000000", //裁剪栏背景颜色2 先显示 background_color 在显示 dimmed_layer_color
        crop_frame_color: "#000000", //裁剪边框颜色
        crop_grid_color: "#000000", //裁剪框颜色
        crop_frame_stroke_width: 1, //裁剪边框 宽度
        crop_grid_row_count: 4, //裁剪框横线数量
        crop_grid_column_count: 4, //裁剪框纵线数量
        crop_grid_stroke_width: 1, //裁剪中框 宽度
        show_crop_grid: false, //显示裁剪框
        show_crop_frame: false,//显示裁剪边框
        lock_aspect_ratio: false, //移动图片
        hide_bottom_controls: false, //隐藏底部菜单栏
        init_aspect_ratio: "original" //菜单栏选中比例
    },
    ios_ui: {
        title:"裁剪", //出现在视图控制器顶部的标题文本。
        done_button_title:"完成", //“完成”按钮的标题。设置这个会覆盖默认值它是Done的本地化字符串
        cancel_button_title:"取消",//“取消”按钮的标题。设置这个会覆盖默认值它是"Cancel"的本地化字符串
        minimum_aspect_ratio: 1.0, //最小作物长宽比。如果设置了,用户将不能将裁剪矩形设置为比参数定义的宽高比更低的宽高比
        rect_x: 1.0, //裁剪的初始矩形:x
        rect_y: 1.0, //裁剪的初始矩形:y
        rect_width: 1.0, //裁剪的初始矩形:宽度。
        rect_height: 1.0, //裁剪的初始矩形:高度。
        show_activity_sheet_on_done: false, // 如果为真,当用户点击'Done'时,一个UIActivityController会在视图控制器结束之前出现
        show_cancel_confirmation_dialog: false, // 当用户点击“取消”时显示一个确认对话框,并且有未决的更改(默认为false)
        rotate_clockwise_button_hidden: false, // 禁用时,工具栏中会显示一个额外的旋转按钮,该按钮以顺时针方向将画布旋转90度段(默认为false)
        hides_navigation_bar: false, // 如果这个控制器嵌入在UINavigationController中,它的导航栏默认是隐藏的。将此属性设置为false以显示导航栏。必须在呈现此控制器之前设置此参数
        rotate_button_hidden: false, // 当启用时,隐藏旋转按钮,以及当showClockwiseRotationButton设置为YES时可见的替代旋转按钮(默认为false)
        reset_button_hidden: false, // 启用时,隐藏工具栏上的“Reset”按钮(默认为false)
        aspect_ratio_picker_button_hidden: false, // 启用时,隐藏工具栏上的“长宽比选择器”按钮(默认为false)
        reset_aspect_ratio_enabled: true, // 如果为真,点击重置按钮也会将宽高比重置为图像默认比例。否则,重置将缩小到当前的宽高比。如果这个设置为false,而aspectRatioLockEnabled设置为true,那么宽高比按钮将自动从工具栏中隐藏(默认为true)
        aspect_ratio_lock_dimension_swap_enabled: false, //如果为true,则设置自定义的宽高比,aspectRatioLockEnabled设置为true,作物框将根据纵向或横向大小的图像交换其尺寸。该值还控制图像旋转时尺寸是否可以交换(默认为false)。
        aspect_ratio_lock_enabled: false, //如果为真,虽然它仍然可以调整大小,作物盒将被锁定到其当前的长宽比。如果这个设置为true,并且resetAspectRatioEnabled设置为false,那么宽高比按钮将自动从工具栏中隐藏(默认为false)
    }
}

重要的事情说三遍 重要的事情说三遍 重要的事情说三遍

已适配Android12 和 Android13 !!!!!!

有问题咨询QQ群 - 838332280 支持离线版和源码版的购买 需要加群交流

目前图片选择使用的uniapp官方的(plus.gallery.pick((res)=> {}))该插件仅仅负责裁剪 。Android 和 IOS 的文件路径因为系统权限不一样,所以使用的时候多注意一点,具体参考如下的使用案例或者插件使用示例工程

上述 uniapp官方的(plus.gallery.pick((res)=> {}) 图片选择插件未适配Android12 Android13 弃用!!!!

使用 uni.chooseImage时官方最新HBuilder选择模块之后运行会报无模块错误!!!!

建议使用案例1 导入原生插件【原生 Android IOS 文件选择】使用

使用案例1

使用 低调推荐】 原生 Android IOS 文件选择 插件选择图片

<template>
    <view class="content">
        <image class="logo" src="/static/logo.png"></image>
        <button type="primary" @click="choosePicture">选择图片</button>
        <view class="image-content">
            <image style="width: 200px; height: 200px; background-color: #eeeeee;" mode="scaleToFill" :src="result"
                @error="imageError"></image>
        </view>
        <view class="textbox">
            <text>{{pickResult}}</text>
        </view>
    </view>
</template>

<script setup lang="ts">
    import {
        onUnmounted,
        ref
    } from "vue";
    var uniModule = uni.requireNativePlugin("RLUni-EasyCropModule")
    var filePickerModule = uni.requireNativePlugin("RLUni-EasyFilePickerModule")
    var result = ref('')
    var pickResult = ref('')

    onUnmounted(() => {
        filePickerModule.clear()
    })

    function imageError(e: any) {
        console.error(e.detail.errMsg)
    }

    function choosePicture() {

        filePickerModule.pickFiles({
                'type': 'image',
                'allowedExtensions': '',
                'allowMultiple': false,
            },
            (ret: any) => {
                console.log(JSON.stringify(ret))
                const path = ret[0].path
                console.log(path)
                uniModule.cropImage({
                    source_path: path,
                    crop_style: "rectangle",
                    compress_format: "png",
                    compress_quality: "90",
                    aspect_ratio_presets: ["square", "ratio3x2", "original", "ratio4x3", "ratio16x9"],
                    // aspect_ratio_presets: ["original", "square", "ratio3x2", "ratio4x3", "ratio5x3", "ratio5x4", "ratio7x5", "ratio16x9"],
                    android_ui: {
                        toolbar_title: "裁剪",
                        toolbar_color: "#b07c37",
                        // statusbar_color: "#00f4d5",
                        toolbar_widget_color: '#e9e9e9',
                        init_aspect_ratio: "original",
                        show_crop_grid: false,
                        show_crop_frame: false,
                    },
                    ios_ui: {
                        title: "裁剪",
                        // done_button_title:"完成",
                        // cancel_button_title:"返回",
                        // minimum_aspect_ratio: 1.0,
                        // rect_x: 1.0,
                        // rect_y: 1.0,
                        // rect_width: 1.0,
                        // rect_height: 1.0,
                        // show_activity_sheet_on_done: false,
                        // show_cancel_confirmation_dialog: false,
                        // rotate_clockwise_button_hidden: false,
                        // hides_navigation_bar: false,
                        // rotate_button_hidden: false,
                        // reset_button_hidden: false,
                        // aspect_ratio_picker_button_hidden: false,
                        // reset_aspect_ratio_enabled: true,
                        // aspect_ratio_lock_dimension_swap_enabled: false,
                        // aspect_ratio_lock_enabled: false,
                    }
                }, (urlLocal: string) => {
                    console.log(urlLocal)
                    const platform = uni.getSystemInfoSync().platform
                    switch (platform) {
                        case "android": {
                            //上传文件需要的路径
                            let filePath = "file://" + urlLocal;
                            console.log(filePath)
                            result.value = filePath
                        };
                        break;
                    case "ios": {
                        var copyPath = plus.io.convertLocalFileSystemURL("_doc/vv-work");
                        var copyPathName = copyPath + "/" + "crop.png";
                        console.log(copyPathName)
                        const p = copyPathToSandboxPath(copyPath, copyPathName, urlLocal)
                        p.then((resPath: string) => {
                            console.log(resPath)
                            // ***file:// 不加的话 文件名含中文的 找不到文件
                            let filePath = "file://" + copyPathName;
                            result.value = filePath;
                            console.log("~~~~~~copye finish -> filePath: ", filePath);
                        }, (errer: any) => {
                            console.log("~~~~~~ e: ", errer);
                        })
                    };
                    break;

                    }
                })
            })
    }

    function copyPathToSandboxPath(copyPath: string, copyPathName: string, filePath: string) {
        return new Promise((resolve, reject) => {
            console.log("copy -> copyPath:" + copyPath);
            console.log("copy -> copyPathName:" + copyPathName);
            console.log("copy -> filePath:" + filePath);
            filePath = filePath.replace("file://", "");

            var NSFileManager = plus.ios.importClass("NSFileManager");
            var fileManager = NSFileManager.defaultManager();

            var isFileExist_Path = plus.ios.invoke(fileManager, "fileExistsAtPath:", copyPath);
            console.log("isFileExist_Path:" + isFileExist_Path);
            if (isFileExist_Path == false) {
                var isCreateDirectory = plus.ios.invoke(fileManager,
                    "createDirectoryAtPath:withIntermediateDirectories:attributes:error:", copyPath, true, null, null);
                console.log("isCreateDirectory:" + isCreateDirectory);
            }

            var isFileExist_PathName = plus.ios.invoke(fileManager, "fileExistsAtPath:", copyPathName);
            console.log("isFileExist_PathName:" + isFileExist_PathName);
            if (isFileExist_PathName == true) {
                // 如果存在 删除
                var isRemove = plus.ios.invoke(fileManager, "removeItemAtPath:error:", copyPathName, null);
                console.log("isRemove:" + isRemove);
            }

            // plus.ios.invoke(fileManager, "copyItemAtPath:toPath:error:", filePath, copyPathName, null);
            var isCopy = plus.ios.invoke(fileManager, "copyItemAtPath:toPath:error:", filePath, copyPathName, null);
            if (isCopy) {
                console.log("FFFFFF isCopy true :" + copyPathName);
                resolve("success")
            } else {
                console.log("FFFFFF copyItem failed")
                reject("failed")
            }
        })
    }
</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .logo {
        height: 200rpx;
        width: 200rpx;
        margin-top: 200rpx;
        margin-bottom: 50rpx;
    }

    .text-area {
        display: flex;
        justify-content: center;
    }

    .title {
        font-size: 36rpx;
        color: #8f8f94;
        margin-top: 10rpx;
    }

    .textbox {
        width: 100%;
        word-wrap: break-word;
    }
</style>

使用案例2

注意:使用 uni.chooseImage时官方最新HBuilder(Mac系统)选择模块之后运行会报无模块错误,建议使用案例1

<template>
    <view class="content">
        <image class="logo" src="/static/logo.png"></image>
        <button type="primary" @click="choosePicture">选择图片</button>
        <view class="image-content">
            <image style="width: 200px; height: 200px; background-color: #eeeeee;" mode="scaleToFill" :src="result"
                @error="imageError"></image>
        </view>
        <view class="textbox">
            <text>{{pickResult}}</text>
        </view>
    </view>
</template>

<script setup lang="ts">
    import {
        onUnmounted,
        ref
    } from "vue";
    var uniModule = uni.requireNativePlugin("RLUni-EasyCropModule")
    var filePickerModule = uni.requireNativePlugin("RLUni-EasyFilePickerModule")
    var result = ref('')
    var pickResult = ref('')

    onUnmounted(() => {
        filePickerModule.clear()
    })

    function imageError(e: any) {
        console.error(e.detail.errMsg)
    }

    function choosePicture() {  
        uni.chooseImage({
            count: 1, //默认9
            sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
            sourceType: ['album'], //从相册选择
            success: function (res) {
                console.log(JSON.stringify(res.tempFilePaths));
                const path = plus.io.convertLocalFileSystemURL(res.tempFilePaths[0])
                //如果文件路径含有中文,需要将url反编码
                // decodeURIComponent(path)
                console.log(path)
                uniModule.cropImage({
                    source_path: path,
                    crop_style: "rectangle",
                    compress_format: "png",
                    compress_quality: "90",
                    aspect_ratio_presets: ["square", "ratio3x2", "original", "ratio4x3", "ratio16x9"],
                    // aspect_ratio_presets: ["original", "square", "ratio3x2", "ratio4x3", "ratio5x3", "ratio5x4", "ratio7x5", "ratio16x9"],
                    android_ui: {
                        toolbar_title: "裁剪",
                        toolbar_color: "#b07c37",
                        // statusbar_color: "#00f4d5",
                        toolbar_widget_color: '#e9e9e9',
                        init_aspect_ratio: "original",
                        show_crop_grid: false,
                        show_crop_frame: false,
                    },
                    ios_ui: {
                        title: "裁剪",
                        // done_button_title:"完成",
                        // cancel_button_title:"返回",
                        // minimum_aspect_ratio: 1.0,
                        // rect_x: 1.0,
                        // rect_y: 1.0,
                        // rect_width: 1.0,
                        // rect_height: 1.0,
                        // show_activity_sheet_on_done: false,
                        // show_cancel_confirmation_dialog: false,
                        // rotate_clockwise_button_hidden: false,
                        // hides_navigation_bar: false,
                        // rotate_button_hidden: false,
                        // reset_button_hidden: false,
                        // aspect_ratio_picker_button_hidden: false,
                        // reset_aspect_ratio_enabled: true,
                        // aspect_ratio_lock_dimension_swap_enabled: false,
                        // aspect_ratio_lock_enabled: false,
                    }
                }, (urlLocal: string) => {
                    console.log(urlLocal)
                    const platform = uni.getSystemInfoSync().platform
                    switch (platform) {
                        case "android": {
                            //上传文件需要的路径
                            let filePath = "file://" + urlLocal;
                            console.log(filePath)
                            result.value = filePath
                        };
                        break;
                    case "ios": {
                        var copyPath = plus.io.convertLocalFileSystemURL("_doc/vv-work");
                        var copyPathName = copyPath + "/" + "crop.png";
                        console.log(copyPathName)
                        const p = copyPathToSandboxPath(copyPath, copyPathName, urlLocal)
                        p.then((resPath: string) => {
                            console.log(resPath)
                            // ***file:// 不加的话 文件名含中文的 找不到文件
                            let filePath = "file://" + copyPathName;
                            result.value = filePath;
                            console.log("~~~~~~copye finish -> filePath: ", filePath);
                        }, (errer: any) => {
                            console.log("~~~~~~ e: ", errer);
                        })
                    };
                    break;

                    }
                })
            }
        });
    }

    function copyPathToSandboxPath(copyPath: string, copyPathName: string, filePath: string) {
        return new Promise((resolve, reject) => {
            console.log("copy -> copyPath:" + copyPath);
            console.log("copy -> copyPathName:" + copyPathName);
            console.log("copy -> filePath:" + filePath);
            filePath = filePath.replace("file://", "");

            var NSFileManager = plus.ios.importClass("NSFileManager");
            var fileManager = NSFileManager.defaultManager();

            var isFileExist_Path = plus.ios.invoke(fileManager, "fileExistsAtPath:", copyPath);
            console.log("isFileExist_Path:" + isFileExist_Path);
            if (isFileExist_Path == false) {
                var isCreateDirectory = plus.ios.invoke(fileManager,
                    "createDirectoryAtPath:withIntermediateDirectories:attributes:error:", copyPath, true, null, null);
                console.log("isCreateDirectory:" + isCreateDirectory);
            }

            var isFileExist_PathName = plus.ios.invoke(fileManager, "fileExistsAtPath:", copyPathName);
            console.log("isFileExist_PathName:" + isFileExist_PathName);
            if (isFileExist_PathName == true) {
                // 如果存在 删除
                var isRemove = plus.ios.invoke(fileManager, "removeItemAtPath:error:", copyPathName, null);
                console.log("isRemove:" + isRemove);
            }

            // plus.ios.invoke(fileManager, "copyItemAtPath:toPath:error:", filePath, copyPathName, null);
            var isCopy = plus.ios.invoke(fileManager, "copyItemAtPath:toPath:error:", filePath, copyPathName, null);
            if (isCopy) {
                console.log("FFFFFF isCopy true :" + copyPathName);
                resolve("success")
            } else {
                console.log("FFFFFF copyItem failed")
                reject("failed")
            }
        })
    }
</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .logo {
        height: 200rpx;
        width: 200rpx;
        margin-top: 200rpx;
        margin-bottom: 50rpx;
    }

    .text-area {
        display: flex;
        justify-content: center;
    }

    .title {
        font-size: 36rpx;
        color: #8f8f94;
        margin-top: 10rpx;
    }

    .textbox {
        width: 100%;
        word-wrap: break-word;
    }
</style>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

读写手机存储

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问