在 Android 开发中,我们经常遇到这样的场景:在一个复杂的自定义 View(比如悬浮面板)里,用户点击关闭按钮,我们需要关闭当前的 Activity。
最直觉(也是最初级)的写法往往是这样的:
Kotlin
// ❌ 反面教材:强耦合写法
fun onCloseBtnClick() {
// 直接把 Context 强转成 Activity
val activity = context as? Activity
activity?.finish()
}
这段代码虽然能跑,但它有两个致命问题:
- 强耦合: 这个 View 彻底依赖了 Activity。如果我想把它放到 Fragment 或者 Dialog 里,这行代码就废了。
- 崩溃隐患: 如果 View 的 Context 被
ContextWrapper包了一层(比如用了换肤库或 Hilt),强转可能会失败甚至导致 Crash。 - 时序错乱: 如果 View 还有关闭动画(比如收起面板),直接
finish()会导致动画还没播完页面就没了,体验极差。
如何解决这个问题?
第一步
(核心概念:View 只负责"通知",不负责"决策")
要解耦,核心思想是 Inversion of Control (控制反转) 。View 不应该命令 Activity 去死(finish),View 应该只是告诉 Activity:"我这边完事了"。
我们可以利用 Kotlin 的高阶函数(Lambda)来实现:
Kotlin
// ✅ 进阶写法:使用回调
class ResultPanelView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
// 定义一个回调,告知外部"我想关闭了"
var onRequestClose: (() -> Unit)? = null
fun triggerClose() {
// ... 执行内部逻辑 ...
// 通知外部,至于外部是 finish 还是 popBackStack,View 不关心
onRequestClose?.invoke()
}
}
在 Activity 中使用:
Kotlin
resultView.onRequestClose = {
// Activity 拿回了控制权
finish()
}
第二步
很多时候,我们的 View 是一个悬浮面板,关闭时有一个"下沉"或"淡出"的动画。如果直接在点击事件里回调 finish(),动画会被直接截断。
这里需要引入状态流 和动画监听的概念:
Kotlin
class FloatingResultView(...) : FrameLayout(...) {
// 专门的回调:当 View 彻底"消失"后触发
var onDismissComplete: (() -> Unit)? = null
fun dismiss() {
// 1. 先播放动画
this.animate()
.translationY(height.toFloat())
.setDuration(300)
.withEndAction {
// 2. 动画播完了,再通知外部
onDismissComplete?.invoke()
}
.start()
}
}
这样,Activity 里的 finish() 实际上是在动画结束后才执行的,用户体验会非常丝滑(Silky Smooth)。
总结
从 (context as Activity).finish() 到 回调,这不仅仅是代码行数的变化,更是思维模式的转变:
- 从"命令式"到"响应式" :View 不再指挥 Activity,而是通过回调暴露状态。
- 关注点分离:View 负责展示和动画,Activity 负责业务流转和页面栈管理。
- 健壮性:解耦后的 View 可以移植到任何地方,再也不用担心 Context 类型不对了。
