那些简单好用又能提升生产力的KTX,不来了解下?

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

Kotlin 当道的 Android 时代,官方推出了很多实用的扩展工具,即 Android KTX。这些工具或是相关类的直接扩展,或是顶层工具函数。用对用好的话,真能帮我们解放双手、拯救劳累的脑细胞们,居家旅行必备啊同学们!

今天先来学习下大概率会经常用的吧。

activity-ktx

这个库主要是 Activity 相关的扩展,依赖包为:

implementation "androidx.activity:activity-ktx:$version"

ComponentActivity.viewModels()

viewModelsComponentActivity 的扩展函数,即在Activity环境下使用,它返回一个 ViewModelLazy 委托:

@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 不是本来就有这个函数吗,扩展个什么?

嗯,这个扩展确实没干什么特别的事,只是调整了下参数顺序,让 postDelayedKotlin 世界里能写出 “末尾外放参数”。

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(数组长度),然后分别调用 putIntputIntArray 将所给参数添加进去:

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...

猜你喜欢

转载自juejin.im/post/7112344513741324324