English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
D'abord, montrez les effets visuels
Animation
Image statique
1. Réflexion
[Android View personnalisé : une petite animation de coche soignée] Dans l'article précédent, nous avons基本上实现了控件的效果,但是...但是...经过三四天后,仔细看回自己写的代码,虽然思路还在,但是部分代码还是不能一下子的看得明白...
Mon dieu, cela doit être refondu immédiatement ! Heureusement, un ami simple ChangQin a imité l'écriture de ce contrôle, après avoir regardé, je pense que je peux aussi le réaliser ainsi.
2. Réflexion
à propos de la pensée de dessin du contrôle des widgets, vous pouvez consulter l'article précédent, ici nous ne l'analysons plus. D'abord, analysons les problèmes persistants des widgets dans l'article précédent, quels endroits doivent être améliorés.
Prenez dessiner la progression du cercle Pour voir cette étape
//compteur private int ringCounter = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!isChecked) { ... return; } //dessiner l'arc de progression, chaque dessin est ajouté12unité, c'est-à-dire que l'arc a de nouveau balayé12degré //ici12unité est écrite à l'avance, et après, nous pouvons réaliser une configuration personnalisée ringCounter += 12; si ringCounter >= 360) { ringCounter = 360; } canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing); ... //redessiner强制 postInvalidate(); }
Ici, nous avons défini un compteur ringCounter, lorsque le dessin est effectué, il est basé sur12unité pour l'auto-incrément atteindre360, pour simuler le changement de progression.
Réfléchissez bien
En changeant l'unité d'auto-incrément pour contrôler le changement de la vitesse de l'animation, ce qui est difficile à ajuster à votre satisfaction. À ce moment-là, nous pouvons penser que le contrôle de la vitesse d'exécution de l'animation rapide ou lente est en fait de contrôler le temps. Si vous pouvezen utilisant le temps pour contrôler la vitesse de l'animationça serait beaucoup plus pratique. L'animation est divisée4étapes d'exécution, si chaque étape de l'animation doit être réalisée à l'aide d'un compteur manuel, il faudra définir4variables membres ou plusTrop de variables membres rendront le code encore plus confusSi l'animation doit être ajoutéeInterpolateur, le compteur manuel écrit à la main ne peut tout simplement pas répondre aux analyses mentionnées ci-dessusJe ne peux pas l'accepter
3. Modifiez, modifiez, modifiez
Alors, comment améliorer le problème mentionné précédemment ? La réponse est d'utiliser l'animation des propriétés personnalisée pour le résoudre. Donc, l'objet principal de cet article est d'utiliser l'animation des propriétés pour remplacer le compteur manuel, en veillant à ce que la logique du code soit aussi claire que possible, en particulier dans la méthode onDraw().
L'un des avantages de l'animation des propriétés est qu'elle génère automatiquement une série de valeurs que vous souhaitez, en fonction d'une plage de valeurs donnée, et en combinaison avec un interpolateur, elle peut produire des effets inattendus. Dans le prochain paragraphe, nous allons réorganiser étape par étape les parties de l'animation.
3.1 Dessiner la barre de progression circulaire
Tout d'abord, utiliser ObjectAnimator personnalisé pour simuler la progression
//ringProgress est le nom de l'attribut personnalisé, la gamme de valeurs générées est de 0 - 360, c'est-à-dire l'angle d'un cercle ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360); //Définir le temps d'exécution de l'animation, ce qui est un bon substitut pour le contrôle de la vitesse de l'animation par unité auto-incrementée auparavant mRingAnimator.setDuration(mRingAnimatorDuration); //Pour le moment, il n'est pas nécessaire d'utiliser un interpolateur mRingAnimator.setInterpolator(null);
Pour la création d'animations des propriétés personnalisées, il faut configurer les setter et getter correspondants, car pendant l'exécution de l'animation, il trouvera le setter pour modifier la valeur correspondante.
private int getRingProgress() { return ringProgress; } private void setRingProgress(int ringProgress) { //Lors de l'exécution de l'animation, il appelle le setter //Ici, nous pouvons enregistrer les valeurs générées par l'animation, les stocker dans des variables et les utiliser dans onDraw this.ringProgress = ringProgress; //N'oubliez pas de redessiner postInvalidate(); }
Dessiner finalement dans onDraw()
//Dessiner l'arc de progression circulaire canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
3.2 Dessiner l'animation de contraction vers le centre du cercle
De même, créer une animation des propriétés
//L'attribut personnalisé ici est le rayon de contraction du cercle ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); //Ajouter un interpolateur de décélération mCircleAnimator.setInterpolator(new DecelerateInterpolator()); mCircleAnimator.setDuration(mCircleAnimatorDuration);
setter/Le getter est similaire et ne sera pas mentionné
Dessiner finalement dans onDraw()
//Dessiner le fond mPaintCircle.setColor(checkBaseColor); canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle); //Une fois que le cercle de progression est dessiné, dessiner un cercle de contraction if (ringProgress == 360) { mPaintCircle.setColor(checkTickColor); canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle); }
3.3 Dessiner l'effet de poulie et d'agrandissement puis de rebond
Ce sont deux effets indépendants, ici ils sont exécutés en même temps, je les ai donc regroupés
Définir d'abord l'animation des propriétés
//transparence progressive de l'animation de coche ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); mAlphaAnimator.setDuration(200); //La dernière animation d'agrandissement et de rebond, modifie la largeur du pinceau pour réaliser //Quant à la largeur du pinceau, la gamme des changements est //Commencer par la largeur d'initialisation, puis multiplier par n la largeur d'initialisation, et finalement revenir à la largeur d'initialisation ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); mScaleAnimator.setInterpolator(null); mScaleAnimator.setDuration(mScaleAnimatorDuration); //l'animation de coche et d'agrandissement rebondissent ensemble AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
getter/setter
private int getTickAlpha() { return 0; } private void setTickAlpha(int tickAlpha) { //Définir la transparence, on peut ne pas enregistrer la variable //Définir directement la valeur de transparence dans le pinceau mPaintTick.setAlpha(tickAlpha); postInvalidate(); } private float getRingStrokeWidth() { return mPaintRing.getStrokeWidth(); } private void setRingStrokeWidth(float strokeWidth) { //Définir la largeur du pinceau, on peut ne pas enregistrer la variable //Définir directement la largeur du pinceau dans le pinceau mPaintRing.setStrokeWidth(strokeWidth); postInvalidate(); }
Finalement, comme précédemment, dessiner dans onDraw()
if (circleRadius == 0) { canvas.drawLines(mPoints, mPaintTick); canvas.drawArc(mRectF, 0, 360, false, mPaintRing); }
3.4 Exécuter les animations en ordre
Pour exécuter plusieurs animations, on peut utiliser AnimatorSet, où playTogether() exécute ensemble, et playSequentially() exécute une après l'autre, étape par étape
mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
Enfin, exécuter l'animation dans onDraw()
//Ici, un identificateur est défini pour informer le programme que l'animation ne peut être exécutée qu'une seule fois if (!isAnimationRunning) { isAnimationRunning = true; //Exécuter l'animation mFinalAnimatorSet.start(); }
3.5 Chaque méthode devrait avoir une seule responsabilité
Si je mettais la méthode de définition des animations d'attributs dans onDraw(), je trouve ça un peu désordonné, et en regardant de plus près, ces animations d'attributs ne nécessitent pas de changements dynamiques, pourquoi ne pas les extraire et les initialiser dès le départ ?
donc, nous allons extraire le code des animations d'attributs et les initialiser dans le constructeur
public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ... initAnimatorCounter(); }
/** * Initialiser quelques compteurs avec ObjectAnimator */ private void initAnimatorCounter() { //progression circulaire ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360); ... //animation de contraction ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0); ... //transparence progressive de l'animation de coche ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255); ... //La dernière animation d'agrandissement et de rebond, modifie la largeur du pinceau pour réaliser ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES); ... //l'animation de coche et d'agrandissement rebondissent ensemble AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet(); mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator); mFinalAnimatorSet = new AnimatorSet(); mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet); }
Enfin, la méthode onDraw() est responsable uniquement du dessin simple, sans autre considération
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!isChecked) { canvas.drawArc(mRectF, 90, 360, false, mPaintRing); canvas.drawLines(mPoints, mPaintTick); return; } //dessiner l'arc de cercle de progression canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing); //dessiner un fond jaune mPaintCircle.setColor(checkBaseColor); canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radius : 0, mPaintCircle); //Dessiner un cercle blanc en rétrécissement if (ringProgress == 360) { mPaintCircle.setColor(checkTickColor); canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle); } //Dessiner un coche, ainsi qu'une animation d'agrandissement et de réduction if (circleRadius == 0) { canvas.drawLines(mPoints, mPaintTick); canvas.drawArc(mRectF, 0, 360, false, mPaintRing); } //Animation ObjectAnimator remplace le compteur if (!isAnimationRunning) { isAnimationRunning = true; mFinalAnimatorSet.start(); } }
Le résultat final est le même, la logique du code est claire
Donc, je pense que, dans le développement, il est utile de faire un review de son code à temps, que ce soit pour soi-même ou pour la maintenance future.
C'est tout~ Merci de votre lecture, je vais également vous donner l'adresse GitHub du projet.
> Adresse GitHub : TickView, une petite animation de coche soignéehttps://github.com/ChengangFeng/TickView
C'est tout~ Merci de votre lecture, je vais également vous donner l'adresse GitHub du projet.