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
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.Dynamic Shuffling:
Implements Fisher-Yates algorithm for randomizing question order, ensuring varied quiz experiences.-
Particle Animation:
Creates engaging visual feedback using HarmonyOS's Particle system with configurable:- Emission rates
- Particle trajectories
- Color transitions
- Scaling effects
Gesture Interactions:
Features spring-based animations withcurves.springCurve
for smooth category selection feedback.-
Responsive Layout:
Implements adaptive UI using:- Percentage-based dimensions
- Flexbox layout (Row/Column)
- Stack positioning
- Dynamic translation transforms
Result Visualization:
Uses ActionSheet component to display detailed score breakdown with scrollable history.
Development Considerations
-
Performance Optimization:
- Animation frame synchronization
- Object pooling for particle effects
- Memoization of static resources
Localization Support:
The architecture supports easy translation through centralized string management in class definitions.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.