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)
}
}
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
})
}
}
}
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
})
}
}
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
})
}
}
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)
}
}
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 })
}
}
}
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
}
})
}
}
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 })
}
}
}
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)