Location>code7788 >text

Hongmeng NEXT Development Case: Carousel

Popularity:692 ℃/2024-11-10 08:14:52

 

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