Administrator
发布于 2026-09-22 / 6,561 阅读
0
0

Android 预测式返回:Predictive Back Gesture 工程实现

前言

Android 13 开始引入预测式返回(Predictive Back),Android 14 正式开放给第三方 App。到了 Android 15,系统开始对未适配的 App 强制走系统默认行为,不再给"假装没看见"的机会。

如果你还在用 onBackPressed() 硬扛,那这篇文章就是给你做迁移用的。如果你已经在用 OnBackPressedDispatcher,这里讲怎么把它升级到预测式动画。

一、什么是预测式返回

旧版返回:用户按返回键 -> 系统直接调 onBackPressed() -> 页面消失。用户按下去之前看不到任何反馈。

预测式返回:用户按住返回键 -> 系统展示"返回后去哪里"的动画预览(缩小当前界面、露出目标界面)-> 用户松手确认返回,或者滑动取消。

核心区别就是"先预览,再确认"。跟 iOS 的边缘滑动返回体验类似,但 Android 的实现方式不同。

旧流程: 按下返回 -> 立即 finish()
新流程: 按住返回 -> 预览动画 -> 松手确认 / 滑动取消

二、迁移三步走

第 1 步:Manifest 声明 opt-in

<application
    android:enableOnBackInvokedCallback="true"
    ... >

这个属性控制是否启用预测式回调。设为 true 后,系统会把返回事件交给 OnBackPressedDispatcher 处理,而不是直接走 onBackPressed() 的旧路径。

第 2 步:用 OnBackPressedDispatcher 替换 onBackPressed

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 旧方式:override fun onBackPressed() - 已废弃
        // 新方式:注册 OnBackPressedCallback
        onBackPressedDispatcher.addCallback(this) {
            // 这里写返回逻辑
            showExitConfirmDialog()
        }
    }

    private fun showExitConfirmDialog() {
        // 双击退出或弹确认框
    }
}

OnBackPressedCallback 是有优先级的。后注册的先执行。如果 callback 返回 false(或不处理),事件会传递给下一个 callback,最终到系统默认行为。

第 3 步:加预测式动画

这是最关键的一步。你需要告诉系统"返回后用户会看到什么",系统才能做预览动画。

@OptIn(OnBackInvokedCallbackApi::class)
class MainActivity : ComponentActivity() {

    private var backInvokedCallback: OnBackInvokedCallback? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 方式一:OnBackInvokedCallback(系统级 API)
        backInvokedCallback = object : OnBackInvokedCallback {
            override fun onBackInvoked() {
                // 用户松手确认返回
                onBackPressedDispatcher.onBackPressed()
            }
        }
        onBackInvokedDispatcher.registerOnBackInvokedCallback(
            OnBackInvokedCallbackPriority.DEFAULT,
            backInvokedCallback!!
        )

        // 方式二:Jetpack 封装(推荐)
        onBackPressedDispatcher.addCallback(this,
            object : OnBackPressedCallback(true) {
                override fun handleOnBackStarted() {
                    // 用户开始滑,准备动画
                }

                override fun handleOnBackProgressed(backEvent: BackEventCompat) {
                    // 滑动过程中,根据 progress 做缩放
                    val scale = 1f - backEvent.progress * 0.1f
                    binding.root.scaleX = scale
                    binding.root.scaleY = scale
                }

                override fun handleOnBackPressed() {
                    // 确认返回
                    finish()
                }

                override fun handleOnBackCancelled() {
                    // 用户取消,恢复
                    binding.root.animate().scaleX(1f).scaleY(1f).start()
                }
            }
        )
    }
}

BackEventCompat 提供三个值:

  • progress:0.0 ~ 1.0,手指滑动进度

  • swipeEdge:滑动起始边缘(左/右)

  • touchX / touchY:触摸点坐标

三、Compose 中的预测式返回

Compose 场景更简单,PredictiveBackHandler 已经封装好了:

@Composable
fun BackHandlerWithAnimation(content: @Composable () -> Unit) {
    var predictiveBack by remember { mutableStateOf(Progress(press = 0f)) }

    PredictiveBackHandler { back ->
        try {
            val progress = back.progress()
            val anim = Animatable(progress)
            launch {
                anim.animateTo(1f)
            }
            predictiveBack = Progress(anim.value)
            awaitBackProgressed()
        } catch (e: CancellationException) {
            // 取消,回弹
            launch {
                Animatable(progress).animateTo(0f)
            }
        }
    }

    Box(
        Modifier.graphicsLayer {
            scaleX = 1f - predictiveBack.press * 0.2f
            scaleY = 1f - predictiveBack.press * 0.2f
            translationX = predictiveBack.press * 100f
        }
    ) {
        content()
    }
}

data class Progress(val press: Float)

四、兼容性矩阵

Android 版本

预测式返回

opt-in 要求

动画支持

13 (API 33)

实验性

enableOnBackInvokedCallback=true

系统级,App 不可控

14 (API 34)

正式可用

enableOnBackInvokedCallback=true

App 级动画

15 (API 35)

推荐启用

不声明则走旧逻辑

全面支持

实际项目中建议的做法:

// 根据版本分发
if (Build.VERSION.SDK_INT >= 34) {
    // 用 OnBackInvokedCallback + 自定义动画
} else {
    // 回退到 OnBackPressedDispatcher
}

五、常见坑

  1. WebView 吃掉返回事件。WebView 内部 canGoBack() 为 true 时会截获返回,你的预测式 callback 不触发。解决:在 WebView 的 OnBackPressedCallback 里判断 canGoBack(),如果为 false 就 isEnabled = false,让事件传下去。

  2. Fragment 栈混乱。多个 Fragment 各自注册 callback,生命周期管理不好会导致返回到错误页面。建议用 LifecycleOwner 参数自动绑定:

// LifecycleOwner DESTROY 时自动移除
onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
    findNavController().popBackStack()
}
  1. 动画卡顿。handleOnBackProgressed 每帧调,不要在里面做重操作。用 graphicsLayer 做 GPU 变换,不要用 setLayoutParams。

小结

预测式返回不是"换个 API 调用"的事,它改变了返回交互的范式:从"按下即执行"变成"预览-确认"。

迁移路径:onBackPressed() -> OnBackPressedDispatcher -> OnBackInvokedCallback + 动画。

下篇讲 KSP vs KAPT。如果你的项目还在用 KAPT 编译注解处理器,编译速度可能被拖慢 30% 以上,KSP 是替代方案。


评论