CMP for OpenHarmony:Counter 计数器的“写入收口”实现——用 applyCount 统一边界、步进与操作来源(项目实战代码)

仓库地址:通过网盘分享的文件:cmp_openharmony.zip
链接: https://pan.baidu.com/s/15rN1LvJ0KENMkYZfLq_R1Q?pwd=nhqe 提取码: nhqe

计数器(Counter)看起来是最简单的组件之一,但在工程里它经常会变成“很多地方都能改同一个数值”的状态源:

  • 主按钮点击加/减
  • 设置面板调整步进、最小/最大范围
  • 业务流程里某个事件需要把数值直接置为最小/最大
  • 需要记录最近一次操作来源,便于排查状态变化

如果这些写入逻辑分散在各个 onClick 中,后续一旦加上边界规则、埋点、联动其它状态,就会非常难维护。

本文基于本仓库真实代码,展示一个实用思路:把“计数值写入”统一收口到一个函数(applyCount,让所有入口共享同一套边界与行为语义。


1. 代码位置:示例页在哪

本文所有 Kotlin 代码均来自下面文件(复制路径即可定位):

  • composeApp/src/commonMain/kotlin/com/tencent/compose/sample/counter/CounterDemoPage.kt

2. 最关键的一步:把写入收口到 applyCount(而不是每个按钮各改各的)

示例页在页面层定义了一个“写入收口函数”(节选,保持项目原样):

fun clamp(value: Int): Int {
    if (!clampEnabled) return value
    return value.coerceIn(minValue, maxValue)
}

fun applyCount(next: Int, source: String) {
    val clamped = clamp(next)
    count = clamped
    lastAction = if (clamped != next && clampEnabled) {
        "$source:请求 $next,已收敛到 $clamped"
    } else {
        "$source:设置为 $clamped"
    }
}

这段代码解决的是“工程可控性”问题,而不是 UI 问题:

  • clamp(...) 负责边界策略:当 clampEnabled == true 时,把任意输入收敛到 [minValue, maxValue]
  • applyCount(...) 负责写入语义:
    • 唯一写入点:所有入口最终都调用它。
    • 带来源source 把操作来源带进 lastAction,便于你在 Demo 或日志里追踪“是谁改了值”。
    • 收敛可观测:如果请求值被边界收敛,会写一条更明确的提示。

当你后续要加埋点、加权限校验、或者让计数值影响其它组件时,只需要改 applyCount,不会到处找按钮回调。


3. 页面状态:计数值、步进、边界、开关各自独立

示例页的状态组织方式(节选,保持项目原样):

var count by remember { mutableIntStateOf(0) }

var step by remember { mutableIntStateOf(1) }
var minValue by remember { mutableIntStateOf(0) }
var maxValue by remember { mutableIntStateOf(20) }

var clampEnabled by remember { mutableStateOf(true) }
var lastAction by remember { mutableStateOf("未操作") }

这套状态的好处是“职责清晰”:

  • count:计数值本体(单一事实来源)。
  • step:步进策略(让 +1/-1 变为 +step/-step)。
  • minValue/maxValue:边界范围。
  • clampEnabled:是否启用边界策略。
  • lastAction:结果记录(Demo 用于展示,工程里也可替换为日志/埋点)。

4. 主操作区:加/减/重置只做一件事——调用 applyCount

主区按钮实现(节选,保持项目原样):

Button(
    onClick = { applyCount(count + step, "加") },
    modifier = Modifier.height(42.dp),
    colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF6200EE))
) {
    Text(text = "+$step", color = Color.White)
}

OutlinedButton(
    onClick = { applyCount(count - step, "减") },
    modifier = Modifier.height(42.dp)
) {
    Text(text = "-$step")
}

OutlinedButton(
    onClick = {
        applyCount(0, "重置")
    },
    modifier = Modifier.height(42.dp)
) {
    Text(text = "重置")
}

解释要点:

  • 按钮回调里不直接修改 count,而是统一走 applyCount
  • 这能保证:无论从哪个入口修改计数,边界规则与记录规则都一致。
  • +step/-step 的 UI 文案直接绑定 step,用户能清楚知道一次点击会变化多少。

5. 控制面板:把“策略变量”独立出来(开关、步进、范围)

5.1 边界开关:用 Switch 控制 clampEnabled

Switch(checked = clampEnabled, onCheckedChange = { clampEnabled = it })

说明:

  • 这里没有在切换开关时立刻改 count,而是把“是否收敛”作为策略保存。
  • 计数值的收敛发生在下一次 applyCount 调用时,也可以按业务需要在开关变化时主动 applyCount(count, "切换约束") 做一次即时收敛。

5.2 步进调整:把 step 限定到合理范围

step = (step - 1).coerceAtLeast(1)

step = (step + 1).coerceAtMost(10)

说明:

  • 这里用 coerceAtLeast(1) / coerceAtMost(10) 做了边界,避免步进变成 0 或无限增大。
  • 工程里常见做法是把 step 做成业务可配置参数,这里用按钮模拟。

5.3 最小/最大范围调整:永远保持 min <= max

示例页在调整最小值/最大值时都有保护(节选,保持项目原样):

minValue = (minValue - 1).coerceAtLeast(-20)
if (minValue > maxValue) minValue = maxValue
applyCount(count, "更新最小值")
maxValue = (maxValue - 1).coerceAtLeast(minValue)
applyCount(count, "更新最大值")

说明:

  • 最重要的不是“调范围”,而是维护不变量minValue <= maxValue
  • 每次范围变化后,都调用一次 applyCount(count, ...),目的是:
    • 如果当前 count 已经越界,可以立即被收敛到新范围内。
    • 同时把“范围更新导致的收敛”记录到 lastAction,便于观察。

6. 快捷区:在不新增新规则的前提下提供更多入口

示例页提供了快捷 +5/-5,并同样走 applyCount(节选,保持项目原样):

OutlinedButton(onClick = { applyCount(count + 5, "快捷 +5") }) {
    Text(text = "+5")
}

OutlinedButton(onClick = { applyCount(count - 5, "快捷 -5") }) {
    Text(text = "-5")
}

说明:

  • 快捷操作的本质是“新增入口”,不是“新增规则”。
  • 入口越多,越需要 applyCount 这种统一写入点,否则边界/埋点/记录会立刻散掉。

7. 如何在工程里直接体验该示例

本仓库的 Demo 入口是 MainPage,你可以临时切换入口来快速验证:

  • 入口文件:composeApp/src/commonMain/kotlin/com/tencent/compose/sample/mainpage/MainPage.kt
  • 入口渲染切换为:com.tencent.compose.sample.counter.CounterDemoPage()

8. 自检清单:计数器在工程里最容易踩的坑

  • 写入点过多:任何地方都能 count++ 会导致规则无法统一,建议收口到 applyCount
  • 边界规则与 UI 脱节:UI 禁用按钮不够,写入函数必须兜底 coerceIn
  • 范围更新不校验当前值:更新 min/max 后应该立即检查并收敛现有 count
  • 缺少可观测性:Demo/调试期建议保留 lastAction 这类来源记录,定位问题更快。

在这里插入图片描述


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

社区链接

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐