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 的设计是:必现提前告诉它们,要不要拦截,这就很鸡肋了
- 在需要拦截的时候,设置为 anable 为 true,在不需要拦截的时候,要立马设置为 flase
- 场景复杂下,需要不断的调用 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
替代 startActivityForResult
和 onActivityResult
的,避免 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