Jetpack Compose中如何限制两个重量选择器的数值范围?
实现目标体重不大于当前体重的逻辑
核心思路
通过将体重转换为总克数统一比较,避免分别处理千克和克的复杂逻辑,然后根据当前选择的是「当前体重」还是「目标体重」,动态限制选择器的范围:
- 选择目标体重时,最大值为「当前体重 - 1克」
- 选择当前体重时,最小值为「目标体重 + 1克」
具体实现步骤
1. 添加辅助转换函数
先写两个工具函数,用于在「千克+克」和「总克数」之间转换,简化比较逻辑:
// 将kg和gr转换为总克数,处理null参数的默认值 private fun toTotalGrams(kg: Int?, gr: Int?): Int { val safeKg = kg ?: 35 val safeGr = gr ?: 0 return safeKg * 1000 + safeGr } // 将总克数拆分为kg和gr的Pair private fun fromTotalGrams(totalGrams: Int): Pair<Int, Int> { val kg = totalGrams / 1000 val gr = totalGrams % 1000 return kg to gr }
2. 修改组件状态初始化
不再使用固定的35kg初始值,而是根据选择类型加载对应已保存的体重,同时确保初始值在允许范围内:
@Composable fun DailyCalorieWeightPickerComponent( onConfirmClick: (Int, Int) -> Unit, onCancelClick: () -> Unit, currentKg: Int?, currentGr: Int?, goalKg: Int?, goalGr: Int?, isClickGoalWeight: Boolean ) { // 基础重量范围:35kg(35000g) ~ 200kg(200000g) const val MIN_TOTAL_GRAMS = 35 * 1000 const val MAX_TOTAL_GRAMS = 200 * 1000 // 计算允许的总重量范围 val (minAllowedTotal, maxAllowedTotal) = if (isClickGoalWeight) { // 目标体重:最大为当前体重减1克,最小不低于基础最小值 val currentTotal = toTotalGrams(currentKg, currentGr) MIN_TOTAL_GRAMS to (currentTotal - 1).coerceAtLeast(MIN_TOTAL_GRAMS) } else { // 当前体重:最小为目标体重加1克,最大不超过基础最大值 val goalTotal = toTotalGrams(goalKg, goalGr) (goalTotal + 1).coerceAtMost(MAX_TOTAL_GRAMS) to MAX_TOTAL_GRAMS } // 初始化并校验初始值在允许范围内 val initialTotal = if (isClickGoalWeight) { toTotalGrams(goalKg, goalGr) } else { toTotalGrams(currentKg, currentGr) } val validInitialTotal = initialTotal.coerceIn(minAllowedTotal, maxAllowedTotal) val (validInitialKg, validInitialGr) = fromTotalGrams(validInitialTotal) var kg by remember { mutableStateOf(validInitialKg) } var gr by remember { mutableStateOf(validInitialGr) }
3. 动态计算选择器范围
根据当前选中的千克数,动态计算克数的可选范围,确保总重量不超出限制:
// 计算kg的可选范围 val kgRange = run { val (minKg, _) = fromTotalGrams(minAllowedTotal) val (maxKg, _) = fromTotalGrams(maxAllowedTotal) minKg..maxKg } // 计算当前kg对应的gr可选范围 val currentGrRange = run { val currentKgTotal = kg * 1000 val minGr = if (kg == kgRange.first) { minAllowedTotal - currentKgTotal } else { 0 } val maxGr = if (kg == kgRange.last) { maxAllowedTotal - currentKgTotal } else { 999 // 修正:gr范围应为0~999,避免出现1000g等于1kg的重复计算 } minGr..maxGr }
4. 更新选择器的交互逻辑
修改NumberPicker的范围和值变更回调,确保选择的重量始终符合规则:
Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom ) { Row( modifier = Modifier .fillMaxWidth() .background(Color.Gray) .padding(top = 30.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { NumberPicker( value = kg, onValueChange = { newKg -> kg = newKg // kg变化时,自动将gr调整到当前kg允许的范围内 gr = gr.coerceIn(currentGrRange) }, range = kgRange ) Text(text = "kg") NumberPicker( value = gr, onValueChange = { newGr -> gr = newGr.coerceIn(currentGrRange) }, range = currentGrRange ) Text(text = "gr") } // 底部确认/取消按钮部分保持不变 Row( modifier = Modifier .fillMaxWidth() .height(IntrinsicSize.Min) .background(Color.Gray) .padding(vertical = 15.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { TextButton(onClick = onCancelClick) { Text( text = "cancel", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DefaultDYTColor, modifier = Modifier.padding(8.dp) ) } Divider( color = Color.White, modifier = Modifier .padding(8.dp) .fillMaxHeight() .width(1.dp) ) TextButton(onClick = { onConfirmClick(kg, gr) }) { Text( text = "okey", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = DefaultDYTColor, modifier = Modifier.padding(8.dp) ) } } } }
关键细节说明
- 修正了gr的范围为0~999,避免出现1000g等于1kg的逻辑错误
- 使用
coerceIn确保所有选择值都在允许范围内,包括初始值和用户操作后的数值 - 通过总克数统一比较,避免了分别判断kg和gr的复杂逻辑,代码更简洁可靠
内容的提问来源于stack exchange,提问作者NewPartizal
相关产品推荐
相关产品推荐

