Location>code7788 >text

Hongmeng NEXT Development Case: Counter

Popularity:535 ℃/2024-11-17 05:28:55

 

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