DEV Community

SameX
SameX

Posted on

Development Guide for Smart Home Control Panel in HarmonyOS Next

This article aims to deeply explore the technical details of Huawei's HarmonyOS Next system and summarize them based on practical development practices. It mainly serves as a carrier for technical sharing and communication. Errors and omissions are inevitable, and colleagues are welcome to put forward valuable opinions and questions so that we can make progress together. This article is original content, and any form of reprinting must indicate the source and the original author.

With the vigorous development of smart homes, it is particularly important to develop a smart home control panel that is compatible with a variety of devices and has a user-friendly interaction. With the powerful capabilities of HarmonyOS Next, we can create a control panel with rich functions, a beautiful interface, and convenient operation. Now, let's take a step-by-step in-depth look at its development process.

UI Design Ideas for Smart Home Applications

Decomposition of Core Functions: Device Control, Scene Management, Data Display

The core functions of a smart home control panel mainly include device control, scene management, and data display. Device control is a basic function, allowing users to perform operations such as turning on/off and adjusting parameters of various smart devices at home, such as sockets, lights, air conditioners, etc., through the control panel. Scene management enables users to preset different scene modes according to their living habits, such as the "Homecoming Mode" and the "Sleep Mode", and achieve the coordinated control of multiple devices with one click. The data display function is used to present information such as the operating status of devices and environmental data (such as temperature and humidity), facilitating users to keep abreast of the situation of smart devices at home in real time.

UI Differences between Large Screens and Small Screens: How to Present Different Layouts on Mobile Phones, Tablets, and Smart Screens

The screen sizes and interaction methods of different devices vary greatly, so there should be differences in the UI layout.

  • Mobile Phones: Mobile phone screens are relatively small, and the operation mainly depends on touch. The UI layout should be simple and clear, highlighting the core functions. Usually, the bottom Tab switching method is adopted to facilitate one-handed operation by users. For example, place functional modules such as "Device Control", "Scene Management", and "Data Display" in different Tabs, and users can quickly switch interfaces by clicking on the Tabs.
  • Tablets: Tablets have larger screens, with a certain degree of portability and operation space. A dual-column mode can be adopted, with the device list displayed on one side and the details or operation interface of the selected device displayed on the other side. In this way, more information can be displayed, and users can quickly select and operate devices.
  • Smart Screens: Smart screens are even larger and are used for centralized control in scenarios such as the living room. A three-column mode is more appropriate. The left side is a sidebar for navigation and switching between different functional modules; the middle displays the device list in the form of larger cards, which is convenient for operation from a distance; the right side displays the detailed information and operation panel of the currently selected device, making full use of the advantages of the large screen to display more details and operation options.

Implementing Dynamic Layouts Using SideBarContainer + Navigation + Grid

Large-screen Devices: Three-column Mode (Sidebar + Device List + Device Details)

On large-screen devices, we use the SideBarContainer to create the sidebar, the Navigation to manage page switching and content display, and the Grid to layout the device list. The sample code is as follows:

@Entry
@Component
struct BigScreenPanel {
    @State selectedDevice: string = ''
    @State deviceList: string[] = ['Socket', 'Light', 'Air Conditioner']
    @State deviceDetails: { [key: string]: string } = {
        'Socket': 'Socket Status: On',
        'Light': 'Light Brightness: 50%',
        'Air Conditioner': 'Temperature: 26℃'
    }
    build() {
        SideBarContainer(SideBarContainerType.Embed) {
            // Sidebar
            Column() {
                ForEach(['Device Control', 'Scene Management', 'Data Display'], (item) => {
                    Text(item).fontSize(20).onClick(() => {
                        // Click logic for the sidebar
                    })
                })
            }
              .width('20%')
              .backgroundColor('#F1F3F5')
            Column() {
                // Device List
                GridRow() {
                    ForEach(this.deviceList, (device) => {
                        GridCol({ span: 4 }) {
                            Column() {
                                Text(device).fontSize(18).onClick(() => {
                                    this.selectedDevice = device
                                })
                            }
                              .padding(10)
                              .backgroundColor('#FFFFFF')
                              .borderRadius(10)
                        }
                    })
                }
                // Device Details
                Column() {
                    Text(this.deviceDetails[this.selectedDevice] || '').fontSize(16).padding(10)
                }
            }
              .width('80%')
        }
          .sideBarWidth('20%')
          .showSideBar(true)
    }
}
Enter fullscreen mode Exit fullscreen mode

Small-screen Devices: Dual-column Mode (Bottom Tab Switching + Device Details Page)

On small-screen devices, different functional pages are switched through the bottom Tabs. The Navigation component is used to achieve page switching, and the Grid is used for the layout of the device details page. The sample code is as follows:

@Entry
@Component
struct SmallScreenPanel {
    @State currentTab: number = 0
    @State selectedDevice: string = ''
    @State deviceList: string[] = ['Socket', 'Light', 'Air Conditioner']
    @State deviceDetails: { [key: string]: string } = {
        'Socket': 'Socket Status: On',
        'Light': 'Light Brightness: 50%',
        'Air Conditioner': 'Temperature: 26℃'
    }
    build() {
        Column() {
            if (this.currentTab === 0) {
                // Device List Page
                GridRow() {
                    ForEach(this.deviceList, (device) => {
                        GridCol({ span: 12 }) {
                            Column() {
                                Text(device).fontSize(18).onClick(() => {
                                    this.selectedDevice = device
                                })
                            }
                              .padding(10)
                              .backgroundColor('#FFFFFF')
                              .borderRadius(10)
                        }
                    })
                }
            } else {
                // Device Details Page
                Column() {
                    Text(this.deviceDetails[this.selectedDevice] || '').fontSize(16).padding(10)
                }
            }
            Tabs({ barPosition: BarPosition.End }) {
                TabContent() {
                    // Device Control Page
                }
                .tabBar(
                    Column() {
                        Image($r('app.media.device_control_icon')).width(24).height(24)
                        Text('Device Control').fontSize(12)
                    }
                  .justifyContent(FlexAlign.Center).height('100%').width('100%')
                )
                TabContent() {
                    // Scene Management Page
                }
                .tabBar(
                    Column() {
                        Image($r('app.media.scene_management_icon')).width(24).height(24)
                        Text('Scene Management').fontSize(12)
                    }
                  .justifyContent(FlexAlign.Center).height('100%').width('100%')
                )
                TabContent() {
                    // Data Display Page
                }
                .tabBar(
                    Column() {
                        Image($r('app.media.data_display_icon')).width(24).height(24)
                        Text('Data Display').fontSize(12)
                    }
                  .justifyContent(FlexAlign.Center).height('100%').width('100%')
                )
            }
              .barMode(BarMode.Fixed)
              .barWidth('100%')
              .barHeight(56)
              .onChange((index: number) => {
                    this.currentTab = index
                })
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Dynamically Adjusting the Arrangement of Device Cards Using the Grid Layout (Single Column for Mobile Phones, Dual Columns for Tablets, and Three Columns for Smart Screens)

With the flexibility of the grid layout, the arrangement of device cards is dynamically adjusted according to the screen sizes of different devices. By setting the span property of GridCol, different arrangement effects are achieved under different breakpoints. The sample code is as follows:

@Entry
@Component
struct GridLayoutForDevices {
    @State currentBreakpoint: string ='sm'
    @State deviceList: string[] = ['Socket', 'Light', 'Air Conditioner']
    build() {
        GridRow({ breakpoints: { value: ['600vp', '840vp'], reference: BreakpointsReference.WindowSize } }) {
            ForEach(this.deviceList, (device) => {
                GridCol({ span: { xs: 12, sm: 12, md: 6, lg: 4 } }) {
                    Column() {
                        Text(device).fontSize(18)
                    }
                      .padding(10)
                      .backgroundColor('#FFFFFF')
                      .borderRadius(10)
                }
            })
        }
          .onBreakpointChange((breakpoint: string) => {
                this.currentBreakpoint = breakpoint
            })
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, under the xs and sm breakpoints (corresponding to the screen size of mobile phones), the device cards are displayed in a single column; under the md breakpoint (corresponding to the screen size of tablets), they are displayed in two columns; under the lg breakpoint (corresponding to the screen size of smart screens), they are displayed in three columns.

Optimizing the Interaction Experience of Device Control

Using Responsive Breakpoints to Allow Users to Freely Adjust the Window Size, and the Control Panel Automatically Adapts

By setting responsive breakpoints and listening to changes in the window size, the appropriate layout is switched under different window sizes. Use the breakpoints property and the onBreakpointChange event of the GridRow component to achieve dynamic layout adjustment. For example:

@Entry
@Component
struct ResponsivePanel {
    @State currentBreakpoint: string ='sm'
    build() {
        GridRow({ breakpoints: { value: ['600vp', '840vp'], reference: BreakpointsReference.WindowSize } }) {
            // Layout content
        }
          .onBreakpointChange((breakpoint: string) => {
                this.currentBreakpoint = breakpoint
            })
    }
}
Enter fullscreen mode Exit fullscreen mode

When the window size changes, the onBreakpointChange callback function will be triggered, thereby updating the layout status to ensure that the control panel can maintain a good display effect even in the free window mode.

Component-based Design: How to Encapsulate Reusable UI Components for Smart Devices (Sockets, Lights, Air Conditioners, etc.)

To improve development efficiency and code maintainability, the UI components of different smart devices are encapsulated. Take the light component as an example:

@Component
struct LightComponent {
    @Prop isOn: boolean
    @Prop brightness: number
    @State isEditing: boolean = false
    build() {
        Column() {
            Text('Light').fontSize(18)
            if (this.isEditing) {
                // Editing mode, brightness can be adjusted
                Slider({ value: this.brightness * 100, min: 0, max: 100, style: SliderStyle.OutSet })
                  .blockColor(Color.White)
                  .width('80%')
                  .onChange((value: number) => {
                        // Logic to update brightness
                    })
            } else {
                // Normal mode, display status
                Text(this.isOn? 'On' : 'Off').fontSize(16)
                Text(`Brightness: ${this.brightness * 100}%`).fontSize(14)
            }
            Button(this.isEditing? 'Done' : 'Edit').onClick(() => {
                this.isEditing =!this.isEditing
            })
        }
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)
    }
}
Enter fullscreen mode Exit fullscreen mode

In the main control panel, the component can be used like this:

@Entry
@Component
struct MainPanel {
    @State lightIsOn: boolean = true
    @State lightBrightness: number = 0.5
    build() {
        Column() {
            LightComponent({ isOn: this.lightIsOn, brightness: this.lightBrightness })
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Optimization of Gesture and Remote Control: Supporting Sliding to Adjust Brightness, Clicking to Switch On/off, and Voice Control Adaptation

On touch devices, by adding touch event listeners, the functions of sliding to adjust the brightness and clicking to switch the on/off state are achieved. For voice control, with the help of the voice recognition capabilities of HarmonyOS Next, user voice commands are recognized and corresponding operations are executed. The sample code is as follows:

@Component
struct InteractiveLightComponent {
    @Prop isOn: boolean
    @Prop brightness: number
    @State isEditing: boolean = false
    build() {
        Column() {
            Text('Light').fontSize(18)
            if (this.isEditing) {
                Slider({ value: this.brightness * 100, min: 0, max: 100, style: SliderStyle.OutSet })
                  .blockColor(Color.White)
                  .width('80%')
                  .onChange((value: number) => {
                        // Logic to update brightness
                    })
            } else {
                Text(this.isOn? 'On' : 'Off').fontSize(16)
                Text(`Brightness: ${this.brightness * 100}%`).fontSize(14)
            }
            Button(this.isEditing? 'Done' : 'Edit').onClick(() => {
                this.isEditing =!this.isEditing
            })
        }
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)
          .onTouch((event) => {
                if (event.type === TouchType.Swipe) {
                    // Logic to adjust brightness according to the sliding direction
                } else if (event.type === TouchType.Click) {
                    // Logic to switch on/off by clicking
                }
            })
    }
}
Enter fullscreen mode Exit fullscreen mode

For voice control, first, the voice recognition function needs to be initialized, and then when the corresponding command is recognized, the device control function is called:

import { speech } from '@ohos.speech';

@Entry
@Component
struct VoiceControlledPanel {
    @State lightIsOn: boolean = true
    @State lightBrightness: number = 0.5
    aboutToAppear() {
        speech.init({
            onResult: (result) => {
                if (result.includes('Turn on the light')) {
                    this.lightIsOn = true
                } else if (result.includes('Turn off the light')) {
                    this.lightIsOn = false
                } else if (result.includes('Brighten the light')) {
                    this.lightBrightness = Math.min(1, this.lightBrightness + 0.1)
                } else if (result.includes('Dim the light')) {
                    this.lightBrightness = Math.max(0, this.lightBrightness - 0.1)
                }
            },
            onError: (error) => {
                console.error('Voice recognition error:', error)
            }
        })
        speech.start()
    }
    aboutToDisappear() {
        speech.stop()
    }
    build() {
        Column() {
            InteractiveLightComponent({ isOn: this.lightIsOn, brightness: this.lightBrightness })
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Through the above design and implementation, we can create a smart home control panel with complete functions, user-friendly interaction, and compatibility with a variety of devices, giving full play to the advantages of HarmonyOS Next and providing users with a convenient smart home control experience.

Top comments (0)