持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情。
在 Kotlin
当道的 Android 时代,官方推出了很多实用的扩展工具,即 Android KTX
。这些工具或是相关类的直接扩展,或是顶层工具函数。用对用好的话,真能帮我们解放双手、拯救劳累的脑细胞们,居家旅行必备啊同学们!
今天先来学习下大概率会经常用的吧。
activity-ktx
这个库主要是 Activity 相关的扩展,依赖包为:
implementation "androidx.activity:activity-ktx:$version"
ComponentActivity.viewModels()
viewModels
是 ComponentActivity
的扩展函数,即在Activity环境下使用,它返回一个 ViewModel
的 Lazy
委托:
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
这个 ViewModelLazy
类,来自 lifecycle-viewmodel-ktx:
public class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
其中,参数factoryProducer
用于指定 model 在创建时所需要的 ViewModelProvider.Factory
。默认为空,即不用定制 factory,用默认的 defaultViewModelProviderFactory
即可。
在类 ViewModelLazy
中,可以看到我们常常用的构造 ViewModel
的核心代码:
ViewModelProvider(store, factory).get(viewModelClass.java)
这样看来,viewModels
扩展总共帮我们做了两件事:
- 隐藏实现细节
- 生成了一个懒委托,避免重复构造
使用起来十分简单,比如有一个MyViewModel
,委托获取它:
class MyComponentActivity : ComponentActivity() {
val viewmodel: MyViewModel by viewModels()
}
如果是 Fragment,则需要用到
androidx.fragment:fragment-ktx
内的Fragment.viewModels()
,以构造 Fragment 域的 ViewModel
OnBackPressedDispatcher.addCallback()
OnBackPressedDispatcher
是一个分发器,用于处理Activity的 onBackPressed()
回调。
public final class OnBackPressedDispatcher {
// ...
@MainThread
public void addCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
// ...
}
@MainThread
public void addCallback(@NonNull LifecycleOwner owner, @NonNull OnBackPressedCallback onBackPressedCallback) {
// ...
}
@MainThread
@NonNull
Cancellable addCancellableCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
// ...
}
// ...
}
OnBackPressedDispatcher.addCallback()
扩展函数如下:
public fun OnBackPressedDispatcher.addCallback(
owner: LifecycleOwner? = null,
enabled: Boolean = true,
onBackPressed: OnBackPressedCallback.() -> Unit
): OnBackPressedCallback {
val callback = object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
onBackPressed()
}
}
if (owner != null) {
addCallback(owner, callback)
} else {
addCallback(callback)
}
return callback
}
从前面的 OnBackPressedDispatcher
可以看到,它有两个addCallback
重载方法,这里的扩展帮我们隐藏了重载调用,它通过 owener 参数是否为空作为判断,调用不同callback添加函数;同时,OnBackPressedCallback
的构造参数也提供了默认值为true,即enabled,毕竟,大部分情况下,添加回调就是要用的,不enable那添加来干嘛?
ActivityResultCaller.registerForActivityResult()
ActivityResultContract
类,是为替代老的 Activity result获取方法而存在的,通常如此使用:
val intent = XXX // TODO 某intent构造
val matchResult = (this as ComponentActivity).registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
// 结果获取
if (it.resultCode == Activity.RESULT_OK) {
finish()
}
}
matchResult.launch(intent)
使用ActivityResultCaller.registerForActivityResult
扩展后:
(this as ComponentActivity).registerForActivityResult(ActivityResultContracts.StartActivityForResult(), intent) {
if (it.resultCode == Activity.RESULT_OK) {
finish()
}
}.launch()
代码更加简洁,launch
时省去了参数,怎么做的呢?来看看
public fun <I, O> ActivityResultCaller.registerForActivityResult(
contract: ActivityResultContract<I, O>,
input: I,
callback: (O) -> Unit
): ActivityResultLauncher<Unit> {
val resultLauncher = registerForActivityResult(contract) { callback(it) }
// 前面案例传入这里的input,就是Intent,构造ActivityResultCallerLauncher
return ActivityResultCallerLauncher(resultLauncher, contract, input)
}
internal class ActivityResultCallerLauncher<I, O>(
val launcher: ActivityResultLauncher<I>,
val callerContract: ActivityResultContract<I, O>,
val callerInput: I // Intent传入
) : ActivityResultLauncher<Unit>() {
val resultContract: ActivityResultContract<Unit, O> by lazy {
object : ActivityResultContract<Unit, O>() {
override fun createIntent(context: Context, input: Unit): Intent {
return callerContract.createIntent(context, callerInput)
}
override fun parseResult(resultCode: Int, intent: Intent?): O {
return callerContract.parseResult(resultCode, intent)
}
}
}
override fun launch(input: Unit, options: ActivityOptionsCompat?) {
launcher.launch(callerInput, options) // 调用处,这里才是实际将intent用上的地方
}
override fun unregister() {
launcher.unregister()
}
override fun getContract(): ActivityResultContract<Unit, O> {
return resultContract
}
}
/**
launch扩展
*/
public fun ActivityResultLauncher<Unit>.launch(options: ActivityOptionsCompat? = null) {
launch(Unit, options)
}
ActivityResultCallerLauncher
实际是一个 ActivityResultLauncher<Unit>
扩展ActivityResultLauncher<Unit>.launch
,就实现了前面参数隐藏。
core-ktx
Android 的核心KTX扩展包,依赖为:
implementation "androidx.core:core-ktx:$version"
Context.getSystemService()
这个简单,用于获取系统服务:
public inline fun <reified T : Any> Context.getSystemService(): T? =
ContextCompat.getSystemService(this, T::class.java)
虽然简单,但是好处也显而易见:它隐藏了不同版本的API差异,也隐藏了兼容性调用,代码更加简洁。
Handler.postDelayed()
Hmm... Handler
不是本来就有这个函数吗,扩展个什么?
嗯,这个扩展确实没干什么特别的事,只是调整了下参数顺序,让 postDelayed
在 Kotlin 世界里能写出 “末尾外放参数”。
public inline fun Handler.postDelayed(
delayInMillis: Long,
token: Any? = null,
crossinline action: () -> Unit
): Runnable {
val runnable = Runnable { action() }
if (token == null) {
postDelayed(runnable, delayInMillis)
} else {
HandlerCompat.postDelayed(this, runnable, token, delayInMillis)
}
return runnable
}
这样一来,原来如此写的:
handler.postDelayed({
// do something
}, 1000)
可以写成:
handler.postDelayed(1000) {
// do something
}
bundleOf()
这是一个顶级函数,封装了Bundle的构造和数据填充,有了它,再也不用写一堆put了。看看核心代码:
public fun bundleOf(vararg pairs: Pair<String, Any?>): Bundle = Bundle(pairs.size).apply {
for ((key, value) in pairs) {
when (value) {
null -> putString(key, null) // Any nullable type will suffice.
// Scalars
is Boolean -> putBoolean(key, value)
is Byte -> putByte(key, value)
// ......
}
}
}
它内部根据具体的参数类型,调用相应的put。
比如下面,构造时,Bundle 容量会设置为2(数组长度),然后分别调用 putInt
和 putIntArray
将所给参数添加进去:
bundleOf("count" to 6, "values" to arrayOf(1, 2, 3))
很方便吧?
TextView的TextWatcher
通常要监听 TextView 的文本变化,都是调用 addTextChangedListener
,实现 TextWatcher
并传入。但是很多时候,我们可能只关注文本变化的一个回调(比如最终值),而TextWatcher
有三个接口函数,未免显得罗嗦冗余。
好在有三个扩展,可以简化之:
public inline fun TextView.doBeforeTextChanged(
crossinline action: (
text: CharSequence?,
start: Int,
count: Int,
after: Int
) -> Unit
): TextWatcher = addTextChangedListener(beforeTextChanged = action)
public inline fun TextView.doOnTextChanged(
crossinline action: (
text: CharSequence?,
start: Int,
before: Int,
count: Int
) -> Unit
): TextWatcher = addTextChangedListener(onTextChanged = action)
public inline fun TextView.doAfterTextChanged(
crossinline action: (text: Editable?) -> Unit
): TextWatcher = addTextChangedListener(afterTextChanged = action)
public inline fun TextView.addTextChangedListener(
crossinline beforeTextChanged: (
text: CharSequence?,
start: Int,
count: Int,
after: Int
) -> Unit = { _, _, _, _ -> },
crossinline onTextChanged: (
text: CharSequence?,
start: Int,
before: Int,
count: Int
) -> Unit = { _, _, _, _ -> },
crossinline afterTextChanged: (text: Editable?) -> Unit = {}
): TextWatcher {
val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
afterTextChanged.invoke(s)
}
override fun beforeTextChanged(text: CharSequence?, start: Int, count: Int, after: Int) {
beforeTextChanged.invoke(text, start, count, after)
}
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
onTextChanged.invoke(text, start, before, count)
}
}
addTextChangedListener(textWatcher)
return textWatcher
}
前三个是三个API函数单独的扩展,最后的 TextView。addTextChangedListener
则相当于给 TextWatcher
实现了一个adapter,参数就是前面的三个回调方法,还分别有默认值 —— 因为有默认值,所以可以只关注需要关注的回调了。
另外,addTextChangedListener
返回值是 TextWatcher
,所以支持级联调用。
小结
工欲善其事,必先利其器,这些好用的工具不就正是利器吗?
今天先讨论这么多,to be continued...