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

Implementación de un indicador de progreso en forma de péndulo personalizado en Android View

He visto un componente IOS PendulumView en línea, que realiza el efecto de animación de péndulo. Debido a que el barra de progreso nativa no es atractiva, quiero personalizar la vista para lograr este efecto y también puede ser utilizado para la barra de progreso de la página de carga en el futuro. 

Sin más preámbulos, aquí está el efecto gráfico

 

El borde negro inferior es accidentalmente grabado durante la grabación y puede ser ignorado. 

Dado que es una vista personalizada, seguiremos el proceso estándar. Primer paso: atributos personalizados 

Atributos personalizados 

Crear un archivo de atributos 

En el proyecto Android res->values/directory para crear un archivo attrs.xml, el contenido del archivo es el siguiente:

 <?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="PendulumView"
  <attr name="globeNum" format="integer"/>
  <attr name="globeColor" format="color"/>
  <attr name="globeRadius" format="dimension"/>
  <attr name="swingRadius" format="dimension"/>
 </declare-styleable>
</resources>

Entre ellos, declare-El atributo name del styleable se utiliza para referenciar el archivo de atributos en el código. Generalmente, el nombre del atributo se escribe como el nombre de la clase de la vista personalizada, lo que es bastante intuitivo.

El sistema puede completar muchas variables constantes (int[] array, índices constantes) y otras tareas mediante styleale, lo que simplifica nuestro trabajo de desarrollo. Por ejemplo, el código que utiliza R.styleable.PendulumView_golbeNum se ha generado automáticamente por el sistema. 

El atributo globeNum representa la cantidad de bolas, globeColor representa el color de las bolas, globeRadius representa el radio de las bolas, swingRadius representa el radio de oscilación 

Leer valores de atributos 

En el método de construcción de la vista personalizada, leer valores de atributos a través de TypedArray 

A través de AttributeSet también se pueden obtener valores de atributos, pero si el valor del atributo es de tipo referencia, se obtiene solo el ID, y aún se necesita continuar analizando el ID para obtener el valor real del atributo, mientras que TypedArray nos ayuda a completar el trabajo anterior directamente. 

public PendulumView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //Utilizar TypedArray para leer valores de atributos personalizados
    TypedArray ta = context.getResources().obtainAttributes(attrs, R.styleable.PendulumView);
    int count = ta.getIndexCount();
    for (int i = 0; i < count; i++) {
      int attr = ta.getIndex(i);
      switch (attr) {
        case R.styleable.PendulumView_globeNum:
          mGlobeNum = ta.getInt(attr, 5);
          break;
        case R.styleable.PendulumView_globeRadius:
          mGlobeRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()));
          break;
        case R.styleable.PendulumView_globeColor:
          mGlobeColor = ta.getColor(attr, Color.BLUE);
          break;
        case R.styleable.PendulumView_swingRadius:
          mSwingRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()));
          break;
      }
    }
    ta.recycle(); //Para evitar problemas al leer la próxima vez
    mPaint = new Paint();
    mPaint.setColor(mGlobeColor);
  }

Se sobrescribe el método OnMeasure() 

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    //la altura es el radio de la bola+radio de oscilación
    int height = mGlobeRadius + mSwingRadius;
    //el ancho es2*radio de oscilación+(número de bolas-1)*diámetro de la bola
    int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
    //Si el modo de medición es EXACTLY, se utiliza directamente el valor recomendado, si no es EXACTLY (generalmente se maneja el caso de wrap_content), se utiliza el ancho y la altura calculados por sí mismos
    setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height);
  }

Dentro de
 int height = mGlobeRadius + mSwingRadius;
<pre name="code" class="java">int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
Se utiliza para manejar el modo de medición AT_MOST, generalmente se establece el ancho y la altura de View en wrap_content, en este caso, se calcula el ancho y la altura de View a través del número de bolas, el radio, el radio de oscilación, etc., como se muestra en la siguiente imagen: 

el número de bolas5Por ejemplo, el tamaño de View es la región rectangular roja en la siguiente imagen 

sobrescribir el método onDraw() 

@Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //dibujar las bolas pequeñas excepto las dos bolas a los lados
    for (int i = 0; i < mGlobeNum - 2; i++) {
      canvas.drawCircle(mSwingRadius + (i + 1) * 2 * mGlobeRadius, mSwingRadius, mGlobeRadius, mPaint);
    }
    if (mLeftPoint == null || mRightPoint == null) {
      //inicializar las coordenadas de las dos bolas más a los lados
      mLeftPoint = new Point(mSwingRadius, mSwingRadius);
      mRightPoint = new Point(mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1), mSwingRadius);
      //iniciar la animación de oscilación
      startPendulumAnimation();
    }
    //dibujar las dos bolas a los lados
    canvas.drawCircle(mLeftPoint.x, mLeftPoint.y, mGlobeRadius, mPaint);
    canvas.drawCircle(mRightPoint.x, mRightPoint.y, mGlobeRadius, mPaint);
  }

el método onDraw() es la clave de la View personalizada, en el cuerpo de este método se dibuja el efecto de visualización de la View. El código primero dibuja las bolas pequeñas excepto las dos bolas más a los lados, luego realiza un juicio sobre los valores de las coordenadas de las dos bolas a los lados; si es la primera vez que se dibuja, los valores de las coordenadas están en blanco, se inicializan las coordenadas de las dos bolas pequeñas y se inicia la animación. Finalmente, se dibujan las dos bolas a los lados a través de los valores x, y de mLeftPoint, mRightPoint. 

donde mLeftPoint y mRightPoint son objetos android.graphics.Point, únicamente se utilizan para almacenar la información de las coordenadas x, y de las dos bolas pequeñas a los lados. 

usando animaciones de propiedades 

public void startPendulumAnimation() {
    //usando animaciones de propiedades
    final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //El parámetro fraction se utiliza para representar la completitud del animación, y según él calculamos el valor actual del animación
        double angle = Math.toRadians(90 * fraction);
        int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle));
        int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle));
        Point point = new Point(x, y);
        return point;
      }
    }, new Point(), new Point());
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        Point point = (Point) animation.getAnimatedValue();
        //obtener el valor fraction actual
        float fraction = anim.getAnimatedFraction();
        //juega si fraction primero disminuye y luego aumenta, es decir, si está en estado de moverse hacia arriba
        //cambia la pelota en movimiento cada vez que está a punto de moverse hacia arriba
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //realizar el efecto de animación mediante la continua modificación de los valores de coordenadas x, y de los pequeños globos izquierdo y derecho
        //usar isNext para determinar si debe moverse el pequeño globo izquierdo o el pequeño globo derecho
        if (isNext) {
          //cuando el pequeño globo izquierdo se balancea, el pequeño globo derecho se coloca en la posición inicial
          mRightPoint.x = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1);
          mRightPoint.y = mSwingRadius;
          mLeftPoint.x = mSwingRadius - point.x;
          mLeftPoint.y = mGlobeRadius + point.y;
        } else {
          //cuando el pequeño globo derecho se balancea, el pequeño globo izquierdo se coloca en la posición inicial
          mLeftPoint.x = mSwingRadius;
          mRightPoint.y = mSwingRadius;
          mRightPoint.x = mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + point.x;
          mRightPoint.y = mGlobeRadius + point.y;
        }
        invalidate();
        lastSlope = fraction < mLastFraction;
        mLastFraction = fraction;
      }
    });
    //establecer la reproducción en bucle infinito
    anim.setRepeatCount(ValueAnimator.INFINITE);
    //establecer el modo de repetición en reproducción inversa
    anim.setRepeatMode(ValueAnimator.REVERSE);
    anim.setDuration(200);
    //Configurar el interpolador, controlar la velocidad de cambio de la animación
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();
  }

 donde se utiliza el método ValueAnimator.ofObject para poder operar con objetos Point, lo que es más vívido y específico. Además, se utiliza el objeto TypeEvaluator personalizado a través del método ofObject, lo que se obtiene el valor fraction, que es un valor desde 0-1cambio de decimal. Por lo tanto, los dos últimos parámetros de este método, startValue (new Point()) y endValue (new Point()), no tienen un significado real, también se pueden omitir, aquí se escriben principalmente para facilitar la comprensión. Del mismo modo, también se puede usar ValueAnimator.ofFloat(0f, 1f) método se obtiene un valor desde 0-1cambio de decimal.

     final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //El parámetro fraction se utiliza para representar la completitud del animación, y según él calculamos el valor actual del animación
        double angle = Math.toRadians(90 * fraction);
        int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle));
        int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle));
        Point point = new Point(x, y);
        return point;
      }
    }, new Point(), new Point());

a través de fraction, calculamos el cambio de ángulo de oscilación de la pelota, 0-90 grados

 

mGlobeRadius-mGlobeRadius representa la longitud de la línea verde en la imagen, la trayectoria de oscilación, la trayectoria del centro de la pelota es una circunferencia con (mSwingRadius-mGlobeRadius) como radio de la curva, el valor de X que cambia es (mSwingRadius-mGlobeRadius)*sin(angle), el valor de y que cambia es (mSwingRadius-mGlobeRadius)*cos(angle) 

La coordenada del centro real de la pelota correspondiente es (mSwingRadius-x, mGlobeRadius+y) 

La trayectoria de movimiento de la pelota de la derecha es similar a la de la izquierda, solo la dirección es diferente. La coordenada del centro real de la pelota de la derecha (mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + x, mGlobeRadius+y) 

Es visible que las coordenadas y de los dos lados de la pelota son iguales, solo la coordenada x es diferente. 

        float fraction = anim.getAnimatedFraction();
        //juega si fraction primero disminuye y luego aumenta, es decir, si está en estado de moverse hacia arriba
        //cambia la pelota en movimiento cada vez que está a punto de moverse hacia arriba
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //registra si el fraction anterior ha disminuido continuamente
        lastSlope = fraction < mLastFraction;
        //registra el fraction anterior
        mLastFraction = fraction;

 Estos dos códigos se utilizan para calcular cuándo cambiar la pelota en movimiento, este animación está configurado para repetirse en bucle, y el modo de repetición es en orden inverso, por lo que un ciclo de la animación es el proceso de lanzar la pelota y luego que cae. En este proceso, el valor de fraction primero cambia de 0 a1,luego por}}1Se convierte en 0. Entonces, ¿cuándo es el comienzo de un nuevo ciclo de animación? Es en el momento en que la pelota está a punto de ser lanzada. En este momento, cambiar la pelota en movimiento puede lograr el efecto de animación en el que la pelota de la izquierda cae y la pelota de la derecha se lanza, y luego la pelota de la derecha cae y la pelota de la izquierda se lanza. 

¿Cómo capturar este punto de tiempo? 

Cuando la pelota se lanza, el valor de fraction aumenta constantemente, y cuando la pelota cae, el valor de fraction disminuye constantemente. El momento en que la pelota está a punto de ser lanzada es el momento en que fraction cambia de disminuir constantemente a aumentar constantemente. El código registra si el fraction de la vez anterior disminuye constantemente, luego compara si el fraction de esta vez aumenta constantemente, si las dos condiciones se cumplen, se cambia la pelota en movimiento. 

    anim.setDuration(200);
    //Configurar el interpolador, controlar la velocidad de cambio de la animación
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();

Configurar la duración de la animación de200 milisegundos, los lectores pueden cambiar este valor para modificar la velocidad de oscilación de la pelota.

Configurar el interpolador de animación, ya que el lanzamiento de la pelota es un proceso de desaceleración gradual, y el descenso es un proceso de aceleración gradual, por lo que se utiliza DecelerateInterpolator para lograr el efecto de desaceleración, y el efecto de aceleración en la reproducción inversa. 

¡Lanzar la animación, la barra de progreso personalizada del efecto de péndulo se ha implementado! ¡Rápidamente ejecútela y vea los efectos!

Esto es todo el contenido de este artículo, espero que ayude a su aprendizaje y que todos los estudiantes apoyen el tutorial de grito.

Declaración: el contenido de este artículo se ha obtenido de la red, pertenece al autor original, el contenido se ha contribuido y subido por los usuarios de Internet de manera autónoma. Este sitio no posee los derechos de propiedad, no ha sido editado por humanos y no asume ninguna responsabilidad legal relacionada. Si encuentra contenido sospechoso de copyright, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, por favor reemplace # con @) para denunciar y proporcionar evidencia relevante. Una vez verificada, este sitio eliminará inmediatamente el contenido sospechoso de infracción.

Te gustará