You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Kotlin中结合Contracts使用UCrop裁剪相机拍摄的图片?

解决方案:结合Contracts与UCrop裁剪相机拍摄图片

问题背景

已实现图片选择器的裁剪功能,且能获取相机拍摄图片的Uri,但不知道如何通过Activity Result Contracts结合UCrop裁剪相机拍摄的图片。

核心思路

复用已实现的uCropContract(接收输入Uri和输出Uri的列表),只需为相机拍摄的图片生成合法的输出Uri,再调用裁剪合约即可,逻辑和图片选择器裁剪完全一致。

具体实现步骤

1. 完善相机拍摄的Activity Result合约

确保相机拍摄生成的Uri通过FileProvider生成(避免Android 7+的FileUriExposedException),并在拍摄成功后触发裁剪:

// 存储相机拍摄的图片Uri
private var cameraImageUri: Uri? = null

// 相机拍摄合约:返回拍摄是否成功
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
    if (success) {
        cameraImageUri?.let { inputUri ->
            // 生成裁剪后的输出Uri(避免覆盖原相机图片)
            val outputUri = File(requireContext().filesDir, "cropped_camera_img.jpg").toUri()
            // 启动裁剪流程
            cropImage.launch(listOf(inputUri, outputUri))
        }
    }
}

// 生成相机拍摄的合法Uri(通过FileProvider)
private fun createCameraImageUri(): Uri? {
    val imageFile = File(requireContext().filesDir, "camera_original_img.jpg")
    return try {
        FileProvider.getUriForFile(
            requireContext(),
            "com.example.tfg_red_social_v0.fileprovider", // 与Manifest中provider的authorities一致
            imageFile
        )
    } catch (e: IllegalArgumentException) {
        e.printStackTrace()
        null
    }
}

2. 启动相机拍摄

在点击相机按钮时调用以下代码:

binding.btnCamera.setOnClickListener {
    cameraImageUri = createCameraImageUri()
    cameraImageUri?.let { takePicture.launch(it) }
}

3. 修复裁剪合约的空值问题(可选但推荐)

原uCropContract的parseResult直接返回非空Uri,若用户取消裁剪会抛出空指针异常,建议修改为:

private val uCropContract = object : ActivityResultContract<List<Uri>, Uri?>() {
    override fun createIntent(context: Context, input: List<Uri>): Intent {
        val inputUri = input[0]
        val outPut = input[1]
        return UCrop.of(inputUri, outPut)
            .withAspectRatio(16f, 9f)
            .getIntent(context)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        return if (resultCode == Activity.RESULT_OK) {
            UCrop.getOutput(intent)
        } else {
            null
        }
    }
}

对应的裁剪回调也要处理空值:

private val cropImage = registerForActivityResult(uCropContract) { uri ->
    uri?.let {
        imagenUri = it
        Glide.with(this).load(it).into(binding.ivImagen)
    }
}

4. 权限配置补充

确保Manifest中开启相机权限,并动态申请(相机是危险权限):

<!-- 去掉注释,开启相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />

在代码中动态申请相机权限(比如启动相机前):

private val requestCameraPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
    if (granted) {
        // 权限已授予,启动相机
        cameraImageUri = createCameraImageUri()
        cameraImageUri?.let { takePicture.launch(it) }
    } else {
        // 权限被拒绝,提示用户
        Toast.makeText(requireContext(), "需要相机权限才能拍摄图片", Toast.LENGTH_SHORT).show()
    }
}

// 启动相机前先申请权限
binding.btnCamera.setOnClickListener {
    if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        requestCameraPermission.launch(Manifest.permission.CAMERA)
    } else {
        cameraImageUri = createCameraImageUri()
        cameraImageUri?.let { takePicture.launch(it) }
    }
}

关键注意事项

  • 相机拍摄的输出Uri和裁剪的输出Uri不要重复,避免覆盖原文件导致裁剪失败
  • 必须使用FileProvider生成相机图片的Uri,否则在Android 7及以上系统会抛出FileUriExposedException
  • 危险权限(相机、存储)需要动态申请,不能仅依赖Manifest配置

内容的提问来源于stack exchange,提问作者Monrac

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 15:55:26