ARTICLE · 1227076
保姆级学习开发安卓手机软件实战(十二)--主界面搭建(更换主题)
保姆级学习开发安卓手机软件实战(十二)--主界面搭建(更换主题)

因为基本通信搭建好了,然后也是我们终于讨论出所有大致功能,我让AI设计了几版UI,最后选定了这款灰橙色作为主题色,主界面效果如下: 平板效果:
手机效果:
(我这个实战步骤可能比较曲折。。。实在是公司开发软件的流程是走一步看一步,随时用不惯再改,然后又突如其来的各种想法,没办法有需求文档后稳步开发,这是不太好的地方,但是也算挑战吧。。。)
大家应该还记得,首先是主题色那几个文件的替换了:
步骤 1:整个替换 ui/theme/Color.kt
package com.example.jstz1.ui.themeimport androidx.compose.ui.graphics.Color/*** 风格B「工业石墨橙」色板** 1:1 对应《ui/主界面-风格B-工业石墨橙.html》第 7~19 行的 :root 变量,* 命名沿用 CSS 变量名(HTML 里 --accent,这里就是 Accent),* 以后想改色直接对着效果图找,不用翻译。*/// ========== 背景层 ==========val Bg = Color(0xFF0E1114) // --bg 最底层val Stage = Color(0xFF14181C) // --stage 内容舞台val PanelA = Color(0xFF1D2228) // --panel-a 面板渐变·上val PanelB = Color(0xFF181C21) // --panel-b 面板渐变·下val CardA = Color(0xFF232930) // --card-a 卡片渐变·上val CardB = Color(0xFF1D2228) // --card-b 卡片渐变·下// ========== 描边 ==========val PBorder = Color(0xFF333B44) // --pborder 面板描边val PBorder2 = Color(0xFF47525F) // --pborder2 卡片描边(更亮一档)// ========== 文字(三级)==========val Text = Color(0xFFE9EDF2) // --text 主文字val Sub = Color(0xFF9AA6B4) // --sub 次要文字val Dim = Color(0xFF64707D) // --dim 弱化文字// ========== 主色:石墨橙 ==========val Accent = Color(0xFFFF8A1E) // --accent 按钮/选中/装饰线val AccentBg = Color(0xFF3A2A18) // --accent-bg 橙色淡底(选中态)val OnAccent = Color(0xFF231303) // --on-accent 橙底上的字(深棕近黑)// ========== 状态三色 ==========val Ok = Color(0xFF57B87E) // --ok 在线 / 运行val OkDark = Color(0xFF0B1512) // --ok-dark(仅单设备界面用到)val Err = Color(0xFFE85D5D) // --err 异常 / 停止val Warn = Color(0xFFE8B33B) // --warn 报警// ========== 按钮 ==========val Btn = Color(0xFF232930) // --btn 普通按钮底val BtnT = Color(0xFFD7DEE6) // --btn-t 按钮文字// ========== 群控专用(第 5.3 节用)==========val RunBg = Color(0xFF1E5E46) // --run-bg 运行按钮底val RunBd = Color(0xFF57B87E) // --run-bd 运行按钮描边val StopBg = Color(0xFF6E2A2A) // --stop-bg 停止按钮底val StopBd = Color(0xFFE85D5D) // --stop-bd 停止按钮描边// ========== 顶栏 / 底栏 ==========val TopA = Color(0xFF22272D) // --top-a 顶栏渐变·上val TopB = Color(0xFF191D22) // --top-b 顶栏渐变·下val Bar = Color(0xFF191D22) // --bar 底栏
步骤 2:整个替换 ui/theme/Theme.kt
package com.example.jstz1.ui.themeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.darkColorSchemeimport androidx.compose.runtime.Composable/*** 把「石墨橙设计变量」填进 Material3 的语义槽。** 设计稿给的是「这个色叫什么」(accent / panel-a / ok)* Material3 要的是「这个色用在哪」(primary / surface / tertiary)* 这里映射一次,之后 UI 代码只写 MaterialTheme.colorScheme.xxx,* 不直接碰色值——想换主题时改这一处即可。*/private val JstzColorScheme = darkColorScheme(// 主色 = 石墨橙primary = Accent,onPrimary = OnAccent,primaryContainer = AccentBg,onPrimaryContainer = Accent,// 次要 = 普通按钮secondary = Btn,onSecondary = BtnT,// 背景 / 面板 / 卡片background = Bg,onBackground = Text,surface = PanelA,onSurface = Text,surfaceVariant = CardA,onSurfaceVariant = Sub,// 描边outline = PBorder,outlineVariant = PBorder2,// 第三色 = 运行/成功(卡片圆点用)tertiary = Ok,onTertiary = OkDark,// 错误error = Err,onError = OnAccent,errorContainer = StopBg,onErrorContainer = Err,)/*** 工业 HMI 用固定深色主题:* 不跟随系统深色/浅色,也不吃 Android 12+ 的动态取色* (取色会把你的橙色换成用户壁纸色,控制屏不能这么干)。*/@ComposablefunJSTZ1Theme(content: @Composable () -> Unit) {MaterialTheme(colorScheme = JstzColorScheme,typography = Typography,content = content)}
步骤 3:主界面布局
整个替换 ui/screen/DeviceListScreen.kt
package com.example.jstz1.ui.screenimport androidx.compose.foundation.backgroundimport androidx.compose.foundation.borderimport androidx.compose.foundation.clickableimport androidx.compose.foundation.rememberScrollStateimport androidx.compose.foundation.verticalScrollimport androidx.compose.foundation.layout.Arrangementimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.Spacerimport androidx.compose.foundation.layout.fillMaxHeightimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.defaultMinSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.heightimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.foundation.layout.widthimport androidx.compose.foundation.lazy.grid.GridCellsimport androidx.compose.foundation.lazy.grid.LazyVerticalGridimport androidx.compose.foundation.lazy.grid.itemsimport androidx.compose.foundation.shape.CircleShapeimport androidx.compose.foundation.shape.RoundedCornerShapeimport androidx.compose.material3.Cardimport androidx.compose.material3.CardDefaultsimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableIntStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.draw.alphaimport androidx.compose.ui.graphics.Brushimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.text.font.FontWeightimport androidx.compose.ui.unit.dpimport androidx.compose.ui.unit.spimport androidx.lifecycle.compose.collectAsStateWithLifecycleimport androidx.lifecycle.viewmodel.compose.viewModelimport com.example.jstz1.data.model.Deviceimport com.example.jstz1.data.model.DeviceState// 这几个是「Material3 没有对应语义槽」的设计变量,只能直接用:// 运行/停止按钮的底色与描边、普通按钮、状态三色、底栏import com.example.jstz1.ui.theme.Barimport com.example.jstz1.ui.theme.Btnimport com.example.jstz1.ui.theme.Dimimport com.example.jstz1.ui.theme.Errimport com.example.jstz1.ui.theme.Okimport com.example.jstz1.ui.theme.PBorder2import com.example.jstz1.ui.theme.RunBdimport com.example.jstz1.ui.theme.RunBgimport com.example.jstz1.ui.theme.StopBdimport com.example.jstz1.ui.theme.StopBgimport com.example.jstz1.ui.theme.TopAimport com.example.jstz1.ui.theme.TopBimport com.example.jstz1.ui.theme.Warn// 卡片圆角 / 面板圆角,对齐效果图的 --radius:4pxprivate val SHAPE = RoundedCornerShape(4.dp)// 群控按钮(对齐效果图 .b 的 min-height:44px)private val CTRL_BTN_HEIGHT = 36.dp/*** 主界面** 布局:主体(左 60% 设备列表 + 右 40% 群控)+ 底部统计栏* 顶栏(用户/角色/退出)放到第 10 节做权限体系时一起加——* 现在加了也是写死的假数据。*/@ComposablefunDeviceListScreen(modifier: Modifier = Modifier,vm: DeviceListViewModel = viewModel()) {val state by vm.uiState.collectAsStateWithLifecycle()/*** 底栏统计「现算」而不是「存字段」:* devices / devicesStates 一变,这三行自动跟着重算。* Kotlin 标准库的 count { } 函数,它接受一个谓词(条件表达式),返回满足条件的元素个数*/val onlineCnt = state.devices.count { state.devicesStates.containsKey(it.devId) }val enabledCnt = state.devices.count { it.enable }val alarmCnt = state.devicesStates.values.count { it.alarm.hasAlarm }Surface(modifier = modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {Column(Modifier.fillMaxSize()) {// ===== 顶栏 =====TopBar()// ===== 主体:左 60% / 右 40% =====Row(modifier = Modifier.fillMaxWidth().weight(1f) // 吃掉顶栏以下、底栏以上的全部高度.padding(12.dp),horizontalArrangement = Arrangement.spacedBy(12.dp)) {DeviceListPanel(devices = state.devices,states = state.devicesStates,onlineCount = onlineCnt,onToggle = { vm.toggleEnabled(it) },onReset = { vm.resetFactory(it) },modifier = Modifier.weight(0.6f).fillMaxHeight())GroupControlPanel(targetCount = onlineCnt,modifier = Modifier.weight(0.4f).fillMaxHeight())}// ===== 底栏 =====BottomBar(total = state.devices.size,online = onlineCnt,enabled = enabledCnt,alarm = alarmCnt)}}}// ============ 顶栏 ============/*** 顶栏(对应 .top)** 注意:admin / 管理员 / 退出登录目前是写死占位,* 第 10 节做登录与权限体系时换成真实登录用户。*/@Composableprivate funTopBar() {Column(Modifier.fillMaxWidth().background(Brush.verticalGradient(listOf(TopA, TopB)))) {Row(Modifier.fillMaxWidth().height(52.dp).padding(horizontal = 20.dp),verticalAlignment = Alignment.CenterVertically) {// logoBox(Modifier.size(34.dp).background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(6.dp)).border(1.dp, MaterialTheme.colorScheme.primary, RoundedCornerShape(6.dp)),contentAlignment = Alignment.Center) {Text("J",style = MaterialTheme.typography.titleMedium,fontWeight = FontWeight.Bold,color = MaterialTheme.colorScheme.primary)}Spacer(Modifier.weight(1f))Text("控制系统",style = MaterialTheme.typography.titleLarge,color = MaterialTheme.colorScheme.onBackground,letterSpacing = 4.sp)Spacer(Modifier.weight(1f))// 用户 + 角色Text("admin",style = MaterialTheme.typography.labelMedium,color = MaterialTheme.colorScheme.onSurfaceVariant)Spacer(Modifier.width(6.dp))Box(Modifier.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(9.dp)).padding(horizontal = 8.dp, vertical = 2.dp)) {Text("用户管理",style = MaterialTheme.typography.labelSmall,fontWeight = FontWeight.Bold,color = MaterialTheme.colorScheme.onPrimary)}Spacer(Modifier.width(14.dp))// 退出登录Text("退出登录",style = MaterialTheme.typography.labelMedium,color = MaterialTheme.colorScheme.onSurfaceVariant,modifier = Modifier.border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(6.dp)).padding(horizontal = 8.dp, vertical = 4.dp))}// 底部橙色装饰线Box(Modifier.fillMaxWidth().height(2.dp).background(Brush.horizontalGradient(0.0f to Color.Transparent,0.2f to MaterialTheme.colorScheme.primary,0.8f to MaterialTheme.colorScheme.primary,1.0f to Color.Transparent)))}}// ============ 左 60%:设备列表 ============@Composableprivate funDeviceListPanel(devices: List<Device>,states: Map<String, DeviceState>,onlineCount: Int,onToggle: (String) -> Unit,onReset: (String) -> Unit,modifier: Modifier = Modifier) {Column(modifier.background(MaterialTheme.colorScheme.surface, SHAPE).border(1.dp, MaterialTheme.colorScheme.outline, SHAPE).padding(14.dp)) {// ---- 标题行 ----Row(verticalAlignment = Alignment.CenterVertically) {Text("设备列表",style = MaterialTheme.typography.titleMedium,color = MaterialTheme.colorScheme.onBackground)Spacer(Modifier.weight(1f))Box(Modifier.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(6.dp)).padding(horizontal = 12.dp, vertical = 5.dp)) {Text("管理设备",style = MaterialTheme.typography.labelMedium,color = MaterialTheme.colorScheme.onPrimary // 橙底上的深棕字)}}Spacer(Modifier.height(10.dp))// ---- 卡片网格(2 列)----LazyVerticalGrid(columns = GridCells.Fixed(2),modifier = Modifier.fillMaxWidth().weight(1f),verticalArrangement = Arrangement.spacedBy(12.dp),horizontalArrangement = Arrangement.spacedBy(12.dp)) {items(items = devices, key = { it.devId }) { device ->DeviceCard(device = device,state = states[device.devId],onToggle = { onToggle(device.devId) },onReset = { onReset(device.devId) })}}}}// ============ 右 40%:控制 ============@Composableprivate funGroupControlPanel(targetCount: Int,modifier: Modifier = Modifier) {// 工作模式:界面 0/1,下发时 +1 转成协议值(1=酸解烃,2=溶解烃)var workMode by remember { mutableIntStateOf(0) }// 运行模式:直接存协议值 0~4,-1 表示还没选var runMode by remember { mutableIntStateOf(-1) }val canSend = runMode >= 0 && targetCount > 0Column(modifier.background(MaterialTheme.colorScheme.surface, SHAPE).border(1.dp, MaterialTheme.colorScheme.outline, SHAPE)// 窗口小于 1280×800 时内容放不下,允许滚动,避免被裁掉.verticalScroll(rememberScrollState()).padding(14.dp),verticalArrangement = Arrangement.spacedBy(10.dp)) {Block( ) {Text("工作模式:",style = MaterialTheme.typography.labelSmall,color = MaterialTheme.colorScheme.onSurfaceVariant)Spacer(Modifier.height(6.dp))SegmentedControl(items = listOf("酸解烃", "溶解烃"),selectedIndex = workMode,onSelect = { workMode = it })Spacer(Modifier.height(10.dp))Text("运行模式:",style = MaterialTheme.typography.labelSmall,color = MaterialTheme.colorScheme.onSurfaceVariant)Spacer(Modifier.height(6.dp))Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {SelectableButton(label = "运行",selected = runMode == 1, // 1 = 自动onBg = RunBg,onBorder = RunBd,onText = MaterialTheme.colorScheme.onBackground,modifier = Modifier.weight(1f)) { runMode = 1 }SelectableButton(label = "停止",selected = runMode == 0,onBg = StopBg,onBorder = StopBd,onText = MaterialTheme.colorScheme.onBackground,modifier = Modifier.weight(1f)) { runMode = 0 }}Spacer(Modifier.height(8.dp))Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {SelectableButton(label = "排液",selected = runMode == 2,onBg = MaterialTheme.colorScheme.primaryContainer,onBorder = MaterialTheme.colorScheme.primary,onText = MaterialTheme.colorScheme.primary,modifier = Modifier.weight(1f)) { runMode = 2 }SelectableButton(label = "抽真空",selected = runMode == 3,onBg = MaterialTheme.colorScheme.primaryContainer,onBorder = MaterialTheme.colorScheme.primary,onText = MaterialTheme.colorScheme.primary,modifier = Modifier.weight(1f)) { runMode = 3 }}Spacer(Modifier.height(8.dp))SelectableButton(label = "清洗",selected = runMode == 4,onBg = MaterialTheme.colorScheme.primaryContainer,onBorder = Warn,onText = MaterialTheme.colorScheme.primary,) { runMode = 4 }Spacer(Modifier.height(10.dp))if(runMode == 4){Text("清洗前请确认碱液瓶、酸液瓶、饱和盐水瓶已装入清水",style = MaterialTheme.typography.labelSmall,color = Warn)}Spacer(Modifier.height(4.dp))Text(if (targetCount > 0) "将向 total", MaterialTheme.colorScheme.onBackground)Sep()Stat("在线", "enabled", MaterialTheme.colorScheme.onBackground)Sep()Stat("报警", "label ",style = MaterialTheme.typography.labelSmall,color = MaterialTheme.colorScheme.onSurfaceVariant)Text(value,style = MaterialTheme.typography.labelSmall,color = valueColor,fontWeight = FontWeight.Bold)}}@Composableprivate funSep() {Spacer(Modifier.width(10.dp))Text("|", style = MaterialTheme.typography.labelSmall, color = PBorder2)Spacer(Modifier.width(10.dp))}// ==================== 通用小件 ====================/** 群控面板里的一个分区(对应 .block)*/@Composableprivate funBlock(title: String ="",modifier: Modifier = Modifier,content: @Composable () -> Unit) {Column(modifier.background(MaterialTheme.colorScheme.surfaceVariant, SHAPE).border(1.dp, MaterialTheme.colorScheme.outline, SHAPE).padding(12.dp)) {if(title!=""){Text(title,style = MaterialTheme.typography.labelMedium,color = MaterialTheme.colorScheme.onSurfaceVariant)Spacer(Modifier.height(9.dp))}content()}}/** 二选一分段控件(对应 .seg)*/@Composableprivate funSegmentedControl(items: List<String>,selectedIndex: Int,onSelect: (Int) -> Unit) {Row(Modifier.height(CTRL_BTN_HEIGHT).border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(7.dp))) {items.forEachIndexed { i, label ->val selected = i == selectedIndexBox(Modifier.weight(1f).fillMaxHeight().background(if (selected) MaterialTheme.colorScheme.primaryContainerelse Color.Transparent).clickable { onSelect(i) },contentAlignment = Alignment.Center) {Text(label,style = MaterialTheme.typography.bodyMedium,color = if (selected) MaterialTheme.colorScheme.onBackgroundelse MaterialTheme.colorScheme.onSurfaceVariant)}}}}/*** 可选中的模式按钮(对应效果图 .b / .b.run / .b.stop / .b.warn)** 选中 → 用该模式自己的配色(运行绿 / 停止红 / 橙色系)* 未选 → 灰底灰边,一眼看出"还没选"** 运行/停止的配色 Material3 没有对应语义槽(primary 是橙、tertiary 是绿),* 所以直接吃设计变量。*/@Composableprivate funSelectableButton(label: String,selected: Boolean,onBg: Color,onBorder: Color,onText: Color,offText: Color? = null,modifier: Modifier = Modifier,onClick: () -> Unit) {Box(modifier.fillMaxWidth().defaultMinSize(minHeight = CTRL_BTN_HEIGHT).background(if (selected) onBg else Btn, RoundedCornerShape(6.dp)).border(1.dp, if (selected) onBorder else PBorder2, RoundedCornerShape(6.dp)).clickable(onClick = onClick),contentAlignment = Alignment.Center) {Text(label,style = MaterialTheme.typography.bodyMedium,color = when {selected -> onTextoffText != null -> offTextelse -> MaterialTheme.colorScheme.onSurfaceVariant})}}/** 主操作按钮(确定下发):不可用时自动置灰并点不动 */@Composableprivate funPrimaryAction(label: String, enabled: Boolean, onClick: () -> Unit) {val container = if (enabled) MaterialTheme.colorScheme.primary else Btnval border = if (enabled) MaterialTheme.colorScheme.primary else PBorder2val content = if (enabled) MaterialTheme.colorScheme.onPrimaryelse MaterialTheme.colorScheme.onSurfaceVariantBox(Modifier.fillMaxWidth().height(CTRL_BTN_HEIGHT).background(container, RoundedCornerShape(7.dp)).border(1.dp, border, RoundedCornerShape(7.dp)).clickable(enabled = enabled, onClick = onClick),contentAlignment = Alignment.Center) {Text(label,style = MaterialTheme.typography.titleSmall,fontWeight = FontWeight.Bold,color = content)}}/** 快捷入口(对应效果图 .q)—— 第 9/10/11 节接真实页面 */@Composableprivate funQuickItem(title: String, subtitle: String, onClick: () -> Unit = {}) {Row(Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surfaceVariant, SHAPE).border(1.dp, MaterialTheme.colorScheme.outline, SHAPE).clickable(onClick = onClick).padding(horizontal = 12.dp, vertical = 8.dp),verticalAlignment = Alignment.CenterVertically) {// 图标位先用首字占位,后面换成矢量图标Box(Modifier.size(30.dp).background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(7.dp)).border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(7.dp)),contentAlignment = Alignment.Center) {Text(title.take(1),style = MaterialTheme.typography.labelMedium,color = MaterialTheme.colorScheme.primary)}Spacer(Modifier.width(10.dp))Column {Text(title,style = MaterialTheme.typography.bodyMedium,color = MaterialTheme.colorScheme.onSurface)Text(subtitle,style = MaterialTheme.typography.labelSmall,color = Dim)}}}/*** 紧凑开关(对应效果图 .switch:34×18 轨道 + 14dp 圆钮)** 为什么不用 Material3 的 Switch:* 它最小 52×32dp,两张卡片并排时把「恢复出厂」挤出卡片外。*/@Composableprivate funMiniSwitch(checked: Boolean, onToggle: () -> Unit) {Box(Modifier.size(width = 34.dp, height = 18.dp).background(if (checked) MaterialTheme.colorScheme.primaryContainerelse MaterialTheme.colorScheme.outline,RoundedCornerShape(9.dp)).border(1.dp,if (checked) MaterialTheme.colorScheme.primary else Color.Transparent,RoundedCornerShape(9.dp)).clickable(onClick = onToggle),contentAlignment = if (checked) Alignment.CenterEnd else Alignment.CenterStart) {Box(Modifier.padding(horizontal = 2.dp).size(14.dp).background(if (checked) MaterialTheme.colorScheme.primaryelse MaterialTheme.colorScheme.onSurfaceVariant,CircleShape))}}// ==================== 设备卡片 ====================/*** 一张设备卡片(对应 .card)** 在线判据:states 里有没有它 = 有没有收到过 0xB1 上报。* 第 6 节加 5 秒看门狗后,这个判据会换成「5 秒内有没有收到过」。*/@Composableprivate funDeviceCard(device: Device,state: DeviceState?,onToggle: () -> Unit,onReset: () -> Unit) {val online = state != nullval hasAlarm = state?.alarm?.hasAlarm == true// 状态 → 圆点颜色 + 文字,三态(离线 / 在线 / 在线·报警)val dotColor = when {!online -> MaterialTheme.colorScheme.onSurfaceVarianthasAlarm -> MaterialTheme.colorScheme.errorelse -> Ok}val statusText = when {!online -> "离线"hasAlarm -> "在线 · 报警"else -> "在线"}Card(modifier = Modifier.fillMaxWidth().alpha(if (device.enable) 1f else 0.6f) // 禁用 → 整卡半透明.border(1.dp,if (hasAlarm) MaterialTheme.colorScheme.errorelse MaterialTheme.colorScheme.outlineVariant,SHAPE),colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)) {Column(Modifier.fillMaxWidth().padding(12.dp)) {// ---- 顶:编号 + 状态 ----Row(verticalAlignment = Alignment.Top) {Text(device.devId,modifier = Modifier.weight(1f),style = MaterialTheme.typography.titleSmall,fontWeight = FontWeight.Bold,color = MaterialTheme.colorScheme.onSurface)Row(verticalAlignment = Alignment.CenterVertically) {Box(Modifier.size(7.dp).background(dotColor, CircleShape))Spacer(Modifier.width(5.dp))Text(statusText,style = MaterialTheme.typography.labelSmall,color = dotColor)}}Spacer(Modifier.height(8.dp))// ---- 数据三行(对齐效果图 .rows)----val vac = if (state != null) "%.1f kPa".format(state.vacuumKpa) else "—"val vol = if (state != null) "%.1f mL".format(state.volume) else "—"DataRow("流程", state?.process?.label ?: "—")DataRow("真空·气量", "$vac · $vol")DataRow("报警",if (hasAlarm) state?.alarm?.label ?: "—" else "—",if (hasAlarm) MaterialTheme.colorScheme.error else null)Spacer(Modifier.height(9.dp))// ---- 底:启用开关 + 恢复出厂 ----Row(verticalAlignment = Alignment.CenterVertically) {MiniSwitch(checked = device.enable, onToggle = onToggle)Spacer(Modifier.width(6.dp))Text(if (device.enable) "启用" else "已禁用",style = MaterialTheme.typography.labelSmall,color = MaterialTheme.colorScheme.onSurfaceVariant,maxLines = 1)Spacer(Modifier.weight(1f))Text("恢复出厂",modifier = Modifier.clickable(onClick = onReset).padding(horizontal = 2.dp, vertical = 4.dp),style = MaterialTheme.typography.labelSmall,color = MaterialTheme.colorScheme.primary,maxLines = 1,softWrap = false)}}}}@Composableprivate funDataRow(label: String, value: String, valueColor: Color? = null) {Row(verticalAlignment = Alignment.CenterVertically) {Text("$label ",style = MaterialTheme.typography.bodySmall,color = MaterialTheme.colorScheme.onSurfaceVariant)Text(value,style = MaterialTheme.typography.bodySmall,color = valueColor ?: MaterialTheme.colorScheme.onSurface)}}















































































