You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Jetpack Compose:如何在多可组合页面间保持键盘常驻?

解决Jetpack Compose切换页面时保持TextField键盘不关闭的问题

核心问题在于切换页面时,旧页面的TextField会从组合树中移除,触发CoreTextField中的DisposableEffect执行onBlur,导致键盘关闭。要解决这个问题,需要让新页面的TextField在旧页面组件销毁前获取焦点,避免输入会话中断。

方案一:共享焦点请求器+延迟旧页面销毁

通过AnimatedVisibility给旧页面设置极短的退出动画,让新页面的TextField先获取焦点,再完成旧页面的销毁,从而保持键盘持续打开。

示例代码

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        var screenIndex by remember { mutableStateOf(0) }
        // 共享焦点请求器,让两个页面的TextField共用一个请求实例
        val sharedFocusRequester = remember { FocusRequester() }

        Box(modifier = Modifier.fillMaxSize()) {
            // 旧页面延迟10ms销毁,给新页面留足获取焦点的时间
            AnimatedVisibility(
                visible = screenIndex == 0,
                exit = fadeOut(animationSpec = tween(10))
            ) {
                Screen(index = 0, focusRequester = sharedFocusRequester) {
                    screenIndex = 1
                }
            }
            AnimatedVisibility(
                visible = screenIndex == 1,
                enter = fadeIn(animationSpec = tween(10))
            ) {
                Screen(index = 1, focusRequester = sharedFocusRequester) {
                    screenIndex = 0
                }
            }
        }
    }
}

@Composable
fun Screen(
    index: Int,
    focusRequester: FocusRequester,
    onButtonClick: () -> Unit,
) {
    Column(
        modifier = Modifier.fillMaxSize().imePadding(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        var value by remember { mutableStateOf("$index") }
        TextField(
            modifier = Modifier.focusRequester(focusRequester),
            value = value,
            onValueChange = { value = it },
        )

        Button(onClick = {
            // 切换页面前先让当前焦点请求器获取焦点(此时新页面的TextField已准备好)
            focusRequester.requestFocus()
            onButtonClick()
        }) {
            Text("Change screen")
        }
    }

    // 页面加载时自动请求焦点
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

原理说明

  • 共享FocusRequester确保新旧页面的TextField可以复用同一个焦点请求通道。
  • 极短的退出动画(10ms)让旧页面不会立即从组合树中移除,给新页面的TextField足够时间获取焦点,此时旧页面的onBlur触发时,焦点已经转移到新组件,系统不会关闭键盘。

方案二:针对Navigation库场景的适配

如果使用Jetpack Navigation进行页面导航,可在导航宿主中共享FocusRequester,并利用导航过渡动画实现类似延迟销毁的效果:

  1. 在NavHost的父级Composable中创建共享FocusRequester。
  2. 给目标页面的TextField传递该请求器,在导航前调用focusRequester.requestFocus()。
  3. 配置导航的退出动画,延长旧页面的销毁时间。

关键代码片段

// NavHost所在的Composable
@Composable
fun AppNavHost() {
    val navController = rememberNavController()
    val sharedFocusRequester = remember { FocusRequester() }

    NavHost(navController = navController, startDestination = "screen1") {
        composable(
            "screen1",
            enterTransition = { fadeIn(tween(10)) },
            exitTransition = { fadeOut(tween(10)) }
        ) {
            Screen1(focusRequester = sharedFocusRequester) {
                sharedFocusRequester.requestFocus()
                navController.navigate("screen2")
            }
        }
        composable(
            "screen2",
            enterTransition = { fadeIn(tween(10)) },
            exitTransition = { fadeOut(tween(10)) }
        ) {
            Screen2(focusRequester = sharedFocusRequester) {
                sharedFocusRequester.requestFocus()
                navController.popBackStack()
            }
        }
    }
}

为什么之前的尝试无效

  • 延迟打开键盘:属于被动补救,无法避免旧页面销毁时触发的键盘关闭动作,仍会出现闪烁。
  • 切换前禁用TextField:会直接丢失焦点,触发键盘关闭,且ViewModel层导航无法同步该操作,扩展性差。
  • InputMethodManager方法:系统级API无法与Compose的焦点管理机制深度协同,且toggleSoftInput已废弃,兼容性无法保证。

内容的提问来源于stack exchange,提问作者Alexey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 02:06:03