自源码已废弃了 startActivityForResult 之后,如果想要启动一个 Activity 并获取返回结果,推荐使用 registerForActivityResult ,方法定义如下:
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
}
其中 I 即 Input 指代输入,O 即 Output 指代输出。
输入的是 约定协议,输出的是 返回结果。
官网文档:获取 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 包中已经预定义了一些常用的 默认协定 ,可以参照其实现自定义协定。