6、Android中的扩展函数实战:为View添加扩展函数
扩展函数这东西,说白了就是给现有类“加私货”。Kotlin 这门语言最让我舒服的特性之一,就是扩展函数。你不需要继承,不需要装饰器模式,直接往类上挂方法。在 Android 开发里,这个能力简直是生产力加速器。
我个人习惯把常用的 UI 操作封装成扩展函数。这样代码读起来像英语一样自然,维护起来也省心。今天我们就来聊聊,怎么给 View、Context、RecyclerView、Fragment 这些核心组件写扩展函数。
核心思想:扩展函数不是真的修改了类,而是通过静态导入的方式,让你能用点号调用。编译器会帮你做类型推断和分发。
6.1 View 扩展:visible()、gone()、invisible()
先来个最基础的。你想想看,每次写 view.visibility = View.GONE 是不是很烦?我早期项目里,这种代码到处都是。后来我统一改成了扩展函数,清爽多了。
// 最常用的三个状态
fun View.visible() {
visibility = View.VISIBLE
}
fun View.gone() {
visibility = View.GONE
}
fun View.invisible() {
visibility = View.INVISIBLE
}
// 带条件的版本,我个人更常用
fun View.visible(show: Boolean) {
visibility = if (show) View.VISIBLE else View.GONE
}
fun View.gone(hide: Boolean) {
visibility = if (hide) View.GONE else View.VISIBLE
}
嗯,这里要注意:visible() 和 gone() 是互逆操作。我见过有人把逻辑写反,结果界面死活不显示。建议你写个单元测试验证一下。
我的习惯:在基类或者工具类里统一放这些扩展。团队里所有人都用同一套命名,review 代码时一眼就能看懂意图。
6.2 Context 扩展:toast() 与 snackbar()
弹 Toast 是每个 App 都有的操作。但每次写 Toast.makeText(context, msg, length).show() 真的很啰嗦。我给它加了个扩展,一行搞定。
fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
// 在 Activity 或 Fragment 里直接调用
toast("操作成功")
toast("网络异常", Toast.LENGTH_LONG)
我曾经踩过一个坑:在非 UI 线程里调 Toast,结果崩溃了。所以如果你在后台线程用这个扩展,记得切回主线程。可以加个重载版本:
fun Context.toastOnMain(message: String) {
Handler(Looper.getMainLooper()).post {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Snackbar 也是同理。不过 Snackbar 需要 View 作为锚点,我一般这样封装:
fun View.snackbar(message: String, action: String? = null, onClick: (() -> Unit)? = null) {
val snack = Snackbar.make(this, message, Snackbar.LENGTH_SHORT)
if (action != null && onClick != null) {
snack.setAction(action) { onClick() }
}
snack.show()
}
6.3 RecyclerView 扩展:快速设置适配器
RecyclerView 的配置代码通常很模板化。我把它压缩成了一个扩展函数,配合高阶函数使用,效果拔群。
// 基础版本:直接设置 Adapter 和 LayoutManager
fun RecyclerView.setup(
layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(context),
adapter: RecyclerView.Adapter<*>
) {
this.layoutManager = layoutManager
this.adapter = adapter
}
// 高阶函数版本:支持自定义配置
fun RecyclerView.fastAdapter(
layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(context),
itemDecoration: RecyclerView.ItemDecoration? = null,
adapter: RecyclerView.Adapter<*>
) {
this.layoutManager = layoutManager
itemDecoration?.let { addItemDecoration(it) }
this.adapter = adapter
}
实际用起来是这样的:
recyclerView.fastAdapter(
layoutManager = GridLayoutManager(this, 2),
itemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
) {
// 这里传入你的 Adapter 实例
MyAdapter(dataList)
}
避坑指南:我曾经在 RecyclerView 的扩展里直接传了 lambda 作为 Adapter,结果导致内存泄漏。因为 lambda 隐式持有外部引用。建议 Adapter 还是单独定义,扩展只负责配置。
6.4 Fragment 扩展:简化事务操作
Fragment 的事务操作是另一个痛点。每次都要写 beginTransaction()、replace()、commit()。我封装了一个链式调用的扩展:
// 为 FragmentActivity 添加扩展
fun FragmentActivity.addFragment(
containerId: Int,
fragment: Fragment,
tag: String? = null
) {
supportFragmentManager.beginTransaction()
.add(containerId, fragment, tag)
.commit()
}
fun FragmentActivity.replaceFragment(
containerId: Int,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val transaction = supportFragmentManager.beginTransaction()
.replace(containerId, fragment, tag)
if (addToBackStack) {
transaction.addToBackStack(tag)
}
transaction.commit()
}
// 为 Fragment 自身添加扩展(方便 Fragment 内部调用)
fun Fragment.replaceWith(
containerId: Int,
fragment: Fragment,
tag: String? = null
) {
parentFragmentManager.beginTransaction()
.replace(containerId, fragment, tag)
.commit()
}
你想想看,这样写出来的代码是不是更像自然语言?
// 在 Activity 里
replaceFragment(R.id.container, UserProfileFragment(), "profile", true)
// 在 Fragment 里
replaceWith(R.id.container, DetailFragment(), "detail")
6.5 知识体系总览
下面这张图概括了本章的核心内容。我把它画成了 SVG,方便你理解各个扩展函数之间的关系。
6.6 总结与最佳实践
写扩展函数有几个原则,我总结一下:
- 命名要直观:
visible()比show()更贴近 Android 原生语义 - 参数要合理:默认值能省掉很多样板代码
- 不要滥用:如果一个扩展函数超过 10 行,考虑拆成多个
- 注意作用域:尽量定义在顶层文件里,方便全局复用
我的建议:把这些扩展函数放在一个单独的 extensions 包下。按组件分类,比如 ViewExt.kt、ContextExt.kt。这样新人来了,看一眼包名就知道去哪找。
好了,这一章的内容就到这里。扩展函数看似简单,但用好了能让你的代码质量提升一个档次。下一章我们会继续深入高阶函数,看看怎么把函数当作参数传递,写出更灵活的代码。