registerForActivityResult 用法详解及适配 Android 10、11

自源码已废弃了 startActivityForResult 之后,如果想要启动一个 Activity 并获取返回结果,推荐使用 registerForActivityResult ,方法定义如下:

public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
        @NonNull ActivityResultContract<I, O> contract,
        @NonNull ActivityResultCallback<O> callback) {
    
}

其中 IInput 指代输入,OOutput 指代输出。
输入的是 约定协议,输出的是 返回结果
官网文档:获取 activity 的结果

在实际开发过程中,我们只要指定我们需要的 输入 和 输出 类型,然后在 callback 处理返回值就可以了。

下面以 Android 常用的 选择图片选择图片并剪裁拍照拍照并剪裁 作为例子来体验下 registerForActivityResult 的用法:

效果图:

一、选择图片并剪裁

1. 定义输入输出的协议 SelectPhotoContract 和 CropPhotoContract
/**
 * 选择照片的协定
 * Input type  : Unit? 不需要传值
 * Output type : Uri?  选择完成后的 image uri
 */
class SelectPhotoContract : ActivityResultContract<Unit?, Uri?>() {

    @CallSuper
    override fun createIntent(context: Context, input: Unit?): Intent {
        return Intent(Intent.ACTION_PICK).setType("image/*")
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        "pick photo result: ${intent?.data}".logD()
        return intent?.data
    }
}

/**
 * 剪裁照片的协定
 * Input type  : CropParams 剪裁照片的相关参数
 * Output type : Uri?       照片剪裁完成后的uri
 */
class CropPhotoContract : ActivityResultContract<CropParams, Uri?>() {

    private var outputUri: Uri? = null

    @CallSuper
    override fun createIntent(context: Context, input: CropParams): Intent {
        // 获取输入图片uri的媒体类型
        val mimeType = context.contentResolver.getType(input.uri)
        // 创建新的图片名称
        val imageName = "${System.currentTimeMillis()}.${
            MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
        }"
        outputUri = if (input.extraOutputUri.isNotEmpty()) {
            // 使用指定的uri地址
            input.extraOutputUri
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                // Android 10 及以上获取图片uri
                val values = contentValuesOf(
                    Pair(MediaStore.MediaColumns.DISPLAY_NAME, imageName),
                    Pair(MediaStore.MediaColumns.MIME_TYPE, mimeType),
                    Pair(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
                )
                context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            } else {
                Uri.fromFile(File(context.externalCacheDir!!.absolutePath, imageName))
            }
        }

        return Intent("com.android.camera.action.CROP")
            .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            .setDataAndType(input.uri, mimeType)
            .putExtra("outputX", input.outputX)
            .putExtra("outputY", input.outputY)
            .putExtra("aspectX", input.aspectX)
            .putExtra("aspectY", input.aspectY)
            .putExtra("scale", input.scale)
            .putExtra("crop", input.crop)
            .putExtra("return-data", input.returnData)
            .putExtra("noFaceDetection", input.noFaceDetection)
            .putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
            .putExtra("outputFormat", input.outputFormat)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        "crop photo outputUri : $outputUri".logD()
        return outputUri
    }
}

/**
 * 剪裁照片的参数
 */
class CropParams(
    val uri: Uri,
    val aspectX: Int = 1,
    val aspectY: Int = 1,
    @androidx.annotation.IntRange(from = 0, to = 1080)
    val outputX: Int = 250,
    @androidx.annotation.IntRange(from = 0, to = 1080)
    val outputY: Int = 250,
    val scale: Boolean = true,
    val crop: Boolean = true,
    val noFaceDetection: Boolean = true,
    val returnData: Boolean = false,
    val outputFormat: String = Bitmap.CompressFormat.JPEG.toString(),
    val extraOutputUri: Uri? = null
)
2. 定义 ActivityResultLauncher
// 剪裁图片 
val cropPhoto = registerForActivityResult(CropPhotoContract()) { uri: Uri? ->
    if (uri != null) {
        ivImage.setImageURI(uri)
    }
}

// 选择图片        
val selectPhoto = registerForActivityResult(SelectPhotoContract()) { uri: Uri? ->
    if (uri != null) {
   		// 返回的选择的图片uri
        if (needCrop) {
        	// 需要剪裁图片,再调用剪裁图片的launch()方法
            cropPhoto.launch(CropParams(uri))
        } else {
        	// 如果不剪裁图片,则直接显示
            ivImage.setImageURI(uri)
        }
    } else {
        "您没有选择任何图片".toast()
    }
}
3. 点击时调用 ActivityResultLauncher 的 launch() 方法
// 从相册选择图片
btnSelectPhoto.setOnClickListener {
    // 定义的是Unit?,直接传 null
    needCrop = false
    selectPhoto.launch(null)
}

// 从相册选择图片并剪裁
btnSelectPhotoWithCrop.setOnClickListener {
    needCrop = true
    selectPhoto.launch(null)
}

二、拍照 并 剪裁

1. 定义输入输出的协议 TakePhotoContract
/**
 * 拍照协定
 * Input type  : Unit? 不需要传值
 * Output type : Uri?  拍照完成后的uri
 */
class TakePhotoContract : ActivityResultContract<Unit?, Uri?>() {

    private var uri: Uri? = null

    @CallSuper
    override fun createIntent(context: Context, input: Unit?): Intent {
        val mimeType = "image/jpeg"
        val fileName = "${System.currentTimeMillis()}.jpg"
        uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android 10 及以上获取图片uri
            val values = contentValuesOf(
                Pair(MediaStore.MediaColumns.DISPLAY_NAME, fileName),
                Pair(MediaStore.MediaColumns.MIME_TYPE, mimeType),
                Pair(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
            )
            context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        } else {
            // Android 9 及以下获取图片uri
            FileProvider.getUriForFile(
                context, "${context.packageName}.fileprovider",
                File(context.externalCacheDir!!.absolutePath, fileName)
            )
        }
        return Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, uri)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
        "take photo uri : $uri".logD()
        return uri
    }
}
2. 定义 ActivityResultLauncher
val takePhoto = registerForActivityResult(TakePhotoContract()) { uri: Uri? ->
    if (uri != null) {
        if (needCrop) {
            cropPhoto.launch(CropParams(uri))
        } else {
            ivImage.setImageURI(uri)
        }
    }
}
3. 点击时调用 ActivityResultLauncher 的 launch() 方法
// 拍照并剪裁图片 
btnTakePhotoWithCrop.setOnClickListener {
    if (!BaseApp.instance.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
        "设备未安装相机应用,无法拍照".toast()
        return@setOnClickListener
    }
    needCrop = true
    takePhoto.launch(null)
}

androidx 包中已经预定义了一些常用的 默认协定 ,可以参照其实现自定义协定。

查看完整示例代码:RegisterForResultActivity

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值