Développement d'une application "Maillet Électronique" sur HarmonyOS

Le "maillet électronique" est une application simulant un maillet de bois traditionnel, permettant aux utilisateurs de le frapper virtuellement sur leurs appareils mobiles pour cultiver la sérénité et accumuler des "mérites électroniques". De nombreux internautes l'utilisent pour gérer leur stress, voire pour "transcedner" leurs supérieurs hiérarchiques, exprimant ainsi leur mécontentement. Les téléchargements de cette application ont explosé, en faisant un phénomène viral sur les réseaux sociaux.

Environnement de Développement Outil de développement : DevEco Studio NEXT Beta1 Build Version: 5.0.3.814 Version API du projet : 12

Analyse des Algorithmes

  1. Modèle Observateur Le modèle observateur est un patron de conception comportemental. Il établit une dépendance unidirectionnelle entre des objets : un objet "sujet" maintient une liste d'objets "observateurs" qui lui sont dépendants. Lorsque l'état du sujet change, il notifie automatiquement tous ses observateurs pour qu'ils se mettent à jour. Dans le code, la classe Cell est marquée comme observateur grâce au décorateur @ObservedV2. Les propriétés dont les changements sont suivis sont annotées avec @Trace.
// Décorateur pour le modèle observateur
@ObservedV2
class Cell {
  value: string = 'Mérite+1'; // Texte affiché par défaut
  @Trace // Suivi des changements de propriété
  opacity: number = 0; // Opacité du texte
  @Trace // Suivi des changements de propriété
  y: number = 0; // Position verticale du texte
}

  1. Effets d'Animation Les effets d'animation sont utilisés pour améliorer l'interactivité et l'attrait visuel des éléments de l'interface utilisateur. Dans ce code, des animations sont appliquées pour créer un effet de mouvement vertical et de changement d'opacité du texte lors de l'interaction avec l'image. La méthode animateToImmediately est utilisée dans la fonction de rappel onClick pour implémenter ces animations.
// Rappel déclenché lors du clic sur l'image
.onClick(() => {
  const currentIndex = this.indexCount % this.count; // Calcul de l'index courant pour l'animation
  this.indexCount++; // Incrémentation du compteur d'index

  // Première animation : initialisation rapide
  animateToImmediately({
    duration: 0, // Durée de 0ms pour un démarrage instantané
    onFinish: () => { // Callback à la fin de la première animation
      // Seconde animation : mouvement et disparition
      animateToImmediately({
        duration: 1000, // Durée de 1000ms pour la seconde animation
      }, () => {
        this.list[currentIndex].y = -200; // Déplacement vertical du texte vers le haut
        this.list[currentIndex].opacity = 0; // Mise à zéro de l'opacité (disparition)
      });
    }
  }, () => {
    // Configuration initiale pour la première animation
    this.list[currentIndex].y = 0; // Réinitialisation de la position verticale
    this.list[currentIndex].opacity = 1; // Rétablissement de l'opacité complète
  });
})

Code Complet

// Décorateur pour le modèle observateur
@ObservedV2
class Cell {
  value: string = 'Mérite+1'; // Texte affiché par défaut
  @Trace // Suivi des changements de propriété
  opacity: number = 0; // Opacité du texte
  @Trace // Suivi des changements de propriété
  y: number = 0; // Position verticale du texte
}

// Composant d'entrée principal
@Entry
@Component
struct Index {
  @State list: Cell[] = []; // Tableau d'objets Cell
  private indexCount: number = 0; // Compteur pour suivre l'index des animations
  private readonly count: number = 10; // Nombre d'éléments dans la liste des Cell
  private readonly imageSrc: string =
    'data:image/png;base64,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';

  // Initialisation du composant avant son apparition
  aboutToAppear(): void {
    for (let i = 0; i < this.count; i++) {
      this.list.push(new Cell()); // Ajout d'un nouvel élément Cell à la liste
    }
  }

  // Méthode pour construire l'interface utilisateur
  build() {
    Column() { // Conteneur principal pour un alignement vertical
      Stack() { // Permet de superposer des éléments
        ForEach(this.list, (item: Cell, _index: number) => { // Boucle pour afficher chaque élément Cell
          Text(item.value) // Affichage du texte de l'élément
            .fontColor(Color.White) // Couleur du texte blanc
            .fontSize('50lpx') // Taille de la police
            .translate({ x: 0, y: `${item.y}lpx` }) // Positionnement vertical basé sur la propriété 'y'
            .opacity(item.opacity) // Opacité de l'élément basée sur la propriété 'opacity'
        })
      }
      .width('300lpx') // Largeur du conteneur Stack
      .height('300lpx') // Hauteur du conteneur Stack
      .align(Alignment.BottomEnd) // Alignement du contenu en bas à droite

      Image(this.imageSrc) // Affichage de l'image
        .width('300lpx') // Largeur de l'image
        .height('300lpx') // Hauteur de l'image
        .objectFit(ImageFit.Contain) // L'image s'adapte à la taille du conteneur
        .clickEffect({ // Effets visuels lors du clic
          scale: 0.5, // Réduction de taille
          level: ClickEffectLevel.LIGHT // Intensité de l'effet
        })
        .onClick(() => { // Gestionnaire d'événement pour le clic sur l'image
          const currentIndex = this.indexCount % this.count; // Calcul de l'index de l'élément à animer
          this.indexCount++; // Incrémentation du compteur global

          // Animation d'apparition : position initiale et opacité pleine
          animateToImmediately({
            duration: 0, // Démarrage immédiat
            onFinish: () => { // À la fin de l'animation d'apparition
              // Animation de disparition : déplacement vers le haut et opacité nulle
              animateToImmediately({
                duration: 1000, // Durée de l'animation de disparition
              }, () => {
                this.list[currentIndex].y = -200; // Déplacement vers le haut
                this.list[currentIndex].opacity = 0; // Disparition
              });
            }
          }, () => {
            // Configuration de l'état initial pour l'animation d'apparition
            this.list[currentIndex].y = 0; // Position d'origine
            this.list[currentIndex].opacity = 1; // Opacité totale
          });
        })
    }
    .height('100%') // Le conteneur prend toute la hauteur disponible
    .width('100%') // Le conteneur prend toute la largeur disponible
    .backgroundColor(Color.Black) // Fond noir pour le conteneur
  }
}

Étiquettes: HarmonyOS DevEco Studio ArkTS Design Patterns animations

Publié le 21 octobre à 07h56