HarmonyOS NEXT Development Case: Garbage Classification

import { curves } from '@kit.ArkUI'; // Import curve module from ArkUI toolkit // Define Garbage Item Class class GarbageItem { name: string; // Item name type: number; // Category type description?: string; // Optional description // Constructor for initializing garbage item constructor(name: string, type: number, description?: string) { this.name = name; // Set item name this.type = type; // Set category type this.description = description || ""; // Set description with default empty string } } // Define Garbage Category Class class GarbageCategory { name: string; // Category name type: number; // Category type color: string; // Display color description: string; // Category description // Constructor for initializing garbage category constructor(name: string, type: number, color: string, description: string) { this.name = name; // Set category name this.type = type; // Set category type this.color = color; // Set display color this.description = description; // Set category description } } // Application entry component with decorators @Entry @Component struct Index { // State management variables @State currentQuestionIndex: number = 0; // Current question index @State quizResults: string[] = []; // Quiz result records @State totalScore: number = 0; // Total score @State showAnimation: boolean = false; // Animation control flag @State scaleOptions: ScaleOptions = { x: 1, y: 1 }; // Scaling parameters @State itemXPosition: number = 0; // Item X-axis position @State itemOpacity: number = 1.0; // Item opacity // Predefined garbage categories @State garbageCategories: GarbageCategory[] = [ new GarbageCategory("Hazardous", 0, "#e2413f", "Waste harmful to human health or environment"), new GarbageCategory("Recyclable", 1, "#1c6bb5", "Reusable and recyclable materials"), new GarbageCategory("Kitchen", 2, "#4ca84e", "Biodegradable organic waste"), new GarbageCategory("Other", 3, "#5f5f5f", "Non-classifiable residual waste"), ]; // Predefined garbage items @State garbageItems: GarbageItem[] = [ // Kitchen waste examples new GarbageItem("Vegetable leaves", 2), new GarbageItem("Leftovers", 2), // ... (other items remain similar with English translations) new GarbageItem("Toilet paper", 3, "Water-soluble paper products cannot be recycled") ]; // Lifecycle hook for initializing quiz aboutToAppear(): void { this.resetQuiz(); } // Quiz initialization logic resetQuiz() { this.quizResults = []; this.totalScore = 0; this.currentQuestionIndex = 0; this.shuffleItems(); } // Fisher-Yates shuffle algorithm implementation shuffleItems() { for (let i = this.garbageItems.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [this.garbageItems[i], this.garbageItems[j]] = [this.garbageItems[j], this.garbageItems[i]]; } } // Answer validation logic checkAnswer(categoryType: number) { const currentItem = this.garbageItems[this.currentQuestionIndex]; const isCorrect = currentItem.type === categoryType; this.quizResults.push( `${currentItem.name} (${this.garbageCategories[categoryType].name}) ` + `[${isCorrect ? "✓" : this.garbageCategories[currentItem.type].name}]` ); if (isCorrect) this.totalScore += 10; this.currentQuestionIndex++; if (this.currentQuestionIndex >= 10) { this.displayResults(); this.resetQuiz(); } } // Results display implementation displayResults() { const resultSheets = this.quizResults.map(result => ({ title: result, action: () => {} })); this.getUIContext().showActionSheet({ title: 'Score Report', message: `Total Score: ${this.totalScore}`, confirm: { defaultFocus: true, value: 'OK', action: () => {} }, alignment: DialogAlignment.Center, sheets: resultSheets }); } // UI construction using ArkUI declarative syntax build() { Column() { Text(`Question: ${this.currentQuestionIndex + 1}/10`) .fontSize(30) .margin(20); Stack() { // Animated item display Text(this.garbageItems[this.currentQuestionIndex].name) .size(130) .style({ border: { width: 1 }, borderRadius: 5, backgroundColor: Color.Orange, textAlign: TextAlign.Center, fontSize: 20, padding: 2 }) .scale(this.scaleOptions); // Particle animation implementation if (this.showAnimation) { Particle({ particles: [{ emitter: { particle: { type: ParticleType.POINT, config: { radius: 5 }}, emitRate: 100, position: ['25%', 0], shape: ParticleEmitterShape.RECTANGLE }, // ... (particle configuration remai

May 11, 2025 - 02:12
 0
HarmonyOS NEXT Development Case: Garbage Classification

Image description

import { curves } from '@kit.ArkUI'; // Import curve module from ArkUI toolkit

// Define Garbage Item Class
class GarbageItem {
  name: string; // Item name
  type: number; // Category type
  description?: string; // Optional description

  // Constructor for initializing garbage item
  constructor(name: string, type: number, description?: string) {
    this.name = name; // Set item name
    this.type = type; // Set category type
    this.description = description || ""; // Set description with default empty string
  }
}

// Define Garbage Category Class
class GarbageCategory {
  name: string; // Category name
  type: number; // Category type
  color: string; // Display color
  description: string; // Category description

  // Constructor for initializing garbage category
  constructor(name: string, type: number, color: string, description: string) {
    this.name = name; // Set category name
    this.type = type; // Set category type
    this.color = color; // Set display color
    this.description = description; // Set category description
  }
}

// Application entry component with decorators
@Entry
@Component
struct Index {
  // State management variables
  @State currentQuestionIndex: number = 0; // Current question index
  @State quizResults: string[] = []; // Quiz result records
  @State totalScore: number = 0; // Total score
  @State showAnimation: boolean = false; // Animation control flag
  @State scaleOptions: ScaleOptions = { x: 1, y: 1 }; // Scaling parameters
  @State itemXPosition: number = 0; // Item X-axis position
  @State itemOpacity: number = 1.0; // Item opacity

  // Predefined garbage categories
  @State garbageCategories: GarbageCategory[] = [
    new GarbageCategory("Hazardous", 0, "#e2413f", "Waste harmful to human health or environment"),
    new GarbageCategory("Recyclable", 1, "#1c6bb5", "Reusable and recyclable materials"),
    new GarbageCategory("Kitchen", 2, "#4ca84e", "Biodegradable organic waste"),
    new GarbageCategory("Other", 3, "#5f5f5f", "Non-classifiable residual waste"),
  ];

  // Predefined garbage items
  @State garbageItems: GarbageItem[] = [
    // Kitchen waste examples
    new GarbageItem("Vegetable leaves", 2),
    new GarbageItem("Leftovers", 2),
    // ... (other items remain similar with English translations)
    new GarbageItem("Toilet paper", 3, "Water-soluble paper products cannot be recycled")
  ];

  // Lifecycle hook for initializing quiz
  aboutToAppear(): void {
    this.resetQuiz();
  }

  // Quiz initialization logic
  resetQuiz() {
    this.quizResults = [];
    this.totalScore = 0;
    this.currentQuestionIndex = 0;
    this.shuffleItems();
  }

  // Fisher-Yates shuffle algorithm implementation
  shuffleItems() {
    for (let i = this.garbageItems.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [this.garbageItems[i], this.garbageItems[j]] = [this.garbageItems[j], this.garbageItems[i]];
    }
  }

  // Answer validation logic
  checkAnswer(categoryType: number) {
    const currentItem = this.garbageItems[this.currentQuestionIndex];
    const isCorrect = currentItem.type === categoryType;

    this.quizResults.push(
      `${currentItem.name} (${this.garbageCategories[categoryType].name}) ` +
      `[${isCorrect ? "" : this.garbageCategories[currentItem.type].name}]`
    );

    if (isCorrect) this.totalScore += 10;
    this.currentQuestionIndex++;

    if (this.currentQuestionIndex >= 10) {
      this.displayResults();
      this.resetQuiz();
    }
  }

  // Results display implementation
  displayResults() {
    const resultSheets = this.quizResults.map(result => ({
      title: result,
      action: () => {}
    }));

    this.getUIContext().showActionSheet({
      title: 'Score Report',
      message: `Total Score: ${this.totalScore}`,
      confirm: {
        defaultFocus: true,
        value: 'OK',
        action: () => {}
      },
      alignment: DialogAlignment.Center,
      sheets: resultSheets
    });
  }

  // UI construction using ArkUI declarative syntax
  build() {
    Column() {
      Text(`Question: ${this.currentQuestionIndex + 1}/10`)
        .fontSize(30)
        .margin(20);

      Stack() {
        // Animated item display
        Text(this.garbageItems[this.currentQuestionIndex].name)
          .size(130)
          .style({
            border: { width: 1 },
            borderRadius: 5,
            backgroundColor: Color.Orange,
            textAlign: TextAlign.Center,
            fontSize: 20,
            padding: 2
          })
          .scale(this.scaleOptions);

        // Particle animation implementation
        if (this.showAnimation) {
          Particle({
            particles: [{
              emitter: {
                particle: { type: ParticleType.POINT, config: { radius: 5 }},
                emitRate: 100,
                position: ['25%', 0],
                shape: ParticleEmitterShape.RECTANGLE
              },
              // ... (particle configuration remains similar)
            }]
          }).size('100%');
        }
      }
      .size(150, 300)
      .translate({ x: `${this.itemXPosition}px` });

      // Category selection buttons
      Row() {
        ForEach(this.garbageCategories, (category) => {
          Column() {
            Text(category.name).style(categoryTextStyle);
            Divider();
            Text(category.description).style(descTextStyle);
          }
          .clickEffect({ scale: 0.8 })
          .onClick(() => this.handleCategoryClick(category))
        });
      }

      Button('Restart')
        .onClick(() => this.resetQuiz())
        .margin(50);
    }
    .width('100%');
  }

  // Animation handling logic
  private handleCategoryClick(category: GarbageCategory) {
    if (this.showAnimation) return;

    this.showAnimation = true;
    const positions = [-270, -90, 90, 270];

    animateToImmediately({
      duration: 200,
      onFinish: this.handleAnimationComplete.bind(this, category)
    }, () => {
      this.itemXPosition = positions[category.type];
    });
  }

  private handleAnimationComplete(category: GarbageCategory) {
    animateToImmediately({
      duration: 800,
      curve: curves.springCurve(0, 20, 90, 20),
      onFinish: this.finalizeAnswerCheck.bind(this, category)
    }, () => {
      this.scaleOptions = { x: 1.3, y: 1.3 };
    });
  }

  private finalizeAnswerCheck(category: GarbageCategory) {
    animateToImmediately({
      duration: 200,
      onFinish: () => {
        this.resetAnimationState();
        this.checkAnswer(category.type);
      }
    }, () => {
      this.itemXPosition = 0;
      this.scaleOptions = { x: 1.0, y: 1.0 };
    });
  }

  private resetAnimationState() {
    this.itemXPosition = 0;
    this.showAnimation = false;
  }
}

// Style constants
const categoryTextStyle = {
  fontSize: 30,
  color: Color.White,
  padding: 5
};

const descTextStyle = {
  fontSize: 28,
  color: Color.White,
  padding: 5
};

Technical Highlights

  1. State Management:

    Utilizes ArkUI's @State decorator for reactive UI updates. The component maintains multiple state variables including current question index, user score, and animation parameters.

  2. Dynamic Shuffling:

    Implements Fisher-Yates algorithm for randomizing question order, ensuring varied quiz experiences.

  3. Particle Animation:

    Creates engaging visual feedback using HarmonyOS's Particle system with configurable:

    • Emission rates
    • Particle trajectories
    • Color transitions
    • Scaling effects
  4. Gesture Interactions:

    Features spring-based animations with curves.springCurve for smooth category selection feedback.

  5. Responsive Layout:

    Implements adaptive UI using:

    • Percentage-based dimensions
    • Flexbox layout (Row/Column)
    • Stack positioning
    • Dynamic translation transforms
  6. Result Visualization:

    Uses ActionSheet component to display detailed score breakdown with scrollable history.

Development Considerations

  1. Performance Optimization:

    • Animation frame synchronization
    • Object pooling for particle effects
    • Memoization of static resources
  2. Localization Support:

    The architecture supports easy translation through centralized string management in class definitions.

  3. Accessibility:

    Built-in contrast ratios and click effect visual feedback enhance usability.

This implementation demonstrates HarmonyOS's capabilities in building engaging, interactive applications while maintaining clean code organization and smooth performance.