文章

android.activity

android.activity

OnBackPressedDispatcher

OnBackPressedDispatcher 处理 Fragment 中处理返回键

Fragment 中处理返回键:

1
2
3
4
5
6
7
8
9
10
11
12
class BaseFragment : Fragment() {
    override fun onAttach(context: Context) {
        super.onAttach(context)
        requireActivity().onBackPressedDispatcher.addCallback(this,
            object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    //这里处理拦截的逻辑
                }

            })
    }
}

OnBackPressedDispatcher 存在的问题

真实场景一般是 fragment 走到特定逻辑了,就需要拦截,没有走到就不拦截,或者随着不同的业务,会动态不断变化,而 Android X 的设计是:必现提前告诉它们,要不要拦截,这就很鸡肋了

  1. 在需要拦截的时候,设置为 anable 为 true,在不需要拦截的时候,要立马设置为 flase
  2. 场景复杂下,需要不断的调用 true 跟 flase,来回切换

自行实现(接口)

定义一个拦截的接口:

1
2
3
4
5
6
7
8
9
/**
 * 监听activity的onBackPress事件
 */
interface BackPressedListener {
    /**
     * @return true代表响应back键点击,false代表不响应
     */
    fun handleBackPressed(): Boolean
}

基类 fragment 实现这个接口:

1
2
3
4
5
6
7
8
9
/**
 * 全局通用的基类fragment
 */
abstract class BaseFragment : Fragment(), BackPressedListener {
    override fun handleBackPressed(): Boolean {
        //默认不响应
        return false
    }
}

fragmentA 需要响应,就 override 这个方法:

1
2
3
4
5
6
class FragmentA : BaseFragment(){
    override fun handleBackPressed(): Boolean {
        //处理自己的逻辑
        return true
    }
}

最后在基类 activity 实现逻辑打通:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class BaseActivity : AppCompatActivity() {
    override fun onBackPressed() {
        if (!interceptBackPressed()) {
            super.onBackPressed()
        }
    }

    /**
     * 拦截事件
     */
    private fun interceptBackPressed(): Boolean {
        supportFragmentManager.fragments.forEach {
            if (it is BackPressedListener) {
                if (it.handleBackPressed()) {
                    return true
                }
            }
        }
        return false
    }
}

Activity Results API

What?

Activity Results API

替代 startActivityForResultonActivityResult 的,避免 onActivityResult 回调方法各种嵌套、耦合严重、难以维护

Activity Results API 是 Google 官方推荐的 Activity、Fragment 获取数据的方式。

引入

1
2
3
4
5
6
7
// activity https://developer.android.com/jetpack/androidx/releases/activity
implementation "androidx.activity:activity:1.3.0-alpha07"
implementation "androidx.activity:activity-ktx:1.3.0-alpha07"

// fragment https://developer.android.com/jetpack/androidx/releases/fragment
implementation "androidx.fragment:fragment:1.3.3"
implementation "androidx.fragment:fragment-ktx:1.3.3"

How

ActivityResultContract/ActivityResultLauncher

ActivityResultContract<I, O> 协议

定义了如何传递数据和如何处理返回的数据;如果您不需要任何输入,可使用 Void(在 Kotlin 中,使用 Void? 或 Unit)作为输入类型。

在 Activity 或 Fragment 通过 registerForActivityResult(ActivityResultContract,ActivityResultCallback) 来注册,需要在 start 前注册,否则报错。

ActivityResultLauncher 启动器

调用 ActivityResultLauncher 的 launch 方法来启动页面跳转,作用相当于原来的 startActivity()

预定义的 ActivityResultContract

预定义 ActivityResultContract用途
StartActivityForResult通用的 Contract,不做任何转换,Intent 作为输入,ActivityResult 作为输出,这也是最常用的一个协定
RequestMultiplePermissions用于请求一组权限
RequestPermission用于请求单个权限
TakePicturePreview调用 MediaStore.ACTION_IMAGE_CAPTURE
拍照,返回值为 Bitmap 图
TakePicture调用 MediaStore.ACTION_IMAGE_CAPTURE
拍照,并将图片保存到给定的 Uri 地址,返回 true 表示保存成功
TakeVideo调用 MediaStore.ACTION_VIDEO_CAPTURE
拍摄视频,保存到给定的 Uri 地址,返回一张缩略图
PickContact从通讯录 APP 获取联系人
GetContent提示用选择一条内容,返回一个通过 ContentResolver#openInputStream(Uri) 访问原生数据的 Uri 地址(content://形式) 。默认情况下,它增加了 Intent#CATEGORY_OPENABLE, 返回可以表示流的内容
CreateDocument提示用户选择一个文档,返回一个 (file:/http:/content:) 开头的 Uri
OpenMultipleDocuments提示用户选择文档(可以选择多个),分别返回它们的 Uri,以 List 的形式
OpenDocumentTree提示用户选择一个目录,并返回用户选择的作为一个 Uri 返回,应用程序可以完全管理返回目录中的文档

上面这些预定义的 Contract 中,除了 StartActivityForResult 和 RequestMultiplePermissions 之外,基本都是处理的与其他 APP 交互,返回数据的场景,比如,拍照,选择图片,选择联系人,打开文档等等。使用最多的就是 StartActivityForResult 和 RequestMultiplePermissions 了

startActivityForResult/onActivityResult 替代

原始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
val launcherV1 = registerForActivityResult(MyActivityResultContract()) {
    Toast.makeText(this, "result value is :${it}", Toast.LENGTH_LONG).show()
    tv_result.append("[ActivityResultContract]resultCode=$it\n")
}
btn_start_activity_result_api_v1.setOnClickListener {
    launcherV1.launch(123)
}

inner class MyActivityResultContract : ActivityResultContract<Int, String>() {
    override fun createIntent(context: Context, input: Int?): Intent {
        return Intent(context, ActivityResultsDestinationActivity::class.java)
                .apply {
                    putExtra("input", input)
                }
    }
    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        if (resultCode == Activity.RESULT_OK) {
            return intent?.getStringExtra("data") ?: ""
        }
        return null
    }
}

StartActivityForResult

1
2
3
4
5
6
7
8
9
10
11
12
val launcherV3 = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    val resultCode = result?.resultCode
    if (resultCode == Activity.RESULT_OK) {
        val data = result.data?.getStringExtra("data")
        tv_result.append("[ActivityResultContracts.StartActivityForResult]data=$data\n")
    }
}
btn_start_activity_result_api_v3.setOnClickListener {
    val intent = Intent(this, ActivityResultsDestinationActivity::class.java)
    intent.putExtra("input", 10086)
    launcherV3.launch(intent)
}

RequestPermission/RequestMultiplePermissions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
request_permission.setOnClickListener {
    requestPermission.launch(permission.BLUETOOTH)
}

request_multiple_permission.setOnClickListener {
    requestMultiplePermissions.launch(
        arrayOf(
            permission.BLUETOOTH,
            permission.NFC,
            permission.ACCESS_FINE_LOCATION
        )
    )
}

// 请求单个权限
private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        // Do something if permission granted
        if (isGranted) toast("Permission is granted")
        else toast("Permission is denied")
    }

// 请求一组权限
private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
        // Do something if some permissions granted or denied
        permissions.entries.forEach {
            // Do checking here
        }                                                                             
}

写的库

一行代码实现权限请求、startActivityForResult、调用相机拍照及调用相机录像等,消除 onActivityResult()、onRequestPermissionsResult() 回调导致的代码分散的问题;支持协程
https://github.com/hacket/ActivityResultHelper

本文由作者按照 CC BY 4.0 进行授权