[1] Introduction (full code at the end)
In Hongmeng NEXT system, developing an interesting and practical carousel application can not only enhance the user experience, but also demonstrate the powerful functions of Hongmeng system. In this article, we will introduce in detail how to develop a carousel application using Hongmeng NEXT system, covering the complete process from component definition to user interaction.
[2] Environmental Preparation
Computer system: windows 10
Development Tools: DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
Engineering version: API 12
The real one: mate60 pro
Languages: ArkTS, ArkUI
[3] Difficulty Analysis
1. Calculation of sector paths
Difficulty: Creating a path for a sector requires precise calculation of the start point, end point and arc parameters. In particular, the use of trigonometric functions is involved, and beginners may be confused about how to convert angles to coordinates.
Solution: You can draw a simple schematic diagram to help understand the construction of the sector, and add detailed comments to the code to explain each step of the calculation process.
2. Dynamic angle calculation
Difficulty: As the carousel rotates, you need to dynamically calculate the angle and rotation of each cell based on the ratio of the cells. This involves cumulative and proportional calculations that can lead to logical errors.
Solution: Use the reduce method of the array to calculate the total ratio and make sure the logic is clear when calculating the angle of each cell. You can verify that the angle of each cell is correct by using a unit test.
3. Realization of animation effects
Difficulty: Realizing the rotating animation of a carousel requires managing the duration, curve and end state of the animation. Beginners may be confused about how to control the smoothness and effect of the animation.
Solution: You can refer to the animation documentation of Hongmeng NEXT to understand the different animation effects and parameter settings. Through step-by-step debugging, observe the animation effect and make adjustments.
4. Handling of user interactions
Difficulty: Handling user click events, especially when animations are in progress, and how to disable buttons to prevent repeat clicks can lead to complexity in state management.
Solution: In the button's click event, use a state variable (such as isAnimating) to control the button's availability and restore the button's state when the animation ends.
5. State management of components
Difficulty: Passing state between multiple components (e.g. currently selected cell, angle of carousel, etc.) can lead to confusing state management.
Solution: Use state management tools such as @State and @Trace to ensure that state is managed consistently and state updates are made where needed to keep components decoupled from each other.
[complete code]
import { CounterComponent, CounterType } from '@'; // Import counter components and counter types // Define the sector component @Component struct Sector { @Prop radius: number; // Radius of the sector @Prop angle: number; // Angle of the sector @Prop color: string; // Sector color // Functions to create sector paths createSectorPath(radius: number, angle: number): string { const centerX = radius / 2; // Calculate the x-coordinate of the sector center const centerY = radius / 2; // Calculate the Y-coordinate of the sector center const startX = centerX; // X coordinate of the start of the sector const startY = centerY - radius; // Y-coordinate of the start of the sector const halfAngle = angle / 4; // Calculate the half angle // Calculate the coordinates of sector end point 1 const endX1 = centerX + radius * ((halfAngle * ) / 180); const endY1 = centerY - radius * ((halfAngle * ) / 180); // Calculate the coordinates of sector end point 2 const endX2 = centerX + radius * ((-halfAngle * ) / 180); const endY2 = centerY - radius * ((-halfAngle * ) / 180); // Determine if the arc is large const largeArcFlag = angle / 2 > 180 ? 1 : 0; const sweepFlag = 1; // Set the arc direction to clockwise // Generate SVG path command const pathCommands = `M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX1} ${endY1} L${centerX} ${centerY} L${endX2} ${endY2} A${radius} ${radius} 0 ${largeArcFlag} ${1 - sweepFlag} ${startX} ${startY} Z`; return pathCommands; // Return path command } // Build the sector component build() { Stack() { // Create the first sector path Path() .width(`${}px`) // Set the width to the radius .height(`${}px`) // Set height to radius .commands((, )) // Set path command .fillOpacity(1) // Set the fill transparency .fill() // Set the fill color .strokeWidth(0) // Set the border width to 0 .rotate({ angle: / 4 - 90 }); // Rotating Sector // Create a second sector path Path() .width(`${}px`) // Set the width to the radius .height(`${}px`) // Set height to radius .commands((, )) // Set path command .fillOpacity(1) // Set the fill transparency .fill() // Set the fill color .strokeWidth(0) // Set the border width to 0 .rotate({ angle: 180 - ( / 4 - 90) }); // Rotating Sector } } } // Define the cell class @ObservedV2 class Cell { @Trace angle: number = 0; // Angle of the sector @Trace title: string; // Title of the current grid @Trace color: string; // Background color @Trace rotate: number = 0; // At the angle of the turntable to be rotated angleStart: number = 0; // Beginning of the interval in which the wheel is located angleEnd: number = 0; // The end of the interval in which the roulette wheel is located proportion: number = 0; // Percentage // Constructor constructor(proportion: number, title: string, color: string) { = proportion; // Setting the ratio = title; // Setting the title = color; // Setting the color } } // Define the carousel component @Entry @Component struct Wheel { @State cells: Cell[] = []; // Array of stored cells @State wheelWidth: number = 600; // Width of the turntable @State currentAngle: number = 0; // Current turntable angle @State selectedName: string = ""; // Selected name isAnimating: boolean = false; // Animated state colorIndex: number = 0; // Color index colorPalette: string[] = [ // Color palette "#26c2ff", "#978efe", "#c389fe", "#ff85bd", "#ff7051", "#fea800", "#ffcf18", "#a9c92a" ]; // Called when the component is about to appear aboutToAppear(): void { // Initialize cells (new Cell(1, "Running.", [++ % ])); (new Cell(2, "Jump rope.", [++ % ])); (new Cell(1, "Sing.", [++ % ])); (new Cell(4, "Dance.", [++ % ])); (); // Calculate the angle } // Calculate the angle of each cell private calculateAngles() { // Calculation of the total percentage based on the ratio const totalProportion = ((sum, cell) => sum + , 0); (cell => { = ( * 360) / totalProportion; // Calculate the angle of each cell }); let cumulativeAngle = 0; // Cumulative angle (cell => { = cumulativeAngle; // Set the starting angle cumulativeAngle += ; // Update cumulative angle = cumulativeAngle; // Setting the end angle = cumulativeAngle - ( / 2); // Calculate the rotation angle }); } // Build the carousel component build() { Column() { Row() { Text('Carousel').fontSize(20).fontColor("#0b0e15"); // show carousel title }.width('100%').height(44).justifyContent(); // Set the width and height of the row // Show current status Text( ? 'Rotating' : `${}`).fontSize(20).fontColor("#0b0e15").height(40); Stack() { Stack() { // Iterate over each cell and draw the sector. ForEach(, (cell: Cell) => { Stack() { Sector({ radius: lpx2px() / 2, angle: , color: }); // Create sectors Text().fontColor().margin({ bottom: `${ / 1.4}lpx` }); // Display the cell title }.width('100%').height('100%').rotate({ angle: }); // Set width and height and rotate }); } .borderRadius('50%') // Setting rounded corners .backgroundColor() // Set the background color .width(`${}lpx`) // Setting the carousel width .height(`${}lpx`) // Setting the carousel height .rotate({ angle: }); // Rotating carousel // Create pointers Polygon({ width: 20, height: 10 }) .points([[0, 0], [10, -20], [20, 0]]) // Set the point of the pointer .fill("#d72b0b") // set the pointer color .height(20) // Set the pointer height .margin({ bottom: '140lpx' }); // Set the bottom pointer margin // Create a start button Button('Start') .fontColor("#c53a2c") // set the button font color .borderWidth(10) // Set the width of the button's border .borderColor("#dd2218") // set the button border color .backgroundColor("#fde427") // set the button background color .width('200lpx') // Set the button width .height('200lpx') // Set the button height .borderRadius('50%') // Set the button to round .clickEffect({ level: }) // Setting the click effect .onClick(() => { // Callback function when the button is clicked if () { // If animation is in progress, return return; } = ""; // Clear the selected name = true; // Set the animation state to being animated animateTo({ // Start animation duration: 5000, // Animation duration of 5000 milliseconds curve: , // Animation curve is slow in and slow out onFinish: () => { // Callback when the animation is complete %= 360; // Keep the current angle between 0 and 360 for (const cell of ) { // Iterate over each cell // Check if the current angle is within the cell's angle range if (360 - >= && 360 - <= ) { = ; // Set the name of the selection to the title of the current cell break; // Exit the loop when found } } = false; // Set the animation state to unanimated }, }, () => { // Callbacks while the animation is in progress += (360 * 5 + (() * 360)); // Update current angle, add random rotation }); }); } // Create a scrolling area Scroll() { Column() { // Iterate over each cell to create input boxes and counters ForEach(, (item: Cell, index: number) => { Row() { // Create a text input box to display the cell title TextInput({ text: }) .layoutWeight(1) // Set the input box to occupy the remaining space .onChange((value) => { // Callback when the content of the input box changes = value; // Update the cell title }); // Create the counter component CounterComponent({ options: { type: , // Set counter type to compact numberOptions: { label: `Current percentage`, // Setting the counter label value: , // Setting the initial counter value min: 1, // Set the minimum value max: 100, // Setting the maximum value step: 1, // Setting the step size onChange: (value: number) => { // Callbacks on counter value changes = value; // Update the cell proportions (); // Recalculate the angle } } } }); // Create a delete button Button('Delete').onClick(() => { (index, 1); // Remove the current cell from the cell array. (); // Recalculate the angle }); }.width('100%').justifyContent() // Set the width of the rows and the content alignment .padding({ left: 40, right: 40 }); // Set left and right inner margins }); }.layoutWeight(1); // Set the scroll area to occupy the remaining space }.layoutWeight(1) // Set the scroll area to occupy the remaining space .margin({ top: 20, bottom: 20 }) // Set the top and bottom margins .align(); // Set the alignment to top // Create the Add New Content button Button('Add new content').onClick(() => { // Add a new cell to the cell array. (new Cell(1, "New content", [++ % ])); (); // Recalculate the angle }).margin({ top: 20, bottom: 20 }); // Set the top and bottom margins of the button } .height('100%') // Set the component height to 100% .width('100%') // Set the component width to 100% .backgroundColor("#f5f8ff"); // set the component background color } }