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

Implementação de efeito de barra de progresso pendular personalizada em Android

Vi um componente IOS PendulumView na internet, que realiza o efeito de animação de pêndulo. Como o进度条 nativo não é bonito, pensei em criar um View personalizado para realizar esse efeito, que também pode ser usado como a barra de progresso da página de carregamento no futuro. 

Não há muitas palavras a dizer, vamos mostrar o efeito gráfico primeiro

 

A borda preta na parte inferior é acidentalmente gravada ao gravar, pode ser ignorada. 

Como é um View personalizado, seguimos o processo padrão, o primeiro passo é criar atributos personalizados 

atributos personalizados 

criar um arquivo de atributos 

No projeto Android do res->values/criar um arquivo attrs.xml no diretório, o conteúdo do arquivo é como follows:

 <?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>

Dentre declare-O atributo name do styleable é usado para referenciar o arquivo de atributos no código. Geralmente, o name atribuído é o nome da classe do nosso View personalizado, o que é bastante intuitivo.

Usando styleale, o sistema pode nos completar muitos constantes (array de inteiros, constantes de índice) e outros, simplificando nosso trabalho de desenvolvimento, por exemplo, o código a seguir usa R.styleable.PendulumView_golbeNum, que é gerado automaticamente pelo sistema para nós. 

A propriedade globeNum representa o número de bolas, globeColor representa a cor das bolas, globeRadius representa o raio das bolas, swingRadius representa o raio de oscilação 

Lendo valores de atributo 

Lendo valores de atributo no construtor de view personalizado 

Pode-se obter valores de atributo usando AttributeSet, mas se o valor do atributo for do tipo referência, apenas o ID é obtido, sendo necessário continuar a obter o valor real do atributo através da análise do ID, enquanto o TypedArray nos ajuda a completar o trabalho acima. 

public PendulumView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //Usando TypedArray para ler 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 ao ler novamente
    mPaint = new Paint();
    mPaint.setColor(mGlobeColor);
  }

Sobrescreva o 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);
    //A altura é o raio da bola+raio de oscilação
    int height = mGlobeRadius + mSwingRadius;
    //A largura é2*raio de oscilação+(número de bolas-1)*diâmetro da bola
    int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
    //Se o modo de medição for EXACTLY, use diretamente o valor recomendado; se não for EXACTLY (geralmente para lidar com o caso de wrap_content), use a largura e altura calculadas por você mesmo
    setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) &63; widthSize : width, (heightMode == MeasureSpec.EXACTLY) &63; heightSize : height);
  }

onde
 int height = mGlobeRadius + mSwingRadius;
<pre name="code" class="java">int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
Usado para lidar com o modo de medição AT_MOST, geralmente o tamanho da largura e altura do View é configurado para wrap_content, neste caso, o tamanho da largura e altura do View é calculado com base no número de bolas, o raio, o raio de oscilação, etc., conforme mostrado na figura a seguir: 

com o número de bolas5Por exemplo, o tamanho do View é a área retangular vermelha na figura a seguir 

sobrescrever o método onDraw() 

@Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //desenhar as outras bolas exceto as duas bolas à esquerda e à direita
    for (int i = 0; i < mGlobeNum - 2; i++) {
      canvas.drawCircle(mSwingRadius + (i + 1) * 2 * mGlobeRadius, mSwingRadius, mGlobeRadius, mPaint);
    }
    if (mLeftPoint == null || mRightPoint == null) {
      //inicializar as coordenadas das duas bolas mais à esquerda e à direita
      mLeftPoint = new Point(mSwingRadius, mSwingRadius);
      mRightPoint = new Point(mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1), mSwingRadius);
      //iniciar animação de oscilação
      startPendulumAnimation();
    }
    //desenhar as duas bolas à esquerda e à direita
    canvas.drawCircle(mLeftPoint.x, mLeftPoint.y, mGlobeRadius, mPaint);
    canvas.drawCircle(mRightPoint.x, mRightPoint.y, mGlobeRadius, mPaint);
  }

o método onDraw() é a chave para a View personalizada, onde dentro deste método são desenhados os efeitos visuais da View. O código primeiro desenha todas as bolas exceto as duas bolas mais à esquerda e à direita, e então avalia os valores das coordenadas das duas bolas; se são o primeiro desenho, as coordenadas estão vazias, então inicializa as coordenadas das duas bolas e inicia a animação. Finalmente, através dos valores x, y de mLeftPoint e mRightPoint, desenha as duas bolas à esquerda e à direita. 

onde mLeftPoint e mRightPoint são objetos android.graphics.Point, são usados apenas para armazenar as informações de coordenadas x, y dos dois pequenos bolas na esquerda e direita. 

usando animação de propriedade 

public void startPendulumAnimation() {
    //usando animação de propriedade
    final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //O parâmetro fraction é usado para representar a conclusão do animação, e com base nele calculamos o valor atual do animação
        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();
        //Obter o valor fraction atual
        float fraction = anim.getAnimatedFraction();
        //julgar se a fraction diminuiu primeiro e depois aumentou, ou seja, se está prestes a se balançar para cima
        //alternar a bolinha em cada vez que estiver prestes a se balançar para cima
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //Obter o efeito de animação alterando continuamente os valores x, y das bolas da esquerda e da direita
        //Usar isNext para determinar se deve ser a bola da esquerda ou a bola da direita que se move
        if (isNext) {
          //Quando a bola da esquerda se move, a bola da direita está na posição inicial
          mRightPoint.x = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1);
          mRightPoint.y = mSwingRadius;
          mLeftPoint.x = mSwingRadius - point.x;
          mLeftPoint.y = mGlobeRadius + point.y;
        } else {
          //Quando a bola da direita se move, a bola da esquerda está na posição 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;
      }
    });
    //Definir reprodução em loop infinito
    anim.setRepeatCount(ValueAnimator.INFINITE);
    //Definir o modo de repetição para reprodução invertida
    anim.setRepeatMode(ValueAnimator.REVERSE);
    anim.setDuration(200);
    //Configure o interpolador, controle a taxa de variação da animação
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();
  }

 onde se usa o método ValueAnimator.ofObject para operar sobre o objeto Point, de maneira mais imaginativa e específica. Além disso, ao usar o método ofObject, é usado um objeto TypeEvaluator personalizado, obtendo assim o valor fraction, que é um valor de 0-1a pequena mudança decimal. Portanto, os dois últimos parâmetros startValue (new Point()) e endValue (new Point()) não têm significado real, também podem ser omitidos, aqui estão escritos principalmente para facilitar a compreensão. Da mesma forma, também pode ser usado ValueAnimator.ofFloat(0f, 1f) para obter uma mudança decimal de 0-1a pequena mudança decimal.

     final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //O parâmetro fraction é usado para representar a conclusão do animação, e com base nele calculamos o valor atual do animação
        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());

através de fraction, calculamos o valor de variação do ângulo de balanço da bolinha, 0-90 graus

 

mSwingRadius-mGlobeRadius representa o comprimento da linha verde na imagem, a trajetória balançante da bolinha é uma linha curva com origem em (mSwingRadius-mGlobeRadius)representa uma curva arredondada de raio (mSwingRadius-mGlobeRadius)*sin(angle),o valor y variável é (mSwingRadius-mGlobeRadius)*cos(angle) 

A coordenada central real da bolinha correspondente é (mSwingRadius-x,mGlobeRadius+y) 

A trajetória da bolinha da direita é semelhante à da esquerda, apenas a direção é diferente. A coordenada central real da bolinha da direita (mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + x,mGlobeRadius+y) 

As coordenadas y dos pequenos globos à esquerda e à direita são iguais, apenas as coordenadas x diferem. 

        float fraction = anim.getAnimatedFraction();
        //julgar se a fraction diminuiu primeiro e depois aumentou, ou seja, se está prestes a se balançar para cima
        //alternar a bolinha em cada vez que estiver prestes a se balançar para cima
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //registra se a última fraction foi constantemente decrescendo
        lastSlope = fraction < mLastFraction;
        //registra a última fraction
        mLastFraction = fraction;

 Estes dois códigos são usados para calcular quando alternar a bolinha de movimento, este animação foi configurado para reprodução em loop, e o modo de loop é de reprodução inversa, então um ciclo da animação é o processo de arremessar a bolinha e a bolinha cair. Durante esse processo, o valor de fraction começa de 0 e se torna1,em seguida, por}}1Para 0. Quando é o início de um novo ciclo de animação? É quando a bolinha está prestes a ser lançada. Neste momento, troque a bolinha de movimento para implementar o efeito de animação onde a bolinha esquerda cai após a bolinha direita ser lançada, e a bolinha direita cai após a bolinha esquerda ser lançada. 

Então, como capturar este ponto de tempo? 

Quando a bolinha é lançada, o valor de fraction aumenta continuamente, quando a bolinha cai, o valor de fraction diminui continuamente. O momento em que a bolinha está prestes a ser lançada é o momento em que fraction muda de diminuição contínua para aumento contínuo. O código registra se a última fraction estava diminuindo, então compara se a this fraction está aumentando, se ambos os condições são atendidas, então troca a bolinha de movimento. 

    anim.setDuration(200);
    //Configure o interpolador, controle a taxa de variação da animação
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();

Configure o tempo de duração da animação para200 milissegundos, o leitor pode alterar este valor para modificar a velocidade de balanço da bolinha.

Configure o interpolador de animação, pois a bolinha lançada é um processo de减速, caindo é um processo de aceleração gradual, então use DecelerateInterpolator para implementar o efeito de desaceleração, e no replay inverso para o efeito de aceleração. 

A animação de início, o efeito de balanço personalizado do View de progresso é implementado! Corra para ver os efeitos!

Isso é tudo o conteúdo deste artigo, esperamos que ajude na aprendizagem de todos e que todos apoiem o tutorial Yell.

Declaração: O conteúdo deste artigo é extraído da internet, pertence ao autor original, postado pelos usuários da internet, este site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidade por questões legais. Se você encontrar conteúdo suspeito de violação de direitos autorais, por favor, envie e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar e forneça provas. Aparentemente confirmada, o site será imediatamente removido o conteúdo suspeito de infringência.

Você pode gostar também