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