English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Implementación de la función de animación de marcar en Android personalizada

Primero, muestre el efecto gráfico

Animación

Imagen estática

1. Mirar atrás

【Android Custom View: Una animación de verificación delicada】En el artículo anterior, ya hemos implementado básicamente el efecto del controlador, pero... pero... después de tres o cuatro días, al mirar de nuevo el código que escribí, aunque la idea sigue presente, parte del código aún no se puede entender de inmediato...

¡Dios mío, esto debe ser reconstruido de inmediato! Afortunadamente, un amigo simple ChangQin lo imitó y lo escribió, después de lo cual pensé que también podría hacerlo de esta manera.

2. Profunda reflexión

Sobre el pensamiento de la dibujo del controlador, puedes ver el artículo anterior, aquí no se analizará. Primero, analicemos algunos problemas persistentes en el artículo anterior, ¿qué lugares necesitan mejoras?

Tomemos Dibujar el progreso del anillo Este paso para ver

//Contador
private int ringCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 if (!isChecked) {
  ...
  return;
 }
 //Dibujar el progreso del círculo, aumentar automáticamente cada vez que se dibuje12La unidad, es decir, el arco ha pasado12Grado
 //Aquí,12La unidad se escribe primero, luego podemos hacer una configuración para lograr la personalización
 ringCounter += 12;
 if (ringCounter >= 360) {
  ringCounter = 360;
 }
 canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
 ...
 //Redibuja forzadamente
 postInvalidate();
}

Aquí, definimos un contador ringCounter, cuando se dibuja, se basa en12Unidad para el autoincremento hasta360, para simular el cambio de progreso.

Piensa con atención

Al cambiar la unidad de autoincremento para controlar el cambio de velocidad de la animación, es difícil ajustarlo a tu satisfacción, en este momento podemos pensar que el control real de la velocidad de ejecución de la animación es controlar el tiempo, si se puedeUsar el tiempo para controlar la velocidad de la animaciónSería mucho más conveniente si la animación se dividiera4Paso de ejecución, si cada paso de la animación se realiza con un contador escrito a mano, se debe definir4Demasiados miembros de la variable solo harán que el código sea más caóticoSi se debe agregar animaciónInterpolador, el contador escrito a mano no puede satisfacer lo que se ve en el análisis anterior,No puedo aceptarlo

3. Cambia, cambia

Entonces, ¿cómo mejorar el problema mencionado anteriormente? La respuesta es utilizar animaciones de atributos personalizadas para resolverlo, por lo que el punto principal de este artículo es utilizar animaciones de atributos para reemplazar el contador escrito a mano, intentando mantener la lógica del código lo más clara posible, especialmente el código en el método onDraw().

Una de las ventajas del uso de animaciones de atributos es que, dada una gama de valores, te ayuda a generar una serie de valores que deseas, y con el interpolador, incluso efectos inesperados. En la próxima página, se llevará a cabo una reconstrucción paso a paso de la parte ejecutada por la animación.

3.1 Dibujar la barra de progreso del círculo

Primero, use ObjectAnimator personalizado para simular el progreso

//ringProgress es el nombre del atributo personalizado, el rango de generación de valores es 0 - 360, es el ángulo de un círculo
ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
//Definir el tiempo de ejecución de la animación, un excelente sustituto para el uso de la unidad de autoaumento anterior para controlar la velocidad de ejecución de la animación
mRingAnimator.setDuration(mRingAnimatorDuration);
//Por ahora no se necesita interpolador
mRingAnimator.setInterpolator(null);

Para personalizar la animación de atributos, también es necesario configurar los setter y getter correspondientes, porque durante la ejecución de la animación, se buscará el setter correspondiente para cambiar el valor correspondiente.

private int getRingProgress() {
 return ringProgress;
}
private void setRingProgress(int ringProgress) {
 //Cuando se ejecuta la animación, se llama a setter
 //Aquí podemos registrar los valores generados por la animación, almacenarlos en variables y usarlos en el momento de ondraw
 this.ringProgress = ringProgress;
 //Recuerda redraw
 postInvalidate();
}

Finalmente, dibujar en onDraw()

//Dibujar el arco de progreso del círculo canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);

3.2 Dibujar la animación de contracción hacia el centro del círculo

Del mismo modo, también creamos una animación de atributos

//Aquí, el atributo personalizado es el radio de contracción del círculo
ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
//Agregar un interpolador de desaceleración
mCircleAnimator.setInterpolator(new DecelerateInterpolator());
mCircleAnimator.setDuration(mCircleAnimatorDuration);

setter/getter también es similar, no se menciona

Finalmente, dibujar en onDraw()

//Dibujar el fondo
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radio : 0, mPaintCircle);
//Cuando se dibuja el círculo de progreso, dibuja el círculo de contracción
if (ringProgress == 360) {
 mPaintCircle.setColor(checkTickColor);
 canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
}

3.3 Dibujar el efecto de gancho, ampliación y rebote

Estos son dos efectos independientes, aquí se ejecutan al mismo tiempo, por lo que los menciono juntos

Primero también es definir la animación de atributos

//gradiente de transparencia del tick
ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
mAlphaAnimator.setDuration(200);
//La última animación de ampliación y rebote cambia el ancho del pincel
//Y el ancho del pincel, el rango de cambio es
//Primero, desde el ancho de inicialización, hasta el n veces el ancho de inicialización, y finalmente vuelve al ancho de inicialización
ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
mScaleAnimator.setInterpolator(null);
mScaleAnimator.setDuration(mScaleAnimatorDuration);
//la animación de marcar y ampliar se ejecuta juntas
AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);

getter/setter

private int getTickAlpha() {
 return 0;
}
private void setTickAlpha(int tickAlpha) {
 //configurar la transparencia, no es necesario guardar variables
 //simplemente configure el valor de transparencia dentro del pincel
 mPaintTick.setAlpha(tickAlpha);
 postInvalidate();
}
private float getRingStrokeWidth() {
 return mPaintRing.getStrokeWidth();
}
private void setRingStrokeWidth(float strokeWidth) {
 //configurar el ancho del pincel, no es necesario guardar variables
 //simplemente configure el ancho del pincel dentro del pincel
 mPaintRing.setStrokeWidth(strokeWidth);
 postInvalidate();
}

finalmente, al igual que en onDraw(), simplemente dibujar

if (circleRadius == 0) {
 canvas.drawLines(mPoints, mPaintTick);
 canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
}

3.4 ejecutar animaciones sucesivamente

para ejecutar múltiples animaciones, se puede usar AnimatorSet, donde playTogether() ejecuta todas juntas, y playSequentially() ejecuta una tras otra, paso a paso

mFinalAnimatorSet = new AnimatorSet();
mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);

finalmente, ejecutar la animación en onDraw()

//aquí se define un identificador que le dice al programa que la animación solo se puede ejecutar una vez
if (!isAnimationRunning) {
 isAnimationRunning = true;
 //ejecutar animación
 mFinalAnimatorSet.start();
}

3.5 cada método debe tener una única responsabilidad

Si coloco el método de definición de animación de atributos en onDraw(), personalmente siento que está muy desordenado, y al mirarlo con más detalle, estos atributos de animación no necesitan cambiar dinámicamente, ¿por qué no los extraer y inicializarlos al principio?

por lo tanto, definiremos el código de animación de atributos y lo pondremos en la inicialización del constructor

public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 ...
 initAnimatorCounter();
}
/**
 * inicializar algunos contadores con ObjectAnimator
 */
private void initAnimatorCounter() {
 //progreso del círculo
 ObjectAnimator mRingAnimator = ObjectAnimator.ofInt(this, "ringProgress", 0, 360);
 ...
 //animación de contracción
 ObjectAnimator mCircleAnimator = ObjectAnimator.ofInt(this, "circleRadius", radius - 5, 0);
 ...
 //gradiente de transparencia del tick
 ObjectAnimator mAlphaAnimator = ObjectAnimator.ofInt(this, "tickAlpha", 0, 255);
 ...
 //La última animación de ampliación y rebote cambia el ancho del pincel
 ObjectAnimator mScaleAnimator = ObjectAnimator.ofFloat(this, "ringStrokeWidth", mPaintRing.getStrokeWidth(), mPaintRing.getStrokeWidth() * SCALE_TIMES, mPaintRing.getStrokeWidth() / SCALE_TIMES);
 ...
 //la animación de marcar y ampliar se ejecuta juntas
 AnimatorSet mAlphaScaleAnimatorSet = new AnimatorSet();
 mAlphaScaleAnimatorSet.playTogether(mAlphaAnimator, mScaleAnimator);
 mFinalAnimatorSet = new AnimatorSet();
 mFinalAnimatorSet.playSequentially(mRingAnimator, mCircleAnimator, mAlphaScaleAnimatorSet);
}

Finalmente, el método onDraw() se encarga solo de la dibujo simple, sin preocuparse por nada más

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 if (!isChecked) {
  canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
  canvas.drawLines(mPoints, mPaintTick);
  return;
 }
 //dibujar un arco de progreso
 canvas.drawArc(mRectF, 90, ringProgress, false, mPaintRing);
 //dibujar un fondo amarillo
 mPaintCircle.setColor(checkBaseColor);
 canvas.drawCircle(centerX, centerY, ringProgress == 360 ? radio : 0, mPaintCircle);
 //Dibujar el círculo blanco de contracción
 if (ringProgress == 360) {
  mPaintCircle.setColor(checkTickColor);
  canvas.drawCircle(centerX, centerY, circleRadius, mPaintCircle);
 }
 //Dibujar ticks y animación de expansión y contracción
 if (circleRadius == 0) {
  canvas.drawLines(mPoints, mPaintTick);
  canvas.drawArc(mRectF, 0, 360, false, mPaintRing);
 }
 //Reemplazo de animación ObjectAnimator del contador
 if (!isAnimationRunning) {
  isAnimationRunning = true;
  mFinalAnimatorSet.start();
 }
}

El efecto final es el mismo, la lógica del código es clara a la vista

Por lo tanto, personalmente creo que, en el desarrollo, es muy útil revisar el código propio a tiempo, ya sea para uno mismo o para la mantenimiento futuro.

That's all~ Gracias por leer, aquí también dejo la dirección de github del proyecto

> Dirección de Github: TickView, una pequeña animación de tick refinadahttps://github.com/ChengangFeng/TickView

Esto es todo lo que he recopilado para ustedes sobre la implementación de la función de animación de ticks personalizados en View, pueden probarlo, si tienen alguna pregunta, pueden discutirla en la sección de comentarios a continuación, gracias por su apoyo a la tutorial de grito.

Te gustará