如何防止同时点击双按钮时导航至两个页面?
问题描述
我的应用包含3个页面(MainScreen、Screen1和Screen2),MainScreen上设有两个用于跳转至其他页面的按钮。
当前应用效果如下:
{: width="300"}
我遇到的问题是:如果同时点击这两个按钮,两个目标页面都会被加入回退栈。我希望避免这种情况,只打开一个页面。具体要求是:MainScreen必须始终保留在回退栈中,且其上方只能存在Screen1或Screen2中的一个页面。该如何实现?
现有代码
MainActivity.kt
enum class Routes { MainScreen, Screen1, Screen2 } sealed class UiEvent { data class Navigate(val route: String): UiEvent() } @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { TestTheme { val navController = rememberNavController() NavHost( navController = navController, startDestination = Routes.MainScreen.name ) { composable(route = Routes.MainScreen.name) { MainScreen(onNavigate = { navController.navigate(route = it.route) { launchSingleTop = true } },) } composable(route = Routes.Screen1.name) { Screen1() } composable(route = Routes.Screen2.name) { Screen2() } } } } } } @Composable fun MainScreen( onNavigate: (UiEvent.Navigate) -> Unit, viewModel: MainViewModel = hiltViewModel() ) { val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(key1 = true) { viewModel.uiEvent.collectLatest { uiEvent -> when (uiEvent) { is UiEvent.Navigate -> { onNavigate(uiEvent) } } } } Row { Button( onClick = { val currentState = lifecycleOwner.lifecycle.currentState if (currentState.isAtLeast(Lifecycle.State.RESUMED)) { viewModel.navigateTo(Routes.Screen1) } } ) { Text(text = "Screen1") } Button( onClick = { val currentState = lifecycleOwner.lifecycle.currentState if (currentState.isAtLeast(Lifecycle.State.RESUMED)) { viewModel.navigateTo(Routes.Screen2) } } ) { Text(text = "Screen2") } } } @Composable fun Screen1() { Text(text = "Screen 1") } @Composable fun Screen2() { Text(text = "Screen 2") }
MainViewModel.kt
@HiltViewModel class MainViewModel @Inject constructor() : ViewModel() { private val _uiEvent = Channel<UiEvent>() val uiEvent = _uiEvent.receiveAsFlow() fun navigateTo(route: Routes) { sendUiEvent(UiEvent.Navigate(route.name)) } private fun sendUiEvent(event: UiEvent) { viewModelScope.launch { _uiEvent.send(event) } } }
补充说明
我已修改代码和描述,并更新了图片。问题存在两种情况:
- 快速先后点击两个按钮(已解决)
- 同时点击两个按钮(未解决,如上方图片所示)
解决方案
要解决同时点击按钮导致双页面入栈的问题,可从导航栈结构控制和点击防抖两个维度处理:
1. 优化导航参数,固定回退栈结构
在navigate调用中补充popUpTo配置,确保MainScreen上方始终只有一个目标页面:
修改MainActivity中MainScreen的导航逻辑:
composable(route = Routes.MainScreen.name) { MainScreen(onNavigate = { navController.navigate(route = it.route) { launchSingleTop = true // 回退到MainScreen但保留它,同时清除其上方所有页面 popUpTo(Routes.MainScreen.name) { saveState = true inclusive = false } } },) }
popUpTo(Routes.MainScreen.name):导航时自动回退到MainScreen页面inclusive = false:不包含MainScreen本身,确保它始终留在栈底saveState = true:可选,保留MainScreen的页面状态
2. 添加点击防抖,禁止并发导航请求
在ViewModel中添加状态标记,防止同时处理多个跳转事件:
修改MainViewModel.kt:
@HiltViewModel class MainViewModel @Inject constructor() : ViewModel() { private val _uiEvent = Channel<UiEvent>() val uiEvent = _uiEvent.receiveAsFlow() // 标记当前是否正在处理导航 private val _isNavigating = mutableStateOf(false) val isNavigating: State<Boolean> = _isNavigating fun navigateTo(route: Routes) { if (!_isNavigating.value) { _isNavigating.value = true sendUiEvent(UiEvent.Navigate(route.name)) } } private fun sendUiEvent(event: UiEvent) { viewModelScope.launch { try { _uiEvent.send(event) } finally { // 导航事件发送完成后重置状态 _isNavigating.value = false } } } }
然后在MainScreen中根据isNavigating状态禁用按钮:
@Composable fun MainScreen( onNavigate: (UiEvent.Navigate) -> Unit, viewModel: MainViewModel = hiltViewModel() ) { val lifecycleOwner = LocalLifecycleOwner.current val isNavigating by viewModel.isNavigating LaunchedEffect(key1 = true) { viewModel.uiEvent.collectLatest { uiEvent -> when (uiEvent) { is UiEvent.Navigate -> { onNavigate(uiEvent) } } } } Row { Button( onClick = { val currentState = lifecycleOwner.lifecycle.currentState if (currentState.isAtLeast(Lifecycle.State.RESUMED)) { viewModel.navigateTo(Routes.Screen1) } }, enabled = !isNavigating ) { Text(text = "Screen1") } Button( onClick = { val currentState = lifecycleOwner.lifecycle.currentState if (currentState.isAtLeast(Lifecycle.State.RESUMED)) { viewModel.navigateTo(Routes.Screen2) } }, enabled = !isNavigating ) { Text(text = "Screen2") } } }
3. 可选:增加目的地校验
导航前判断当前栈顶是否为目标页面,避免重复跳转:
composable(route = Routes.MainScreen.name) { MainScreen(onNavigate = { val currentRoute = navController.currentDestination?.route if (currentRoute == it.route) return@MainScreen navController.navigate(route = it.route) { launchSingleTop = true popUpTo(Routes.MainScreen.name) { saveState = true inclusive = false } } },) }
内容的提问来源于stack exchange,提问作者Matteo Zattera
相关产品推荐
相关产品推荐

