[quote] (full code at the end)
In this article, we will introduce how to utilize the features of Hongmeng NEXT to develop efficient and beautiful applications through a simple counter application case. We will cover the basic functional implementation of counters, user interface design, data persistence and the addition of animation effects.
[Environmental Preparation]
Computer system: windows 10
Development Tools: DevEco Studio 5.0.1 Beta3 Build Version: 5.0.5.200
Project version: API 13
Real camera: Mate60 Pro
Languages: ArkTS, ArkUI
[Project overview]
The aim of this project is to create a multi-counter application where the user can freely add, edit, reset and delete counters. Each counter has a separate name, current value, increase step and decrease step. The application also includes a display of the total count so that the user can quickly see the sum of all counters.
[Function realization
1. Counter model
First, we define a CounterItem class to represent a single counter, which contains the basic properties and behavior of the counter.
@ObservedV2 class CounterItem { id: number = ++; @Trace name: string; @Trace count: number = 0; @Trace scale: ScaleOptions = { x: 1, y: 1 }; upStep: number = 1; downStep: number = 1; constructor(name: string) { = name; } }
2、Application entrance and state management
The main entry component of the application, Index, is responsible for managing the list of counters, the total count, and the state of the UI. The @State and @Watch decorators are used here to monitor state changes.
@Entry @Component struct Index { static counterStorageKey: string = "counterStorageKey"; static counterId: number = 0; @State listSpacing: number = 20; @State listItemHeight: number = 120; @State baseFontSize: number = 60; @State @Watch('updateTotalCount') counters: CounterItem[] = []; @State totalCount: number = 0; @State isSheetVisible: boolean = false; @State selectedIndex: number = 0; // ... Other methods }
3. Data persistence
To ensure that the data is still available after an application restart, we use the preferences module to read and write the data synchronously.
saveDataToLocal() { const saveData: object[] = (counter => ({ count: , name: , upStep: , downStep: , })); ?.putSync(, (saveData)); ?.flush(); }
4、User Interface
The user interface is designed in a modern and clean style, mainly consisting of a total count display area at the top, a counter list area in the center and action buttons at the bottom. The list items support left and right swiping to display reset and delete buttons.
@Builder itemStart(index: number) { Row() { Text('Reset').fontColor().fontSize('40lpx').textAlign().width('180lpx'); } .height('100%') .backgroundColor() .justifyContent() .borderRadius({ topLeft: 10, bottomLeft: 10 }) .onClick(() => { [index].count = 0; (); (); }); }
5, animation effect
When the user adds a new counter, the new counter is gradually enlarged to normal size through an animation effect to enhance the user experience.
(new CounterItem(`new counter${}`)); ({ xOffset: 0, yOffset: 0 }); [0].scale = { x: 0.8, y: 0.8 }; animateTo({ duration: 1000, curve: (0, 10, 80, 10), iterations: 1, onFinish: () => {} }, () => { [0].scale = { x: 1, y: 1 }; });
[Summary]
Through the above steps, we successfully built a counter application with basic functionality. In the process, we not only learned how to use the various APIs provided by Hongmeng NEXT, but also mastered how to optimize the user experience by combining animation, data persistence and other technical points. Hope this article can provide some help and inspiration for your Hongmeng development journey!
[complete code]
import { curves, promptAction } from '@' // Importing animated curves and cueing operations import { preferences } from '@' // Import the Preferences module @ObservedV2 // Observer decorator to monitor state changes class CounterItem { id: number = ++ // Counter ID, auto increment @Trace name: string // Counter name @Trace count: number = 0 // Current value of the counter, initially 0 @Trace scale: ScaleOptions = { x: 1, y: 1 } // Counter scaling, initially 1 upStep: number = 1 // Increase the step size, initially to 1 downStep: number = 1 // Reduce the step size, initially to 1 constructor(name: string) { // Constructor to initialize counter name = name } } @Entry // Entry component decorator @Component // Component Decorator struct Index { static counterStorageKey: string = "counterStorageKey" // Key to store counter data static counterId: number = 0 // Static counter ID @State listSpacing: number = 20 // list item spacing @State listItemHeight: number = 120 // list item height @State baseFontSize: number = 60 // Base font size @State @Watch('updateTotalCount') counters: CounterItem[] = [] // Counter array to monitor total count updates @State totalCount: number = 0 // Total count @State isSheetVisible: boolean = false // Control the visibility of the bottom popup form @State selectedIndex: number = 0 // Index of the currently selected counter listScroller: ListScroller = new ListScroller() // Example of a list scroller dataPreferences: | undefined = undefined // Examples of preferences updateTotalCount() { // Method for updating the total count let total = 0; // Initialize total count for (let i = 0; i < ; i++) { // Iterate through the counter array total += [i].count // Accumulate the count value for each counter. } = total // Update total count () // Save data locally } saveDataToLocal() { // Methods to save counter data locally const saveData: object[] = [] // Initialize the array that holds the data for (let i = 0; i < ; i++) { // Iterate through the counter array let counter: CounterItem = [i] // Get the current counter (Object({ // Add counter data to the save array count: , name: , upStep: , downStep: , })) } ?.putSync(, (saveData)) // Save data to preferences ?.flush() // Refresh preferences } @Builder // Builder Decorator itemStart(index: number) { // Reset button to the left of the list item Row() { Text('Reset').fontColor().fontSize('40lpx')// Display "Reset" text .textAlign()// Text centering .width('180lpx') // Set the width } .height('100%') // Set the height .backgroundColor() // Set the background color .justifyContent() // Set the content to be evenly distributed .borderRadius({ topLeft: 10, bottomLeft: 10 }) // Setting rounded corners .onClick(() => { // Click event [index].count = 0 // Reset the counter's count to 0 () // Update total count () // Disable all sliding actions }) } @Builder // Builder Decorator itemEnd(index: number) { // Delete button to the right of the list item Row() { Text('Delete').fontColor().fontSize('40lpx')// Display "Delete" text .textAlign()// Text centering .width('180lpx') // Set the width } .height('100%') // Set the height .backgroundColor() // Set the background color .justifyContent() // Set the content to be evenly distributed .borderRadius({ topRight: 10, bottomRight: 10 }) // Setting rounded corners .onClick(() => { // Click event (index, 1) // Remove the counter from the array () // Disable all sliding actions ({ // Display a prompt that the deletion was successful message: 'Deleted successfully', duration: 2000, bottom: '400lpx' }); }) } aboutToAppear(): void { // Called when the component is about to appear const options: = { name: }; // Get preference options = (getContext(), options); // Synchronize getting preferences const savedData: string = (, "[]") as string // Get the saved data const parsedData: Array<CounterItem> = (savedData) as Array<CounterItem> // Parsing the data (`parsedData:${(parsedData)}`) // Print the parsed data for (const item of parsedData) { // Iterate over the parsed data const newItem = new CounterItem() // Create a new counter instance = // Set the counter's count = // Set the counter's upStep = // Set the counter's downStep (newItem) // Add the new counter to the array } () // Update total count } build() { // Build the component's UI Column() { Text('Counter')// Show title .width('100%')// Set the width .height('88lpx')// Set the height .fontSize('38lpx')// Setting the font size .backgroundColor()// Set the background color .textAlign() // Text centering Column() { List({ space: , scroller: }) { // Create a list ForEach(, (counter: CounterItem, index: number) => { // Iterate through the counter array ListItem() { // List items Row() { // Row Layout Stack() { // Stacked Layout Rect().fill("#65DACC").width(`${ / 2}lpx`).height('4lpx') // top horizontal bar Circle()// Round button .width(`${}lpx`) .height(`${}lpx`) .fillOpacity(0)// Transparent fill .borderWidth('4lpx')// Border width .borderRadius('50%')// Rounded corners .borderColor("#65DACC") // Border color } .width(`${ * 2}lpx`) // Set the width .height(`100%`) // Set the height .clickEffect({ scale: 0.6, level: }) // Click effect .onClick(() => { // Click event -= // Reduce the counter count () // Update total count }) Stack() { // Stacked Layout Text()// Display counter name .fontSize(`${ / 2}lpx`)// Setting the font size .fontColor()// Set the font color .margin({ bottom: `${ * 2}lpx` }) // Set the bottom margin Text(`${}`)// Display the current value of the counter .fontColor()// Set the font color .fontSize(`${}lpx`) // Setting the font size }.height('100%') // Set the height Stack() { // Stacked Layout Rect().fill("#65DACC").width(`${ / 2}lpx`).height('4lpx') // lower horizontal bar Rect() .fill("#65DACC") .width(`${ / 2}lpx`) .height('4lpx') .rotate({ angle: 90 }) // Vertical horizontal bar Circle()// Round button .width(`${}lpx`)// Set the width .height(`${}lpx`)// Set the height .fillOpacity(0)// Transparent fill .borderWidth('4lpx')// Border width .borderRadius('50%')// Rounded corners .borderColor("#65DACC") // Border color } .width(`${ * 2}lpx`) // Set the stacked layout width .height(`100%`) // Set the stacked layout height .clickEffect({ scale: 0.6, level: }) // Click effect .onClick(() => { // Click event += // Increase the counter's count () // Update total count }) } .width('100%') // Set the width of the list item .backgroundColor() // Set the background color .justifyContent() // Set the content to be aligned at both ends .padding({ left: '30lpx', right: '30lpx' }) // Set left and right inner margins } .height() // Set the height of the list item .width('100%') // Set the width of the list item .margin({ // Set the outer margin of the list item top: index == 0 ? : 0, // If it's the first item, set the top margin bottom: index == - 1 ? : 0 // If it's the last item, set the bottom margins }) .borderRadius(10) // Setting rounded corners .clip(true) // Trim the excess .swipeAction({ start: (index), end: (index) }) // Setting up the slide operation .scale() // Setting the counter scaling .onClick(() => { // Click event = index // Set the currently selected counter index = true // Show bottom popup form }) }, (counter: CounterItem) => ())// Use the counter ID as a unique key .onMove((from: number, to: number) => { // List item move event const tmp = (from, 1); // Remove the counter from its original position (to, 0, tmp[0]) // Insert into new position }) } .scrollBar() // Hide the scrollbar .width('648lpx') // Set the list width .height('100%') // Set the height of the list } .width('100%') // Set the column width .layoutWeight(1) // Setting Layout Weights Row() { // Bottom total line Column() { // Column layout Text('Total').fontSize('26lpx').fontColor() // Display "Total" text Text(`${}`).fontSize('38lpx').fontColor() // Display total count }.margin({ left: '50lpx' }) // Set the left margin .justifyContent() // Set the content to be left-aligned .alignItems() // Set the item to be left-aligned .width('300lpx') // Set the column width Row() { // Add button rows Text('Add').fontColor().fontSize('28lpx') // Display "Add" text } .onClick(() => { // Click event (new CounterItem(`new counter${}`)) // Add new counter ({ xOffset: 0, yOffset: 0 }) // Scroll to top [0].scale = { x: 0.8, y: 0.8 }; // Set new counter scaling animateTo({ // Animation effects duration: 1000, // Animation duration curve: (0, 10, 80, 10), // Animated curves iterations: 1, // Number of animation iterations onFinish: () => { // Callback when the animation is complete } }, () => { [0].scale = { x: 1, y: 1 }; // Restore zoom }) }) .width('316lpx') // Set the button width .height('88lpx') // Set the button height .backgroundColor("#65DACC") // set the button background color .borderRadius(10) // Set button rounding .justifyContent() // Set the content to be centered }.width('100%').height('192lpx').backgroundColor() // Set row width and height } .backgroundColor("#f2f2f7") // set the background color .width('100%') // Set the width .height('100%') // Set the height .bindSheet(, (), { // Bind the bottom popup form height: 300, // Set the height of the form dragBar: false, // Disable the drag bar onDisappear: () => { // Callback when form disappears = false // Hide the form } }) } @Builder // Builder Decorator mySheet() { // Create a bottom popup form Column({ space: 20 }) { // Column layout, set spacing Row() { // Row Layout Text('Count Title:') // Display "Count Title" text TextInput({ text: [].name }).width('300lpx').onChange((value) => { // Input box, binding counter name [].name = value // Update the counter name }) } Row() { // Row Layout Text('Increase step size:') // Display "Increase Step Size" text TextInput({ text: `${[].upStep}` })// Input box, bind to increase step size .width('300lpx')// Set the width of the input box .type()// Set the input box type to numeric .onChange((value) => { // Input box change event [].upStep = parseInt(value) // Update to increase step size () // Update total count }) } Row() { // Row Layout Text('Reduction of step length:') // Display "Reduce Step" text TextInput({ text: `${[].downStep}` })// Input box, bind reduce step .width('300lpx')// Set the width of the input box .type()// Set the input box type to numeric .onChange((value) => { // Input box change event [].downStep = parseInt(value) // Update reduction step () // Update total count }) } } .justifyContent() // Set the content to be left-aligned .padding(40) // Setting the inner margins .width('100%') // Set the width .height('100%') // Set the height .backgroundColor() // Set the background color } }