一、前言
今天新建项目引入 implementation "androidx.fragment:fragment-ktx:1.3.0"包后,发现startActivityForResult()、onActivityResult()、requestPermissions()、onRequestPermissionsResult()方法被标记为过时,取而代之的是新方法registerForActivityResult()
二、基本用法
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val data = it.data
val resultCode = it.resultCode
}.launch(Intent(context, BActivity::class.java))
跳转到BActivity后,调用setResult()方法传递数据,这部分和以前一样
注意:
所以上面代码可能需要如下编写方式,为了方便,下面的案例统一使用上面的方式。
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val data = it.data
val resultCode = it.resultCode
}
val textView = findViewById<TextView>(R.id.textView)
textView.setOnClickListener {
activityResultLauncher.launch(Intent(this, BActivity::class.java))
}
}
三、调用联系人列表
registerForActivityResult(ActivityResultContracts.PickContact()) {
if (it != null) {
val cursor = contentResolver.query(it, null, null, null, null)
cursor?.run {
if (cursor.moveToFirst()) {
val name =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
Log.e(TAG, "联系人姓名:$name")
if (cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) == "1") {
//该联系人名下存在手机号,查询方法自行实现
}
}
}
}
}.launch(null)
四、获取敏感权限
4.1 获取单个权限
registerForActivityResult(ActivityResultContracts.RequestPermission()){
if(it){
//用户同意了该权限
}else{
//用户拒绝了该权限
}
}.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
4.2 获取多个权限
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){it->
//通过的权限
val grantedList = it.filterValues { it }.mapNotNull { it.key }
//未通过的权限
val deniedList = (it - grantedList).map { it.key }
//拒绝并且点了“不再询问”权限
val alwaysDeniedList = deniedList.filterNot {
ActivityCompat.shouldShowRequestPermissionRationale(
activity,
it
)
}
}.launch(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
)
五、调用文件选择器
调用文件选择器,获取指定类型的文件,可在launch()方法里使用mimetype指定调用文件类型
registerForActivityResult(ActivityResultContracts.GetContent()){
}.launch("text/plain")
如果需要选择多种文件类型,可以使用OpenDocument
registerForActivityResult(ActivityResultContracts.OpenDocument()){
}.launch(arrayOf("image/*","text/plain"))
常用的文件mimetype对照表
扩展名类 | 型/子类型 |
---|---|
.jpg、.jpeg | image/jpeg |
.png | image/png |
.webp | image/webp |
.bmp | image/bmp |
.gif | image/gif |
.xls | application/vnd.ms-excel |
.xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
.doc | application/msword |
.docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
application/pdf | |
.pps | application/vnd.ms-powerpoint |
.ppt | application/vnd.ms-powerpoint |
.pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
.apk | application/vnd.android.package-archive |
.js | application/x-javascript |
.jar | application/java-archive |
.bin、.class、.exe、.rar | application/octet-stream |
.tar | application/x-tar |
.tgz | application/x-compressed |
.zip | application/x-zip-compressed |
.z | application/x-compress |
.html、.htm | text/html |
.txt、.c、.cpp、.h、.java、.conf、.log、.prop、.rc、.sh、.xml | text/plain |
.wav | audio/x-wav |
.wma | audio/x-ms-wma |
.wmv | audio/x-ms-wmv |
.m3u | audio/x-mpegurl |
.m4a、.m4b、.m4p | audio/mp4a-latm |
.mp2、.mp3 | audio/x-mpeg |
.mpga | audio/mpeg |
.ogg | audio/ogg |
.3gp | video/3gpp |
.asf | video/x-ms-asf |
.avi | video/x-msvideo |
.m4u | video/vnd.mpegurl |
.m4v | video/x-m4v |
.mov | video/quicktime |
.mp4、.mpg4 | video/mp4 |
.mpe、.mpeg、.mpg | video/mpeg |
最全的:http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
六、调用相机
//需要WRITE_EXTERNAL_STORAGE权限
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, "图片名称.jpg")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}else{
FileProvider.getUriForFile(this,BuildConfig.authorities,File(externalCacheDir!!.absolutePath+"图片名称.jpg"))
}
registerForActivityResult(ActivityResultContracts.TakePicture()){
if(it)
Glide.with(this).load(uri).into(binding.imageView)
}.launch(uri)
或者使用下面的方式,直接返回Bitmap图片
registerForActivityResult(ActivityResultContracts.TakePicturePreview()){
Glide.with(this).load(it).into(binding.imageView)
}.launch(null)
七、自定义ActivityResultContract<I,O>
ActivityResultContract<I,O> 官方提供的,通过输入类型I构建意图并将回调数据转换成输入类型O的契约类,可以非常轻松地调用文件,联系人,敏感权限等等,另外,我们也可以实现自己的ActivityResultContract来简化意图操作,这里写一个裁剪图片的ActivityResultContract为例
class CropImageContent : ActivityResultContract<CropImageResult, Uri>() {
var outUri: Uri? = null
//构建意图
override fun createIntent(context: Context, input: CropImageResult): Intent {
//把CropImageResult转换成裁剪图片的意图
val intent = Intent("com.android.camera.action.CROP")
val imageName = "${input.imageName}.jpg"
outUri = Uri.fromFile(getClipImage(context, imageName))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra("noFaceDetection", true) //去除默认的人脸识别,否则和剪裁匡重叠
intent.setDataAndType(input.uri, "image/*")
intent.putExtra("crop", "true") // crop=true 有这句才能出来最后的裁剪页面.
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) // 返回格式
intent.putExtra("return-data", false)
intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri)
if (input.outputX != 0 && input.outputY != 0) {
intent.putExtra("outputX", input.outputX)
intent.putExtra("outputY", input.outputY)
}
if (input.aspectX != 0 && input.aspectY != 0) {
if (input.aspectY == input.aspectX && Build.MANUFACTURER == "HUAWEI") {
intent.putExtra("aspectX", 9999)
intent.putExtra("aspectY", 9998)
} else {
intent.putExtra("aspectX", input.aspectX)
intent.putExtra("aspectY", input.aspectY)
}
}
return intent
}
//接收意图并处理数据
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
if (resultCode == -1) {
if (outUri != null)
return outUri!!
else
return null
} else { //取消裁剪
return null
}
}
/**
* 获取裁剪之后的图片文件
*/
private fun getClipImage(context: Context, clipImageName: String): File {
val file = File(externalCacheDir!!.absolutePath+"图片名称.jpg")
return file
}
}
/**
* uri:需要裁剪的图片
* aspect:裁剪长宽比例
* output:图片输出长宽
*/
class CropImageResult(
val uri: Uri,
val aspectX: Int = 0,
val aspectY: Int = 0,
@androidx.annotation.IntRange(from = 0, to = 1080)
val outputX: Int = 0,
@androidx.annotation.IntRange(from = 0, to = 1080)
val outputY: Int = 0,
val imageName: String = "temp_crop_image"
)
使用:
//裁剪图片
private fun crop(uri: Uri) {
registerForActivityResult(CropImageContent()){
Glide.with(this).load(it.toFile).into(binding.ivImage)
}.launch(CropImageResult(uri))
}