如何在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
相关产品推荐
相关产品推荐

