[HarmonyOS NEXT 实战案例十一] 智能家居控制面板网格布局(上)

简介: 本教程将详细讲解如何使用HarmonyOS NEXT中的GridRow和GridCol组件实现智能家居控制面板的网格布局。通过网格布局,我们可以创建一个直观、美观的智能家居控制界面,方便用户管理和控制家中的各种智能设备。

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

image.png

1. 概述

本教程将详细讲解如何使用HarmonyOS NEXT中的GridRow和GridCol组件实现智能家居控制面板的网格布局。通过网格布局,我们可以创建一个直观、美观的智能家居控制界面,方便用户管理和控制家中的各种智能设备。

本教程将涵盖以下内容:

  • 智能设备数据结构设计
  • 数据准备
  • 整体布局实现
  • GridRow和GridCol组件配置
  • 设备卡片实现
  • 布局效果分析

2. 数据结构设计

首先,我们需要定义智能设备的数据结构,包含设备的基本信息:

// 智能设备类型
export interface DeviceType {
   
  id: number;         // 设备ID
  name: string;       // 设备名称
  icon: Resource;     // 设备图标
  type: string;       // 设备类型
  room: string;       // 所在房间
  status: boolean;    // 设备状态(开/关)
  value?: number;     // 设备值(如亮度、温度等)
  color?: string;     // 设备颜色(如灯光颜色)
}

// 房间类型
export interface RoomType {
   
  id: number;         // 房间ID
  name: string;       // 房间名称
  icon: Resource;     // 房间图标
  deviceCount: number; // 设备数量
}

3. 数据准备

接下来,我们准备一些示例数据用于展示:

// 房间数据
private rooms: RoomType[] = [
  {
   
    id: 1,
    name: '客厅',
    icon: $r('app.media.ic_living_room'),
    deviceCount: 5
  },
  {
   
    id: 2,
    name: '卧室',
    icon: $r('app.media.ic_bedroom'),
    deviceCount: 3
  },
  {
   
    id: 3,
    name: '厨房',
    icon: $r('app.media.ic_kitchen'),
    deviceCount: 4
  },
  {
   
    id: 4,
    name: '浴室',
    icon: $r('app.media.ic_bathroom'),
    deviceCount: 2
  },
  {
   
    id: 5,
    name: '书房',
    icon: $r('app.media.ic_study'),
    deviceCount: 3
  }
];

// 智能设备数据
private devices: DeviceType[] = [
  {
   
    id: 1,
    name: '客厅灯',
    icon: $r('app.media.ic_light'),
    type: 'light',
    room: '客厅',
    status: true,
    value: 80,
    color: '#FFD700'
  },
  {
   
    id: 2,
    name: '空调',
    icon: $r('app.media.ic_ac'),
    type: 'ac',
    room: '客厅',
    status: true,
    value: 24
  },
  {
   
    id: 3,
    name: '电视',
    icon: $r('app.media.ic_tv'),
    type: 'tv',
    room: '客厅',
    status: false
  },
  {
   
    id: 4,
    name: '窗帘',
    icon: $r('app.media.ic_curtain'),
    type: 'curtain',
    room: '客厅',
    status: true,
    value: 100
  },
  {
   
    id: 5,
    name: '音响',
    icon: $r('app.media.ic_speaker'),
    type: 'speaker',
    room: '客厅',
    status: false
  },
  {
   
    id: 6,
    name: '卧室灯',
    icon: $r('app.media.ic_light'),
    type: 'light',
    room: '卧室',
    status: false,
    value: 0,
    color: '#FFFFFF'
  },
  {
   
    id: 7,
    name: '床头灯',
    icon: $r('app.media.ic_bedside_lamp'),
    type: 'light',
    room: '卧室',
    status: true,
    value: 30,
    color: '#FFA07A'
  },
  {
   
    id: 8,
    name: '卧室空调',
    icon: $r('app.media.ic_ac'),
    type: 'ac',
    room: '卧室',
    status: true,
    value: 26
  },
  {
   
    id: 9,
    name: '厨房灯',
    icon: $r('app.media.ic_light'),
    type: 'light',
    room: '厨房',
    status: false,
    value: 0,
    color: '#FFFFFF'
  },
  {
   
    id: 10,
    name: '冰箱',
    icon: $r('app.media.ic_fridge'),
    type: 'fridge',
    room: '厨房',
    status: true,
    value: 4
  },
  {
   
    id: 11,
    name: '烤箱',
    icon: $r('app.media.ic_oven'),
    type: 'oven',
    room: '厨房',
    status: false
  },
  {
   
    id: 12,
    name: '洗碗机',
    icon: $r('app.media.ic_dishwasher'),
    type: 'dishwasher',
    room: '厨房',
    status: false
  },
  {
   
    id: 13,
    name: '浴室灯',
    icon: $r('app.media.ic_light'),
    type: 'light',
    room: '浴室',
    status: false,
    value: 0,
    color: '#FFFFFF'
  },
  {
   
    id: 14,
    name: '热水器',
    icon: $r('app.media.ic_water_heater'),
    type: 'water_heater',
    room: '浴室',
    status: true,
    value: 45
  },
  {
   
    id: 15,
    name: '书房灯',
    icon: $r('app.media.ic_light'),
    type: 'light',
    room: '书房',
    status: true,
    value: 70,
    color: '#F5F5DC'
  },
  {
   
    id: 16,
    name: '台灯',
    icon: $r('app.media.ic_desk_lamp'),
    type: 'light',
    room: '书房',
    status: true,
    value: 50,
    color: '#F0E68C'
  },
  {
   
    id: 17,
    name: '书房空调',
    icon: $r('app.media.ic_ac'),
    type: 'ac',
    room: '书房',
    status: false,
    value: 0
  }
];

4. 布局实现

4.1 整体布局结构

我们将使用Column作为最外层容器,包含顶部状态栏、房间选择栏和设备网格列表:

build() {
   
  Column() {
   
    // 顶部状态栏
    this.StatusBar()

    // 房间选择栏
    this.RoomSelector()

    // 设备网格列表
    this.DeviceGrid()
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#F5F5F5')
}

4.2 顶部状态栏

@Builder
private StatusBar() {
   
  Row() {
   
    Column() {
   
      Text('智能家居')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')

      Text('欢迎回家,张先生')
        .fontSize(14)
        .fontColor('#666666')
        .margin({
    top: 4 })
    }
    .alignItems(HorizontalAlign.Start)

    Blank()

    Row() {
   
      // 添加设备按钮
      Button({
    type: ButtonType.Circle }) {
   
        Image($r('app.media.ic_add'))
          .width(24)
          .height(24)
      }
      .width(40)
      .height(40)
      .backgroundColor('#EEEEEE')
      .margin({
    right: 12 })

      // 设置按钮
      Button({
    type: ButtonType.Circle }) {
   
        Image($r('app.media.ic_settings'))
          .width(24)
          .height(24)
      }
      .width(40)
      .height(40)
      .backgroundColor('#EEEEEE')
    }
  }
  .width('100%')
  .padding({
    left: 16, right: 16, top: 16, bottom: 16 })
  .backgroundColor(Color.White)
}

4.3 房间选择栏

@State currentRoom: string = '全部';

@Builder
private RoomSelector() {
   
  Scroll(ScrollDirection.Horizontal) {
   
    Row() {
   
      // 全部选项
      Column() {
   
        Image($r('app.media.ic_all_rooms'))
          .width(40)
          .height(40)
          .margin({
    bottom: 8 })

        Text('全部')
          .fontSize(14)
          .fontColor(this.currentRoom === '全部' ? '#4285F4' : '#666666')
          .fontWeight(this.currentRoom === '全部' ? FontWeight.Bold : FontWeight.Normal)
      }
      .width(80)
      .height(80)
      .padding(8)
      .margin({
    right: 12 })
      .borderRadius(12)
      .backgroundColor(this.currentRoom === '全部' ? '#E8F0FE' : Color.White)
      .onClick(() => {
   
        this.currentRoom = '全部';
      })

      // 各个房间选项
      ForEach(this.rooms, (room: RoomType) => {
   
        Column() {
   
          Image(room.icon)
            .width(40)
            .height(40)
            .margin({
    bottom: 8 })

          Text(room.name)
            .fontSize(14)
            .fontColor(this.currentRoom === room.name ? '#4285F4' : '#666666')
            .fontWeight(this.currentRoom === room.name ? FontWeight.Bold : FontWeight.Normal)
        }
        .width(80)
        .height(80)
        .padding(8)
        .margin({
    right: 12 })
        .borderRadius(12)
        .backgroundColor(this.currentRoom === room.name ? '#E8F0FE' : Color.White)
        .onClick(() => {
   
          this.currentRoom = room.name;
        })
      })
    }
    .padding({
    left: 16, right: 16 })
  }
  .scrollBar(BarState.Off)
  .width('100%')
  .margin({
    top: 16, bottom: 16 })
}

4.4 设备网格列表

这是本教程的核心部分,我们使用GridRow和GridCol组件实现设备网格列表:

@Builder
private DeviceGrid() {
   
  Column() {
   
    // 标题栏
    Row() {
   
      Text(this.currentRoom === '全部' ? '所有设备' : `${
     this.currentRoom}设备`)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')

      Blank()

      Text(`${
     this.getFilteredDevices().length}个设备`)
        .fontSize(14)
        .fontColor('#666666')
    }
    .width('100%')
    .padding({
    left: 16, right: 16, bottom: 12 })

    // 使用GridRow和GridCol实现网格布局
    Scroll() {
   
      GridRow({
   
        columns: {
    xs: 2, sm: 2, md: 3, lg: 4 },
        gutter: {
    x: 16, y: 16 }
      }) {
   
        ForEach(this.getFilteredDevices(), (device: DeviceType) => {
   
          GridCol() {
   
            this.DeviceCard(device)
          }
        })
      }
      .width('100%')
      .padding(16)
    }
    .scrollBar(BarState.Off)
    .scrollable(ScrollDirection.Vertical)
    .width('100%')
    .layoutWeight(1)
  }
  .width('100%')
  .layoutWeight(1)
  .backgroundColor('#F5F5F5')
  .borderRadius({
    topLeft: 24, topRight: 24 })
}

4.5 设备卡片实现

@Builder
private DeviceCard(device: DeviceType) {
   
  Column() {
   
    // 设备图标和状态
    Row() {
   
      Image(device.icon)
        .width(24)
        .height(24)
        .fillColor(device.status ? '#4285F4' : '#999999')

      Blank()

      Toggle({
    type: ToggleType.Switch, isOn: device.status })
        .width(40)
        .height(24)
        .selectedColor('#4285F4')
        .onChange((isOn: boolean) => {
   
          this.toggleDevice(device.id, isOn);
        })
    }
    .width('100%')

    // 设备名称
    Text(device.name)
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
      .fontColor('#333333')
      .margin({
    top: 12 })

    // 设备状态文本
    Text(this.getDeviceStatusText(device))
      .fontSize(14)
      .fontColor('#666666')
      .margin({
    top: 4 })

    // 设备控制组件(根据设备类型显示不同控制)
    if (device.type === 'light' && device.status) {
   
      // 灯光亮度控制
      Column() {
   
        Row() {
   
          Image($r('app.media.ic_brightness'))
            .width(16)
            .height(16)
            .margin({
    right: 8 })

          Text('亮度')
            .fontSize(14)
            .fontColor('#666666')

          Blank()

          Text(`${
     device.value}%`)
            .fontSize(14)
            .fontColor('#4285F4')
        }
        .width('100%')
        .margin({
    top: 12 })

        Slider({
   
          value: device.value,
          min: 0,
          max: 100,
          step: 1,
          style: SliderStyle.OutSet
        })
          .width('100%')
          .margin({
    top: 8 })
          .selectedColor('#4285F4')
          .onChange((value: number) => {
   
            this.updateDeviceValue(device.id, value);
          })
      }
      .width('100%')
    } else if (device.type === 'ac' && device.status) {
   
      // 空调温度控制
      Row() {
   
        Button('-')
          .width(32)
          .height(32)
          .fontSize(16)
          .fontColor('#666666')
          .backgroundColor('#EEEEEE')
          .borderRadius(16)
          .onClick(() => {
   
            if (device.value > 16) {
   
              this.updateDeviceValue(device.id, device.value - 1);
            }
          })

        Text(`${
     device.value}°C`)
          .fontSize(16)
          .fontColor('#4285F4')
          .fontWeight(FontWeight.Bold)
          .margin({
    left: 12, right: 12 })

        Button('+')
          .width(32)
          .height(32)
          .fontSize(16)
          .fontColor('#666666')
          .backgroundColor('#EEEEEE')
          .borderRadius(16)
          .onClick(() => {
   
            if (device.value < 30) {
   
              this.updateDeviceValue(device.id, device.value + 1);
            }
          })
      }
      .width('100%')
      .margin({
    top: 12 })
      .justifyContent(FlexAlign.Center)
    }
  }
  .width('100%')
  .padding(16)
  .backgroundColor(Color.White)
  .borderRadius(16)
  .onClick(() => {
   
    this.showDeviceDetail(device);
  })
}

4.6 辅助方法

// 获取当前房间的设备列表
private getFilteredDevices(): DeviceType[] {
   
  if (this.currentRoom === '全部') {
   
    return this.devices;
  } else {
   
    return this.devices.filter(device => device.room === this.currentRoom);
  }
}

// 切换设备状态
private toggleDevice(deviceId: number, status: boolean): void {
   
  const index = this.devices.findIndex(device => device.id === deviceId);
  if (index !== -1) {
   
    this.devices[index].status = status;
  }
}

// 更新设备值
private updateDeviceValue(deviceId: number, value: number): void {
   
  const index = this.devices.findIndex(device => device.id === deviceId);
  if (index !== -1) {
   
    this.devices[index].value = value;
  }
}

// 获取设备状态文本
private getDeviceStatusText(device: DeviceType): string {
   
  if (!device.status) {
   
    return '已关闭';
  }

  switch (device.type) {
   
    case 'light':
      return `亮度 ${
     device.value}%`;
    case 'ac':
      return `温度 ${
     device.value}°C`;
    case 'curtain':
      return `开启度 ${
     device.value}%`;
    case 'fridge':
      return `温度 ${
     device.value}°C`;
    case 'water_heater':
      return `温度 ${
     device.value}°C`;
    default:
      return '已开启';
  }
}

// 显示设备详情
private showDeviceDetail(device: DeviceType): void {
   
  // 在实际应用中,这里会跳转到设备详情页面
  console.info(`显示设备详情:${
     device.name}`);
}

5. GridRow和GridCol配置详解

在本案例中,我们使用了GridRow和GridCol组件实现网格布局。下面详细解析其配置:

5.1 GridRow配置

GridRow({
   
  columns: {
    xs: 2, sm: 2, md: 3, lg: 4 },
  gutter: {
    x: 16, y: 16 }
})
  • columns:定义不同屏幕尺寸下的列数

    • xs: 2:极小屏幕(如小型手机)显示2列
    • sm: 2:小屏幕(如大型手机)显示2列
    • md: 3:中等屏幕(如平板)显示3列
    • lg: 4:大屏幕(如桌面)显示4列
  • gutter:定义网格间的间距

    • x: 16:水平间距为16像素
    • y: 16:垂直间距为16像素

5.2 GridCol配置

在本案例中,我们使用了默认的GridCol配置,没有指定span属性,这意味着每个设备卡片占据一个网格单元。

GridCol() {
   
  this.DeviceCard(device)
}

如果需要某些设备卡片占据更多的空间,可以通过span属性进行配置:

GridCol({
   
  span: {
    xs: 2, sm: 1, md: 1, lg: 1 }
}) {
   
  this.DeviceCard(device)
}

这样配置后,在极小屏幕(xs)上,该设备卡片会占据2列,而在其他屏幕尺寸上占据1列。

6. 布局效果分析

6.1 响应式布局

通过GridRow的columns配置,我们实现了响应式布局,使应用能够适应不同屏幕尺寸的设备:

屏幕尺寸 列数 效果
极小屏幕(xs) 2列 每行显示2个设备卡片
小屏幕(sm) 2列 每行显示2个设备卡片
中等屏幕(md) 3列 每行显示3个设备卡片
大屏幕(lg) 4列 每行显示4个设备卡片

6.2 网格间距

通过GridRow的gutter配置,我们设置了网格间的间距为16像素,使布局更加美观、清晰。

6.3 设备卡片设计

我们设计的设备卡片包含以下元素:

  1. 设备图标和开关:显示设备类型和当前状态,用户可以直接切换设备的开关状态
  2. 设备名称:清晰地显示设备的名称
  3. 设备状态文本:显示设备的当前状态,如亮度、温度等
  4. 设备控制组件:根据设备类型显示不同的控制组件,如灯光的亮度滑块、空调的温度控制按钮等

这种设计使用户能够快速了解设备状态,并进行简单的控制操作。

7. GridRow和GridCol组件详解

7.1 GridRow组件

GridRow是HarmonyOS NEXT提供的网格行容器组件,用于创建网格布局。它具有以下主要属性:

属性 类型 描述
columns number \ { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 当前行的总列数
gutter number \ { x?: number, y?: number } 栅格间隔
breakpoints { value: number, reference: BreakpointsReference }[] 自定义断点值

7.2 GridCol组件

GridCol是HarmonyOS NEXT提供的网格列容器组件,用于在GridRow中创建网格列。它具有以下主要属性:

属性 类型 描述
span number \ { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 列宽度
offset number \ { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 列偏移量
order number \ { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 列顺序

8. 完整代码

@Entry
@Component
struct SmartHomeGrid {
   
  // 智能设备类型
  interface DeviceType {
   
    id: number;         // 设备ID
    name: string;       // 设备名称
    icon: Resource;     // 设备图标
    type: string;       // 设备类型
    room: string;       // 所在房间
    status: boolean;    // 设备状态(开/关)
    value?: number;     // 设备值(如亮度、温度等)
    color?: string;     // 设备颜色(如灯光颜色)
  }

  // 房间类型
  interface RoomType {
   
    id: number;         // 房间ID
    name: string;       // 房间名称
    icon: Resource;     // 房间图标
    deviceCount: number; // 设备数量
  }

  // 房间数据
  private rooms: RoomType[] = [
    {
   
      id: 1,
      name: '客厅',
      icon: $r('app.media.ic_living_room'),
      deviceCount: 5
    },
    {
   
      id: 2,
      name: '卧室',
      icon: $r('app.media.ic_bedroom'),
      deviceCount: 3
    },
    {
   
      id: 3,
      name: '厨房',
      icon: $r('app.media.ic_kitchen'),
      deviceCount: 4
    },
    {
   
      id: 4,
      name: '浴室',
      icon: $r('app.media.ic_bathroom'),
      deviceCount: 2
    },
    {
   
      id: 5,
      name: '书房',
      icon: $r('app.media.ic_study'),
      deviceCount: 3
    }
  ];

  // 智能设备数据
  @State devices: DeviceType[] = [
    {
   
      id: 1,
      name: '客厅灯',
      icon: $r('app.media.ic_light'),
      type: 'light',
      room: '客厅',
      status: true,
      value: 80,
      color: '#FFD700'
    },
    {
   
      id: 2,
      name: '空调',
      icon: $r('app.media.ic_ac'),
      type: 'ac',
      room: '客厅',
      status: true,
      value: 24
    },
    {
   
      id: 3,
      name: '电视',
      icon: $r('app.media.ic_tv'),
      type: 'tv',
      room: '客厅',
      status: false
    },
    {
   
      id: 4,
      name: '窗帘',
      icon: $r('app.media.ic_curtain'),
      type: 'curtain',
      room: '客厅',
      status: true,
      value: 100
    },
    {
   
      id: 5,
      name: '音响',
      icon: $r('app.media.ic_speaker'),
      type: 'speaker',
      room: '客厅',
      status: false
    },
    {
   
      id: 6,
      name: '卧室灯',
      icon: $r('app.media.ic_light'),
      type: 'light',
      room: '卧室',
      status: false,
      value: 0,
      color: '#FFFFFF'
    },
    {
   
      id: 7,
      name: '床头灯',
      icon: $r('app.media.ic_bedside_lamp'),
      type: 'light',
      room: '卧室',
      status: true,
      value: 30,
      color: '#FFA07A'
    },
    {
   
      id: 8,
      name: '卧室空调',
      icon: $r('app.media.ic_ac'),
      type: 'ac',
      room: '卧室',
      status: true,
      value: 26
    },
    {
   
      id: 9,
      name: '厨房灯',
      icon: $r('app.media.ic_light'),
      type: 'light',
      room: '厨房',
      status: false,
      value: 0,
      color: '#FFFFFF'
    },
    {
   
      id: 10,
      name: '冰箱',
      icon: $r('app.media.ic_fridge'),
      type: 'fridge',
      room: '厨房',
      status: true,
      value: 4
    },
    {
   
      id: 11,
      name: '烤箱',
      icon: $r('app.media.ic_oven'),
      type: 'oven',
      room: '厨房',
      status: false
    },
    {
   
      id: 12,
      name: '洗碗机',
      icon: $r('app.media.ic_dishwasher'),
      type: 'dishwasher',
      room: '厨房',
      status: false
    },
    {
   
      id: 13,
      name: '浴室灯',
      icon: $r('app.media.ic_light'),
      type: 'light',
      room: '浴室',
      status: false,
      value: 0,
      color: '#FFFFFF'
    },
    {
   
      id: 14,
      name: '热水器',
      icon: $r('app.media.ic_water_heater'),
      type: 'water_heater',
      room: '浴室',
      status: true,
      value: 45
    },
    {
   
      id: 15,
      name: '书房灯',
      icon: $r('app.media.ic_light'),
      type: 'light',
      room: '书房',
      status: true,
      value: 70,
      color: '#F5F5DC'
    },
    {
   
      id: 16,
      name: '台灯',
      icon: $r('app.media.ic_desk_lamp'),
      type: 'light',
      room: '书房',
      status: true,
      value: 50,
      color: '#F0E68C'
    },
    {
   
      id: 17,
      name: '书房空调',
      icon: $r('app.media.ic_ac'),
      type: 'ac',
      room: '书房',
      status: false,
      value: 0
    }
  ];

  @State currentRoom: string = '全部';

  build() {
   
    Column() {
   
      // 顶部状态栏
      this.StatusBar()

      // 房间选择栏
      this.RoomSelector()

      // 设备网格列表
      this.DeviceGrid()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  @Builder
  private StatusBar() {
   
    Row() {
   
      Column() {
   
        Text('智能家居')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')

        Text('欢迎回家,张先生')
          .fontSize(14)
          .fontColor('#666666')
          .margin({
    top: 4 })
      }
      .alignItems(HorizontalAlign.Start)

      Blank()

      Row() {
   
        // 添加设备按钮
        Button({
    type: ButtonType.Circle }) {
   
          Image($r('app.media.ic_add'))
            .width(24)
            .height(24)
        }
        .width(40)
        .height(40)
        .backgroundColor('#EEEEEE')
        .margin({
    right: 12 })

        // 设置按钮
        Button({
    type: ButtonType.Circle }) {
   
          Image($r('app.media.ic_settings'))
            .width(24)
            .height(24)
        }
        .width(40)
        .height(40)
        .backgroundColor('#EEEEEE')
      }
    }
    .width('100%')
    .padding({
    left: 16, right: 16, top: 16, bottom: 16 })
    .backgroundColor(Color.White)
  }

  @Builder
  private RoomSelector() {
   
    Scroll(ScrollDirection.Horizontal) {
   
      Row() {
   
        // 全部选项
        Column() {
   
          Image($r('app.media.ic_all_rooms'))
            .width(40)
            .height(40)
            .margin({
    bottom: 8 })

          Text('全部')
            .fontSize(14)
            .fontColor(this.currentRoom === '全部' ? '#4285F4' : '#666666')
            .fontWeight(this.currentRoom === '全部' ? FontWeight.Bold : FontWeight.Normal)
        }
        .width(80)
        .height(80)
        .padding(8)
        .margin({
    right: 12 })
        .borderRadius(12)
        .backgroundColor(this.currentRoom === '全部' ? '#E8F0FE' : Color.White)
        .onClick(() => {
   
          this.currentRoom = '全部';
        })

        // 各个房间选项
        ForEach(this.rooms, (room: RoomType) => {
   
          Column() {
   
            Image(room.icon)
              .width(40)
              .height(40)
              .margin({
    bottom: 8 })

            Text(room.name)
              .fontSize(14)
              .fontColor(this.currentRoom === room.name ? '#4285F4' : '#666666')
              .fontWeight(this.currentRoom === room.name ? FontWeight.Bold : FontWeight.Normal)
          }
          .width(80)
          .height(80)
          .padding(8)
          .margin({
    right: 12 })
          .borderRadius(12)
          .backgroundColor(this.currentRoom === room.name ? '#E8F0FE' : Color.White)
          .onClick(() => {
   
            this.currentRoom = room.name;
          })
        })
      }
      .padding({
    left: 16, right: 16 })
    }
    .scrollBar(BarState.Off)
    .width('100%')
    .margin({
    top: 16, bottom: 16 })
  }

  @Builder
  private DeviceGrid() {
   
    Column() {
   
      // 标题栏
      Row() {
   
        Text(this.currentRoom === '全部' ? '所有设备' : `${
     this.currentRoom}设备`)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')

        Blank()

        Text(`${
     this.getFilteredDevices().length}个设备`)
          .fontSize(14)
          .fontColor('#666666')
      }
      .width('100%')
      .padding({
    left: 16, right: 16, bottom: 12 })

      // 使用GridRow和GridCol实现网格布局
      Scroll() {
   
        GridRow({
   
          columns: {
    xs: 2, sm: 2, md: 3, lg: 4 },
          gutter: {
    x: 16, y: 16 }
        }) {
   
          ForEach(this.getFilteredDevices(), (device: DeviceType) => {
   
            GridCol() {
   
              this.DeviceCard(device)
            }
          })
        }
        .width('100%')
        .padding(16)
      }
      .scrollBar(BarState.Off)
      .scrollable(ScrollDirection.Vertical)
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .layoutWeight(1)
    .backgroundColor('#F5F5F5')
    .borderRadius({
    topLeft: 24, topRight: 24 })
  }

  @Builder
  private DeviceCard(device: DeviceType) {
   
    Column() {
   
      // 设备图标和状态
      Row() {
   
        Image(device.icon)
          .width(24)
          .height(24)
          .fillColor(device.status ? '#4285F4' : '#999999')

        Blank()

        Toggle({
    type: ToggleType.Switch, isOn: device.status })
          .width(40)
          .height(24)
          .selectedColor('#4285F4')
          .onChange((isOn: boolean) => {
   
            this.toggleDevice(device.id, isOn);
          })
      }
      .width('100%')

      // 设备名称
      Text(device.name)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#333333')
        .margin({
    top: 12 })

      // 设备状态文本
      Text(this.getDeviceStatusText(device))
        .fontSize(14)
        .fontColor('#666666')
        .margin({
    top: 4 })

      // 设备控制组件(根据设备类型显示不同控制)
      if (device.type === 'light' && device.status) {
   
        // 灯光亮度控制
        Column() {
   
          Row() {
   
            Image($r('app.media.ic_brightness'))
              .width(16)
              .height(16)
              .margin({
    right: 8 })

            Text('亮度')
              .fontSize(14)
              .fontColor('#666666')

            Blank()

            Text(`${
     device.value}%`)
              .fontSize(14)
              .fontColor('#4285F4')
          }
          .width('100%')
          .margin({
    top: 12 })

          Slider({
   
            value: device.value,
            min: 0,
            max: 100,
            step: 1,
            style: SliderStyle.OutSet
          })
            .width('100%')
            .margin({
    top: 8 })
            .selectedColor('#4285F4')
            .onChange((value: number) => {
   
              this.updateDeviceValue(device.id, value);
            })
        }
        .width('100%')
      } else if (device.type === 'ac' && device.status) {
   
        // 空调温度控制
        Row() {
   
          Button('-')
            .width(32)
            .height(32)
            .fontSize(16)
            .fontColor('#666666')
            .backgroundColor('#EEEEEE')
            .borderRadius(16)
            .onClick(() => {
   
              if (device.value > 16) {
   
                this.updateDeviceValue(device.id, device.value - 1);
              }
            })

          Text(`${
     device.value}°C`)
            .fontSize(16)
            .fontColor('#4285F4')
            .fontWeight(FontWeight.Bold)
            .margin({
    left: 12, right: 12 })

          Button('+')
            .width(32)
            .height(32)
            .fontSize(16)
            .fontColor('#666666')
            .backgroundColor('#EEEEEE')
            .borderRadius(16)
            .onClick(() => {
   
              if (device.value < 30) {
   
                this.updateDeviceValue(device.id, device.value + 1);
              }
            })
        }
        .width('100%')
        .margin({
    top: 12 })
        .justifyContent(FlexAlign.Center)
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(16)
    .onClick(() => {
   
      this.showDeviceDetail(device);
    })
  }

  // 获取当前房间的设备列表
  private getFilteredDevices(): DeviceType[] {
   
    if (this.currentRoom === '全部') {
   
      return this.devices;
    } else {
   
      return this.devices.filter(device => device.room === this.currentRoom);
    }
  }

  // 切换设备状态
  private toggleDevice(deviceId: number, status: boolean): void {
   
    const index = this.devices.findIndex(device => device.id === deviceId);
    if (index !== -1) {
   
      this.devices[index].status = status;
    }
  }

  // 更新设备值
  private updateDeviceValue(deviceId: number, value: number): void {
   
    const index = this.devices.findIndex(device => device.id === deviceId);
    if (index !== -1) {
   
      this.devices[index].value = value;
    }
  }

  // 获取设备状态文本
  private getDeviceStatusText(device: DeviceType): string {
   
    if (!device.status) {
   
      return '已关闭';
    }

    switch (device.type) {
   
      case 'light':
        return `亮度 ${
     device.value}%`;
      case 'ac':
        return `温度 ${
     device.value}°C`;
      case 'curtain':
        return `开启度 ${
     device.value}%`;
      case 'fridge':
        return `温度 ${
     device.value}°C`;
      case 'water_heater':
        return `温度 ${
     device.value}°C`;
      default:
        return '已开启';
    }
  }

  // 显示设备详情
  private showDeviceDetail(device: DeviceType): void {
   
    // 在实际应用中,这里会跳转到设备详情页面
    console.info(`显示设备详情:${
     device.name}`);
  }
}

9. 总结

本教程详细讲解了如何使用HarmonyOS NEXT中的GridRow和GridCol组件实现智能家居控制面板的网格布局。通过合理的数据结构设计、精心的UI设计和灵活的GridRow配置,我们实现了一个美观、响应式的智能家居控制面板。

主要内容包括:

  • 智能设备数据结构设计和数据准备
  • 整体布局实现,包括顶部状态栏、房间选择栏和设备网格列表
  • GridRow和GridCol组件的配置和使用
  • 设备卡片的设计和实现,包括不同类型设备的控制组件
  • 布局效果分析
相关文章
|
2月前
|
移动开发 前端开发 JavaScript
鸿蒙NEXT时代你所不知道的全平台跨端框架:CMP、Kuikly、Lynx、uni-app x等
本篇基于当前各大活跃的跨端框架的现状,对比当前它们的情况和未来的可能,帮助你在选择框架时更好理解它们的特点和差异。
229 0
|
3月前
|
容器
鸿蒙Next仓颉语言开发实战教程:店铺详情页
本文介绍了使用仓颉语言开发HarmonyOS应用中的店铺详情页实现方法,重点讲解了List容器的使用技巧,包括如何布局、设置圆角及处理列表项分组等内容,并附有完整代码示例。
|
19天前
|
移动开发 网络协议 小程序
鸿蒙NEXT即时通讯/IM系统RinbowTalk v2.4版发布,基于MobileIMSDK框架、ArkTS编写
RainbowTalk是一套基于开源即时通讯讯IM框架 MobileIMSDK 的产品级鸿蒙NEXT端IM系统。纯ArkTS编写、全新开发,没有套壳、也没走捷径,每一行代码都够“纯血”。与姊妹产品RainbowChat和RainbowChat-Web 技术同源,历经考验。
50 1
|
2月前
|
缓存 移动开发 网络协议
纯血鸿蒙NEXT即时通讯/IM系统:RinbowTalk正式发布,全源码、纯ArkTS编写
RainbowTalk是一套基于MobileIMSDK的产品级鸿蒙NEXT端IM系统,目前已正式发布。纯ArkTS、从零编写,无套壳、没走捷径,每一行代码都够“纯”(详见:《RainbowTalk详细介绍》)。 MobileIMSDK是一整套开源IM即时通讯框架,历经10年,超轻量级、高度提炼,一套API优雅支持 UDP 、TCP 、WebSocket 三种协议,支持 iOS、Android、H5、标准Java、小程序、Uniapp、鸿蒙NEXT,服务端基于Netty编写。
147 1
|
3月前
|
开发者 容器
鸿蒙Next仓颉语言开发实战教程:设置页面
本教程介绍了仓颉语言商城应用设置页面的开发,重点讲解了List容器的使用。页面分为三组列表内容,第一组直接使用ListItem实现,后两组通过ListItemGroup及自定义组件setrow完成布局。教程还演示了如何通过自定义组件提升代码复用性,并介绍了分割线divider的设置方法,帮助开发者高效构建美观的设置界面。
鸿蒙Next仓颉语言开发实战教程:订单列表
本文介绍了使用仓颉语言开发HarmonyOS商城应用的订单列表页实现方法,包含导航栏、订单类型切换和订单列表展示三部分。通过代码示例讲解了布局技巧与组件使用,适合初学者学习参考。
鸿蒙Next仓颉语言开发实战教程:下拉刷新和上拉加载更多
本文介绍了在仓颉开发语言中实现下拉刷新和上拉加载更多的方法。通过Refresh组件实现下拉刷新,结合Timer模拟网络加载;并通过监听列表滚动事件,在列表底部添加加载动画实现上拉加载更多功能。代码示例清晰实用,适用于商城等含列表交互的移动应用开发。
|
编译器
鸿蒙NEXT-鸿蒙三层架构搭建,嵌入HMRouter,实现便捷跳转,新手攻略。(2/3)
本文介绍在三层架构中实现模块依赖的步骤。首先在产品定制层(features)的oh-package.json5文件中导入共享包依赖,如"basic":"file:../../commons/basic"。然后在产品层(products)的配置文件中同时导入公共能力层和产品定制层的依赖,示例展示了如何添加"basic"和"my"两个依赖项。通过这些配置,三层架构的各模块之间建立了完整的依赖关系。
123 0
鸿蒙NEXT-鸿蒙三层架构搭建,嵌入HMRouter,实现便捷跳转,新手攻略。(2/3)
|
3月前
|
存储 开发者
鸿蒙Next仓颉开发语言中的数据类型总结分享
仓颉语言数据类型包括多种数字类型(Int、Float)、字符串(String)、数组(Array、ArrayList、ObservedArrayList)及HashMap。数字类型区分长度和精度,数组支持固定与动态操作,HashMap用于存储键值对。适合开发者快速掌握仓颉基础数据结构。#仓颉 #HarmonyOS
详解鸿蒙Next仓颉开发语言中的全屏模式
仓颉开发语言实现全屏模式教程:默认非全屏,需手动设置沉浸模式以占满屏幕。通过`setWindowLayoutFullScreen`开启全屏,并利用`getWindowAvoidArea`获取摄像头与导航条区域高度,结合AppStorage保存尺寸,适配界面布局,避免内容被遮挡。附屏幕尺寸获取方法及单位说明。