DEV Community

SameX
SameX

Posted on

Development Guide for Smart Home Control Panel in HarmonyOS Next

Development Guide for Smart Home Control Panel in HarmonyOS Next

This article aims to deeply explore the technical details of the Huawei HarmonyOS Next system and summarize them based on practical development practices. It mainly serves as a medium for technical sharing and communication. There may inevitably be some errors or omissions. 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 reprint 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. Users can use the control panel to perform operations such as turning on/off and adjusting parameters of various smart devices at home, such as sockets, lights, air conditioners, etc. Scene management allows users to preset different scene modes according to their living habits, such as the "home mode" and "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), making it convenient for 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 relies 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 respectively, and users can quickly switch the interface 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 it is convenient for users to quickly select and operate the devices.
  • Smart Screens: Smart screens have even larger screens and are used for centralized control in scenarios such as the living room. A three-column mode is more appropriate. The left side is the sidebar, which is used for navigation and switching between different functional modules; the middle displays the device list in the form of larger cards, which is convenient for long-distance operation; 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(() => {
                        // Logic for clicking on 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 the switching between pages, 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, 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 can be 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 mobile phone screen size), the device cards are displayed in a single column; under the md breakpoint (corresponding to the tablet screen size), they are displayed in two columns; under the lg breakpoint (corresponding to the smart screen size), 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 the dynamic adjustment of the layout. 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 also maintain a good display effect in the free window mode.

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

In order to improve development efficiency and the maintainability of the code, 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 for updating the brightness
                    })
            } else {
                // Normal mode, display the 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: Support for Adjusting Brightness by Swiping, Switching the Switch by Clicking, and Adaptation to Voice Control

On touch devices, by adding touch event listeners, the functions of adjusting the brightness by swiping and switching the switch by clicking are achieved. For voice control, with the help of the speech recognition capability of HarmonyOS Next, user voice commands are recognized and the 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 for updating the 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 for adjusting the brightness according to the swipe direction
                } else if (event.type === TouchType.Click) {
                    // Logic for switching the switch by clicking
                }
            })
    }
}
Enter fullscreen mode Exit fullscreen mode

For voice control, first, the speech 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('Speech 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)