Jetpack Compose按钮点击未触发重组,原因何在?
我原本以为当状态变化或可组合项(如按钮文本、尺寸)被修改时,会触发该可组合项的重组。但在我的示例中遇到了困惑:
我定义了一个挂起函数timerForCompose(),会在延迟指定时间后打印“Timer ended”,若协程终止则打印“Timer cancelled”。该挂起函数在可组合函数Timer()中调用,并在LaunchedEffect的lambda中执行。
主可组合函数TimerScreen1包含一个Button,点击按钮会修改按钮尺寸,Timer()接收由状态对象timerDuration提供的初始延迟时间。
代码如下:
挂起函数
suspend fun timerForCompose(time: Long, onTimerEnd: () -> Unit){ delay(timeMillis = time) onTimerEnd() }
Timer可组合函数
@Composable fun Timer(timerDuration: Long) { LaunchedEffect(key1 = Unit){ try { timerForCompose(timerDuration){ println("Timer ended successfully") } } catch (ex: Exception){ println("Timer canceled") } } }
TimerScreen1函数
@Composable fun TimerScreen1() { var dpForButton = remember { mutableStateOf(200.dp) } var timerDuration = remember { mutableLongStateOf(4000L) } Button(onClick = { dpForButton.value = 400.dp }, modifier = Modifier.size(dpForButton.value)) { Text(timerDuration.value.toString()) Timer(timerDuration = timerDuration.value + 2000L) } }
实际现象:每次点击按钮时Button可组合项并未触发预期的Timer协程重启,按逻辑点击按钮后整个Button应重组,进而重新调用Timer()函数,但实际并未发生,仅在旋转屏幕时才会看到Timer的协程重新执行。我把timerDuration设为状态是参考了相关示例,简化到按钮部分后预期会触发重组,现在不理解为什么没触发。
核心原因:LaunchedEffect的key设置与状态变化范围
点击按钮后的重组逻辑:
点击按钮修改dpForButton.value时,确实会触发TimerScreen1的重组,Button的尺寸也会更新。但Timer组件内的LaunchedEffect不会重新执行,因为你给它设置的key是Unit——LaunchedEffect的key决定了何时重启协程,当key值不变时,即使组件重组,lambda内的逻辑也只会执行一次(首次进入组合时)。旋转屏幕触发的原因:
旋转屏幕会导致整个Compose组合树被销毁重建,Timer组件会重新进入组合,此时LaunchedEffect会再次执行,所以你能看到日志输出。
修复方案
1. 让LaunchedEffect随计时器时长变化重启
将timerDuration作为LaunchedEffect的key,这样当timerDuration参数变化时,协程会自动重启:
@Composable fun Timer(timerDuration: Long) { // 用timerDuration作为key,参数变化时重启协程 LaunchedEffect(key1 = timerDuration){ try { timerForCompose(timerDuration){ println("Timer ended successfully") } } catch (ex: Exception){ println("Timer canceled") } } }
2. 让点击按钮时同步修改计时器状态
当前点击按钮仅修改了按钮尺寸,没有改变timerDuration.value,所以Timer接收的参数timerDuration.value + 2000L不会变化。如果希望点击按钮时同时更新计时器时长,需要在点击事件中修改timerDuration:
Button( onClick = { dpForButton.value = 400.dp // 修改timerDuration,触发Timer的参数更新 timerDuration.value += 2000L }, modifier = Modifier.size(dpForButton.value) ) { Text(timerDuration.value.toString()) Timer(timerDuration = timerDuration.value + 2000L) }
完成以上修改后,点击按钮时:
- 按钮尺寸会因
dpForButton变化而更新 timerDuration的变化会触发Timer组件的参数更新,加上LaunchedEffect的key绑定了timerDuration,协程会重启,你就能看到新的计时器日志输出。
内容的提问来源于stack exchange,提问作者steeveKA1

