HarmonyOS智能家居——房间详情页与设备控制交互

发布时间:2026/7/14 4:54:47
HarmonyOS智能家居——房间详情页与设备控制交互 房间详情页是从首页的房间卡片或底部导航栏点进去的——用户选择某个房间后这个页面列出该房间的所有设备并提供开关控制和参数调节。从代码看这个页面有两个核心设计一是按设备类型条件渲染不同的控制组件灯显示亮度滑块空调显示温度滑块二是每次操作后重新查询房间设备列表来触发 UI 刷新。完整效果页面结构从上到下三个区域导航栏返回按钮 房间名称 设备数量设备列表纵向滚动每个设备一张卡片底部留白为可能的底部操作预留空间每张设备卡片内部有两种形态——普通设备只显示名称和开关可调节设备灯、空调在开启状态下多一个 Slider 滑块。参数接收与数据查询Stateroom:string客厅Statedevices:Device[][]aboutToAppear():void{constprouter.getParams()asRecordstring,Objectif(pp[room]){this.roomp[room]asstring}this.devicesgetDevicesByRoom(this.room)}从路由参数里取出房间名称默认值是客厅。然后调用getDevicesByRoom(this.room)获取该房间的所有设备。getDevicesByRoom的实现是遍历DEVICES数组把room属性匹配的设备收集到新数组里返回。这个查询是浅拷贝——返回的数组是新的但里面的设备对象仍然是原始DEVICES数组里的引用。修改返回数组里某个设备的属性会直接影响原始数据。设备卡片的信息展示ForEach(this.devices,(d:Device){Column(){Row(){Text(d.icon).fontSize(28).width(50).height(50).borderRadius(14).backgroundColor(#F5F5FA).textAlign(TextAlign.Center)Column(){Text(d.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor(T1)Text(d.typethermostat?d.value°C:d.typelight?亮度 d.value%:d.on?运行中:已关闭).fontSize(12).fontColor(T3)}.alignItems(HorizontalAlign.Start).margin({left:12}).layoutWeight(1)Text(d.on?:⚪).fontSize(18).onClick((){d.on!d.on;this.devicesgetDevicesByRoom(this.room)})}}})每个设备卡片分左右两部分左侧是 50×50 的 emoji 图标容器浅灰底色 14px 圆角右侧是设备名称和状态文字。状态文字根据设备类型有三种显示方式thermostat温控器显示当前温度如24°Clight灯显示当前亮度如亮度 80%其他设备显示开关状态文字运行中或已关闭这个三元表达式嵌套了三层读起来不太直观但它把所有设备类型的状态展示逻辑压缩成了一行代码。如果用if/else展开代码量会翻倍。开关切换的刷新逻辑.onClick((){d.on!d.on;this.devicesgetDevicesByRoom(this.room)})切换设备状态后调用getDevicesByRoom(this.room)重新获取房间设备列表赋值给State devices触发 UI 刷新。这里有一个关键细节d.on !d.on修改的是DEVICES数组里原始设备对象的属性因为getDevicesByRoom返回的是引用而this.devices getDevicesByRoom(this.room)重新查询后返回的是一个新数组。State检测到devices引用变了触发重新渲染所有设备卡片的状态文字和开关圆点同时更新。如果只写d.on !d.on而不重新查询数组UI 不会刷新——因为State没有检测到devices数组引用的变化。条件渲染按设备类型显示滑块if(d.ond.typelight){Slider({value:d.value,min:1,max:100,step:1,style:SliderStyle.OutSet}).blockColor(A).trackColor(#F0ECF8).selectedColor(#A78BFA).trackThickness(4).blockSize({width:20,height:20}).onChange((v:number){d.valueMath.round(v)this.devicesgetDevicesByRoom(this.room)}).width(100%)}if(d.ond.typethermostat){Slider({value:d.value,min:16,max:30,step:1,style:SliderStyle.OutSet}).blockColor(#FF6B6B).trackColor(#FFF0F0).selectedColor(#FF9F43).trackThickness(4).blockSize({width:20,height:20}).onChange((v:number){d.valueMath.round(v)this.devicesgetDevicesByRoom(this.room)}).width(100%)}两个if条件渲染控制了滑块的显隐——只有设备开启d.on且类型匹配时才显示对应的滑块。设备关闭后滑块自动消失卡片高度缩小。灯的亮度滑块范围1-100百分比滑块颜色主色紫色系blockColor: #6C5CE7selectedColor: #A78BFA轨道颜色极浅紫#F0ECF8轨道粗细4vp空调的温度滑块范围16-30摄氏度滑块颜色红色系blockColor: #FF6B6BselectedColor: #FF9F43轨道颜色极浅红#FFF0F0轨道粗细4vp两种滑块用不同的颜色系区分功能——紫色代表灯光冷色红色代表温度暖色。这种颜色编码让用户不用看标签就能区分这是调亮度的和这是调温度的。SliderStyle.OutSet 的选择SliderStyle.OutSet让滑块的圆形把手block突出显示在轨道上方而不是嵌入轨道内部。这种样式在需要精确调节的场景里更常见——把手更大20×20vp用户更容易拖动。如果用SliderStyle.InSet把手会嵌入轨道内部视觉上更紧凑但操作精度会降低。对于亮度和温度这种需要精确控制的参数OutSet是更合适的选择。onChange 的刷新逻辑.onChange((v:number){d.valueMath.round(v)this.devicesgetDevicesByRoom(this.room)})滑块拖动时Math.round(v)把浮点数取整比如 78.6 → 79赋值给设备的value属性然后重新查询房间设备列表触发刷新。Math.round是必要的——Slider 的step: 1虽然指定了步长为 1但在某些设备上拖动时仍可能产生浮点数比如 79.0000001。取整后保证value始终是整数显示在 UI 上不会出现亮度 78.99999%这种尴尬的情况。卡片高度的动态适配Row(){// ... 设备图标、名称、开关}.width(100%).margin({bottom:d.on(d.typelight||d.typethermostat)?8:0})这行代码给设备卡片的底部加了一个条件 margin——当设备是灯或空调且处于开启状态时底部留 8px 间距为下方的 Slider 滑块预留空间。设备关闭或不是灯/空调类型时底部间距为 0。这个间距的变化让卡片高度能够自适应内容——有滑块时卡片更高多出滑块的高度 8px 间距没有滑块时卡片更紧凑。用户在滚动列表时能明显感觉到这个设备可以调参数和这个设备只能开关的区别。数据流的闭环房间页面的数据流形成了一个完整的闭环首页 (Index) │ │ params: room (房间名称) ↓ RoomPage │ ├→ getDevicesByRoom(room) 查询房间设备 │ ↓ │ 设备列表渲染 │ ↓ ├→ toggleDevice(id) 切换开关 │ ↓ ├→ setDeviceValue(id, val) 调节参数Slider onChange │ ↓ └→ getDevicesByRoom(room) 重新查询 → UI 刷新每次操作后都重新查询房间设备列表而不是直接修改State devices数组里的元素。这种重新查询的刷新方式虽然比直接修改多了一次数组遍历但保证了数据的一致性——因为getDevicesByRoom读取的是DEVICES源数组返回的永远是最新的数据。如果用直接修改的方式比如this.devices[idx].on !this.devices[idx].on需要手动复制数组才能触发刷新。重新查询把数据修改和UI 刷新分成了两步——第一步修改源数据toggleDevice/setDeviceValue第二步重新查询获取最新数据getDevicesByRoom。职责分离更清晰。三种设备类型的控制对比设备类型状态显示开关控制参数控制Slider 范围Slider 颜色light亮度 X%/⚪ 切换亮度滑块1-100紫色系thermostatX°C/⚪ 切换温度滑块16-30红色系appliance运行中/已关闭/⚪ 切换无--curtain运行中/已关闭/⚪ 切换无--灯和温控器有参数控制其他设备只有开关控制。这个差异通过if条件渲染实现——不需要为每种设备类型写单独的组件只需要在同一个卡片里根据类型决定是否显示滑块。这种同一结构 条件分支的模式比为每种类型写独立组件更轻量。在设备类型不多4 种的情况下条件分支的可读性是可以接受的。如果以后设备类型增加到十几种可能需要考虑用策略模式或组件工厂来替代条件分支。回到首页的数据同步用户在 RoomPage 关闭了一个设备后点击返回按钮回到首页Row(){SymbolGlyph($r(sys.symbol.chevron_left))...}.onClick((){router.back()})router.back()返回首页时首页的onPageShow被触发onPageShow():void{this.devCountgetActiveCount()}getActiveCount()重新遍历DEVICES数组统计在线设备数——因为 RoomPage 里的toggleDevice已经修改了源数组所以这里读到的是最新状态。首页的状态卡片数字和快捷控制的圆点颜色自动更新。这个数据同步不需要额外的事件机制或回调——因为所有页面共享同一份DEVICES数据修改和读取操作天然一致。onPageShow是触发刷新的信号getActiveCount是获取最新数据的方法两者配合实现了跨页面的数据同步。