Android onActivityResult的替代方法—registerForActivityResult

一、前言

今天新建项目引入 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()方法传递数据,这部分和以前一样

注意:\color{red}{ActivityResultLauncher必需在activity的onCreate()方法或fragment的onCreate()、onAttach()里先注册,然后在需要调用的地方调用launch方法。}\
所以上面代码可能需要如下编写方式,为了方便,下面的案例统一使用上面的方式。

    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
.pdf 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))
    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容