Chapter 4 UI Component Introduction and Practical Applications (Part 3)
Through the learning of previous chapters, we have already gained a preliminary understanding of some basic components and layout methods of OpenHarmony. In this chapter, we will further consolidate the learned content by combining the use of multiple components through a complete example of a digital clock. This example will demonstrate how to use OpenHarmony's animation functions, state management, as well as components like sliders and toggle switches, helping you better understand and apply the ArkUI framework.
1. Example Demonstration
After learning the previous chapters, we have a general understanding of some basic components. To learn more, you can go to the HarmonyOS official website.
We will implement a dynamic digital clock that can display real-time updated time, including the following:
Core functions:
- Time dynamic update: Achieve flip effect of numbers through animation.
- Brightness adjustment: Use slider component to dynamically set screen brightness.
- Full-screen mode switching: Use toggle component to switch between full-screen mode and normal mode.
Key components:
- Text: Used to display clock numbers.
- Slider: Used to adjust screen brightness.
- Toggle: Used for full-screen switching function.
- Animation: Achieve number flip effect.
1.1 Main Code of Digital Clock:
// Modify time
changeTime(): Array<number> {
const time = new Date()
const hour = time.getHours()
const hourOne = Math.floor(hour / 10)
const hourTwo = hour % 10
const minutesOne = Math.floor(time.getMinutes() / 10)
const minutesTwo = time.getMinutes() % 10
const secondsOne = Math.floor(time.getSeconds() / 10)
const secondsTwo = time.getSeconds() % 10
return [hourOne, hourTwo, minutesOne, minutesTwo, secondsOne, secondsTwo]
}
@Builder box(num : number) {
Column() {
Divider()
.zIndex(5)
.strokeWidth(2)
.color(Color.White)
.position({ x: 0, y: '50%' })
Text(`${this.timeOne[num]}`)
.zIndex(1)
.height(108)
.width('100%')
.borderRadius(8)
.fontWeight(700)
.padding({ top: 0 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.backgroundColor($r('app.color.text_bg'))
.textAlign(TextAlign.Center)
Text(`${this.timeTwo[num]}`)
.zIndex(2)
.height(64)
.width('100%')
.fontWeight(700)
.borderRadius(8)
.padding({ top: 3 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.backgroundColor($r('app.color.text_bg'))
.textAlign(TextAlign.Center)
Text(`${this.timeThree[num]}`)
.zIndex(4)
.height(64)
.width('100%')
.fontWeight(700)
.borderRadius(8)
.padding({ top: 3 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.textAlign(TextAlign.Center)
.backgroundColor($r('app.color.text_bg'))
.rotate({
x: 1,
y: 0,
z: 0,
centerX: '50%',
centerY: '100%',
angle: this.angleOne[num]
})
Text(`${this.timeFour[num]}`)
.zIndex(3)
.height(108)
.width('100%')
.fontWeight(700)
.borderRadius(8)
.padding({ top: 0 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.backgroundColor($r('app.color.text_bg'))
.textAlign(TextAlign.Center)
.rotate({
x: 1,
y: 0,
z: 0,
centerX: '50%',
centerY: '50%',
angle: this.angleTwo[num]
})
}
.height(108)
.width('50%')
}1.2 Animation Rotation Part Code:
animationOne(i : number): void {
animateTo({
duration: 250,
delay: 0,
iterations: 1,
onFinish: () => {
this.animationTwo(i)
this.angleOne[i] = 0
}
}, () => this.angleOne[i] = 90)
}
animationTwo(i : number): void {
animateTo({
duration: 250,
delay: 0,
iterations: 1,
onFinish: () => {
this.angleTwo[i] = -90
}
}, () => this.angleTwo[i] = 0)
}1.3 Full-screen Part Code:
this.isFullScreen = !this.isFullScreen
try {
let win = await window.getLastWindow(getContext(this))
let names = this.isFullScreen ? [] : ['status', 'navigation']
await win.setWindowSystemBarEnable(names as Array<'status' | 'navigation'>)
}Complete code display:
import { brightness } from '@kit.BasicServicesKit';
import { LockSwitch } from './LockSwitch';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
@State timeOne: Array<number> = []
@State timeTwo: Array<number> = []
@State timeThree: Array<number> = []
@State timeFour: Array<number> = []
@State angleOne: Array<number> = [0, 0, 0, 0, 0, 0]
@State angleTwo: Array<number> = [-90, -90, -90, -90, -90, -90]
@State isFullScreen: boolean = false
@State inSetValue: number = 40
aboutToAppear() {
const arr = this.changeTime()
this.timeOne = [...arr]
this.timeTwo = [...arr]
this.timeThree = [...arr]
this.timeFour = [...arr]
setInterval(() => {
const time = new Date()
if (this.timeOne[5] != time.getSeconds() % 10) {
const arr = this.changeTime()
for (let i = 0;i < 6; i++) {
if (arr[i] != this.timeFour[i]) {
this.timeFour[i] = arr[i]
this.animationOne(i)
setTimeout(() => {
this.timeTwo[i] = arr[i]
}, 100)
setTimeout(() => {
this.timeThree[i] = arr[i]
}, 150)
setTimeout(() => {
this.timeOne[i] = arr[i]
}, 240)
}
}
}
}, 1000)
}
animationOne(i : number): void {
animateTo({
duration: 250,
delay: 0,
iterations: 1,
onFinish: () => {
this.animationTwo(i)
this.angleOne[i] = 0
}
}, () => this.angleOne[i] = 90)
}
animationTwo(i : number): void {
animateTo({
duration: 250,
delay: 0,
iterations: 1,
onFinish: () => {
this.angleTwo[i] = -90
}
}, () => this.angleTwo[i] = 0)
}
setBrightness(): void {
brightness.setValue(this.inSetValue)
}
//Modify time
changeTime(): Array<number> {
const time = new Date()
const hour = time.getHours()
const hourOne = Math.floor(hour / 10)
const hourTwo = hour % 10
const minutesOne = Math.floor(time.getMinutes() / 10)
const minutesTwo = time.getMinutes() % 10
const secondsOne = Math.floor(time.getSeconds() / 10)
const secondsTwo = time.getSeconds() % 10
return [hourOne, hourTwo, minutesOne, minutesTwo, secondsOne, secondsTwo]
}
@Builder box(num : number) {
Column() {
Divider()
.zIndex(5)
.strokeWidth(2)
.color(Color.White)
.position({ x: 0, y: '50%' })
Text(`${this.timeOne[num]}`)
.zIndex(1)
.height(108)
.width('100%')
.borderRadius(8)
.fontWeight(700)
.padding({ top: 0 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.backgroundColor($r('app.color.text_bg'))
.textAlign(TextAlign.Center)
Text(`${this.timeTwo[num]}`)
.zIndex(2)
.height(64)
.width('100%')
.fontWeight(700)
.borderRadius(8)
.padding({ top: 3 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.backgroundColor($r('app.color.text_bg'))
.textAlign(TextAlign.Center)
Text(`${this.timeThree[num]}`)
.zIndex(4)
.height(64)
.width('100%')
.fontWeight(700)
.borderRadius(8)
.padding({ top: 3 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.textAlign(TextAlign.Center)
.backgroundColor($r('app.color.text_bg'))
.rotate({
x: 1,
y: 0,
z: 0,
centerX: '50%',
centerY: '100%',
angle: this.angleOne[num]
})
Text(`${this.timeFour[num]}`)
.zIndex(3)
.height(108)
.width('100%')
.fontWeight(700)
.borderRadius(8)
.padding({ top: 0 })
.fontSize(90)
.position({ x: 0, y: 0 })
.fontColor(Color.White)
.fontFamily('Monospace')
.backgroundColor($r('app.color.text_bg'))
.textAlign(TextAlign.Center)
.rotate({
x: 1,
y: 0,
z: 0,
centerX: '50%',
centerY: '50%',
angle: this.angleTwo[num]
})
}
.height(108)
.width('50%')
}
build() {
Column() {
Row() {
Row({ space: 2 }) {
this.box(0)
this.box(1)
}
.width('30%')
.height('30%')
Image($r('app.media.dot'))
.width(20)
.height(50)
Row({ space: 2 }) {
this.box(2)
this.box(3)
}
.width('30%')
.height('30%')
Image($r('app.media.dot'))
.width(20)
.height(50)
Row({ space: 2 }) {
this.box(4)
this.box(5)
}
.width('30%')
.height('30%')
}
.id('currentTimeBox')
.width('50%')
.height('50%')
Column({ space: 10 }) {
Divider()
.strokeWidth(5)
.color($r('app.color.divider_bg'))
Row() {
Text($r("app.string.fullScreen")).fontSize(18)
Blank()
Toggle({ type: ToggleType.Switch, isOn: this.isFullScreen })
.switchPointColor(0xe5ffffff)
.onChange(async () => {
this.isFullScreen = !this.isFullScreen
try {
let win = await window.getLastWindow(getContext(this))
let names = this.isFullScreen ? [] : ['status', 'navigation']
await win.setWindowSystemBarEnable(names as Array<'status' | 'navigation'>)
} catch (err) {
console.info(`setFullScreen fail, code = ${err.code}`)
}
})
}
.width('90%')
.height(62)
.borderRadius(24)
.margin({ top: 10 })
.backgroundColor(Color.White)
.padding({ left: '3%', right: '3%' })
Divider()
.strokeWidth(5)
.color($r('app.color.divider_bg'))
Row() {
Lock()
}
.width('90%')
.height(62)
.borderRadius(24)
.margin({ top: 10 })
.backgroundColor(Color.White)
.padding({ left: '3%', right: '3%' })
Divider()
.strokeWidth(5)
.color($r('app.color.divider_bg'))
Row() {
Text($r("app.string.brightness"))
.fontSize(18)
Slider({
value: this.inSetValue,
min: 0,
max: 255,
step: 5,
style: SliderStyle.OutSet
})
.id('brightness')
.width('90%')
.showTips(false)
.showSteps(false)
.blockColor(0xCCCCCC)
.trackColor(Color.Black)
.selectedColor(0xCCCCCC)
.onChange((value: number, mode: SliderChangeMode) => {
this.inSetValue = value
this.setBrightness()
})
}
.width('90%')
.height(62)
.borderRadius(24)
.margin({ top: 10 })
.backgroundColor(Color.White)
.padding({ left: '3%', right: '3%' })
Divider()
.strokeWidth(5)
.color($r('app.color.divider_bg'))
}
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
}
}
@Component
struct Lock {
@State isComTime: boolean = true
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Column() {
LockSwitch({ isComTime: $isComTime })
}
.width('100%')
}
}
}Source code address: Example Source Code
