Princípio e uso da animação de atributos do Android (Kotlin)

O artigo anterior escreveu sobre Exibir animação e animação de quadro. . . . .

Então, este artigo será uma onda de animação de atributos. . . . .

Todos nós devemos saber que a animação da vista altera apenas a posição do desenho da vista, e não altera as propriedades da vista, como as coordenadas dos quatro vértices. Ou seja, você move uma Visualização de sua posição original A para uma nova posição B e permanece em B. Se você clicar na posição B, não haverá resposta ao evento de clique. Por exemplo, está disponível apenas no ponto A. . . .

Portanto, se uma animação tem um movimento de posição, mas também deve lidar com o evento de clique, a animação de visualização não pode atender à demanda. . .

Mas a animação da propriedade   pode compensar essa deficiência de visualização da animação! ! ! ! ! !

 

A animação de atributos foi introduzida pelo Android 3.0. Pode-se dizer que é uma expansão da animação de Visualização. É uma versão aprimorada da animação de Visualização ou pode substituir quase completamente a animação de Visualização, e a animação de atributo pode criar qualquer objeto, não apenas Visão. . . .

Como ele só pode ser usado após a 3.0, se você quiser usar a versão anterior à 3.0, pode usar a biblioteca de código aberto: http://nineoldandroids.com/

A animação de propriedade é na verdade duas classes, ValueAnimator ObjectAnimator 

ValueAnimator  

Defina o valor inicial e o valor final, e há um tempo total. Obtenha o valor excessivo de cada nó de tempo durante este tempo, e use o valor para controlar o alvo da animação.Na verdade, este tipo de cena é pouco utilizado. . . . . . .

ObjectAnimator  :

Este é muito usado, e basicamente é usado para controlar a animação. Você pode usar diretamente o objeto de tarefa como o objeto de animação. . . .

 

============ 【ValueAnimator 】 ============

Deixe-me falar sobre  ValueAnimator

Use: ValueAnimator.ofFloat ()

    /**
     *      ValueAnimator
     * */
    private fun valueAnimator() {
        // 从 0 过渡到 1
        val valueAnimator = ValueAnimator.ofFloat(0F, 1F)
        // 时间是 100毫秒
        valueAnimator.duration = 100
        // 监听进度
        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            override fun onAnimationUpdate(animation: ValueAnimator?) {
                //获取动画进度值,通过这个值,去控制动画目标
                //比如 一个View背景颜色的百分比
                val animatedValue = animation?.animatedValue as Float
                Log.d(TAG, "进度:$animatedValue")
            }
        })
        valueAnimator.start()
    }

Olhe o registro:

A partir dos resultados, podemos ver que esse progresso de transição foi chamado de volta muitas vezes. . . . .

 

Vejamos o código-fonte de ValueAnimator.ofFloat ()

 

Você pode passar vários parâmetros, então:

Se ValueAnimator.ofFloat (0F, 1F) representa a transição de 0 para 1,

 

Se ValueAnimator.ofFloat (0F, 50F, 3F, 100F) representa a transição de 0 para 50, então de 50 para 3 e então de 3 para 100

No código:

    /**
     *      ValueAnimator
     * */
    private fun valueAnimator() {
        // 从 0 过渡到 1
        val valueAnimator = ValueAnimator.ofFloat(0F, 50F,3F,100F)
        // 时间是 100毫秒
        valueAnimator.duration = 1000
        // 监听进度
        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            override fun onAnimationUpdate(animation: ValueAnimator?) {
                //获取动画进度值,通过这个值,去控制动画目标
                //比如 一个View背景颜色的百分比
                val animatedValue = animation?.animatedValue as Float
                Log.d(TAG, "进度:$animatedValue")
            }
        })
        valueAnimator.start()
    }

Veja o resultado:

进度:0.0
进度:0.0
进度:0.09472668
进度:0.40268898
进度:0.9233728
进度:1.655303
进度:2.535273
进度:3.670764
进度:5.009651
进度:6.4521832
进度:8.17451
进度:10.087408
进度:12.056963
进度:14.323733
进度:16.763512
进度:19.211622
进度:21.966991
进度:24.87359
进度:27.73996
进度:30.916098
进度:34.217968
进度:37.43199
进度:40.950714
进度:44.566513
进度:48.049103
进度:48.285706          // 接近50时,开始从 50 往 3F去过度
进度:44.67541
进度:41.22999
进度:37.528618
进度:33.79583
进度:30.263401
进度:26.500002
进度:22.736603
进度:19.204176
进度:15.471386
进度:11.770042
进度:8.324593
进度:4.7142982           // 接近3时,开始从 3 往 100F去过度
进度:6.3578963
进度:13.540961
进度:20.555607
进度:27.381924
进度:33.617188
进度:40.022766
进度:46.184475
进度:51.745235
进度:57.384045
进度:62.423447
进度:67.47879
进度:72.21197
进度:76.36032
进度:80.43042
进度:84.14144
进度:87.29662
进度:90.28128
进度:92.878716
进度:94.96303
进度:96.78872
进度:98.20865
进度:99.17078
进度:99.79256
进度:100.0

Além de ValueAnimator.ofFloat, os mais comumente usados ​​são: ValueAnimator.ofInt ()

O princípio é o mesmo. Não vou mostrar a eles um por um. . . . . .

 

Além disso, também podemos ligar para

método setStartDelay () para definir o tempo de atraso da animação,

setRepeatCount ()  define o número de vezes que a animação é reproduzida em um loop

setRepeatMode () é o modo de reprodução de loop. Os modos de loop incluem RESTART e REVERSE, que significam, respectivamente, replay e reprodução reversa.

Esses métodos são muito simples e não serão explicados em detalhes.
 

 

============ 【ObjectAnimator 】 ==========

ObjectAnimator  é diferente.Você pode animar qualquer objeto, como a cor de fundo da visualização e o valor alfa.

Do código-fonte, ObjectAnimator   herda  ValueAnimator,

Portanto, na análise final, a   animação é operada da maneira que ValueAnimator define o valor. Portanto, ValueAnimator é o núcleo de toda a animação de propriedade.

Assim    , o método ValueAnimator , em  ObjectAnimator   quase todo o uso de. . . . Portanto,  ObjectAnimator   pode substituir quase completamente ValueAnimator   . . . .

Na verdade, o uso de ObjectAnimator    é semelhante, olhe para o código ----

 

Nós olhamos para a transparência (alfa)

private fun objectAnimator() {
        /**
         *      参数1     tv_animator_view 目标View
         *      参数2     需要进行动画的属性 比如:alpha ,这个参数字符乱传会怎样? 后面会说到
         *      参数3...  这里可以传进多个参数,现在代码设置就是:0F, 1F, 0F, 1F
         *                就是从 0(完全透明)过渡到 1 (完全不透明) 再到 0(完全透明) 再到 1 (完全不透明)
         * */
        val objectAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        objectAnimator.duration = 4000
        objectAnimator.start()
    }

OK, execute o código, tv_animator_view é realmente exibido conforme mencionado acima:

Transição de completamente transparente para completamente opaco, depois completamente transparente e depois completamente opaco

aprender por analogia,

 

Olhamos rotação (rotação)

val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view2, "rotation", 0F, -90F,90F,0F)
        rotationAnimator.duration = 10000
        rotationAnimator.start()

Lembre-se do que eu disse no artigo anterior, a configuração de rotação

Números positivos representam o método horário,

Os números negativos representam a abordagem no sentido anti-horário.

Portanto, o efeito da operação acima é: primeiro gire da posição original para 90 graus no sentido anti-horário, depois gire da posição atual para 90 graus no sentido horário e, em seguida, retorne à posição original.

Não vou postar as renderizações de animação em tempo de execução. . . . problema. . .

 

Vejamos a tradução (translationX ou translationY) :

        //获取当前 View X方向偏量 如果在原始位置,XY轴的偏移量都是0
        val originX = tv_animator_view3.translationX
        //获取 View 的宽度,返回值单位 px像素
        val width = tv_animator_view3.width
        /**
         *          参数1     tv_animator_view3 目标View
         *          参数2     translationX X轴的平移 正为往右  负数为左,单位为像素
         *          参数3     这里可以传进多个参数,
         *                    运行结果:从原始位置 往左移动 当前View的宽度,然后再回到原始位置
         * */
        val translationAnimator =
            ObjectAnimator.ofFloat(tv_animator_view3, "translationX", originX, width.toFloat(), originX)
        translationAnimator.duration = 10000
        translationAnimator.start()

O resultado da operação é o que escrevi no comentário: mova a largura da View atual da posição original para a esquerda e, em seguida, retorne à posição original

Observe as unidades no método:

translationX a tradução do eixo X é positiva para a direita, negativa para a esquerda, a unidade é o pixel

translaçãoY A translação do eixo Y é para baixo, o negativo é para cima, a unidade é o pixel

 

Vejamos em escala (scaleX ou scaleY) :

        val translationAnimator =
            ObjectAnimator.ofFloat(tv_animator_view3, "scaleX", 0F, 1F)
        translationAnimator.duration = 10000
        translationAnimator.start()

Olhando para o código, você pode ver que os inteligentes estão ampliando de 0 a 1 (tamanho original) na direção do eixo X.

Se você mudar para  scaleY  , o zoom será feito na direção vertical. . . . .

 

Tão longe. As quatro animações mais comumente usadas (alfa, rotação, translaçãoX e escalaY) de animação de atributo são mostradas.

Mas assim como a pergunta do topo, o segundo parâmetro do método, o que mais pode ser preenchido, eu responderei para você: qualquer caractere.

você está brincando comigo? Não, eu não estou brincando.

Ao projetar o Android, não há limite para apenas View, que pode ser qualquer subclasse de Object.

Portanto, se você passar abd em ObjectAnimator.ofFloat (tv_animator_view3, " abd ", 0F, 1F), não haverá efeito de animação nesta Visualização.

Claro, não vai ser carsh ...

Você pode entender   desta forma. Depois que um "scaleX" é transmitido, a animação continuará a procurar o destino enquanto a animação está em andamento. O destino atualmente transmitido tem o método setScaleX () .

Bata no quadro-negro! Nota! ! É o método correspondente 

Se houver, ele chamará esse método de acordo com o progresso. Caso contrário, não será processado.

E o que é passado no início é TextView, que tem um método setScaleX () , e a animação vai alterá-lo.

Mas se o que eu passo for " abd ", TextView não tem nenhum método setAbd (), então não mudará nada. . . . Sem erro

O método setScaleX () de TextView está,na verdade, em sua classe pai View. O código-fonte é o seguinte:

 

Então, ele chama isso de setScaleX () of  View

Por analogia, você que é inteligente também vai pensar que na Visão definitivamente haverá:

Os métodos setRotation (), getRotation (), setTranslationX (), getTranslationX (), setScaleY (), getScaleY (). . . . .

 

Quanto a como o mecanismo ObjectAnimator encontra o método set () correspondente. . . Na verdade, este é o caso, ObjectAnimator é dependente do thread atual Looper, se o thread atual não tiver Looper, um erro será relatado. Do rastreamento ObjectAnimator.start () ao método ValueAnimator.star () final pode ser visto.

Quanto a como criar o Looper atual, não é o conteúdo deste capítulo. Os leitores vão à aula por conta própria. . . . .

Na verdade, o código-fonte foi seguido até o animateValue final (fração flutuante),

Existe um trecho de código chamado: mValues ​​[i] .calculateValue (fração);  este código é usado para calcular o valor do atributo de cada quadro

 

Ok, vamos dar uma olhada em como a animação de propriedade obtém os métodos get () e set (). Isso é o que mais nos preocupa. . . .

O método get () é obtido quando não somos inicializados. Em  PropertyValuesHolder

Obtido por reflexão

 

Então, que tal set ()  ? Na verdade, ele também está em  PropertyValuesHolder  . Porque criamos o segundo parâmetro passado por ObjectAnimator,

Ele é armazenado nesta classe, portanto,  get e set são obtidos por meio de  PropertyValuesHolder  .

O rastreamento do código-fonte acima é apenas uma interceptação da parte principal, o leitor encontrará o caminho de rastreamento específico por si mesmo ,,, haha

 

 

============ [ Agência intermediária de animação ] ==================

Ok, vamos fazer uma demonstração para verificar o método de animação de propriedade  - propriedade

 

Primeiro criamos uma classe AnimationTemp

class AnimationTemp {

    private val TAG = "AnimationTemp"


    fun setAbcint(abcInt: Int) {
        Log.d(TAG, "调用 setAbcInt() abcInt :  $abcInt ")
    }

    fun getAbcint(): Int {
        Log.d(TAG, "调用 getAbcInt() ")
        return 100
    }

}

 

Então, de acordo com o princípio da animação de atributos, o objeto de animação pode ser qualquer objeto, bom. Usamos este AnimationTemp como o objeto de animação

        /**
         *          参数1   以 AnimationTemp 作为 动画对象
         *          参数2   改变 abcint 这个属性,
         *                  对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
         *          参数3   起始值
         *          参数4   结束值
         * */
        val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 0, 10)
        ofInt.duration = 100
        ofInt.start()

 

Veja os resultados da corrida:

Ok, é óbvio. . . . . Continue chamando  setAbcint (abcInt: Int)   para imprimir de 0 a 10

 

Em resumo, analise o princípio:

1> ObjectAnimator.onInt () cria uma animação com um gradiente Int. O objeto de animação é uma instância de AnimationTemp. A propriedade que precisa ser alterada é chamada: abcint

2> Quando a animação está em andamento, o sistema tenta alterar o abcint do  objeto de animação  , então o correspondente

        O sistema irá procurar por um método chamado: setAbcint (abcInt: Int) no objeto de animação , preste atenção ao tipo de parâmetros de entrada  do método e ao caso do nome do método

3> Então, ao longo do ciclo de animação, chame  setAbcint (abcInt: Int)   para alterar o valor da propriedade da animação de destino

 

Neste momento, você pode ver que nosso getAbcint () não é chamado, na verdade o código-fonte acima foi analisado, ou seja, ele será chamado quando não tivermos nenhum valor de inicialização.

Ok, vamos mudar o código:

 Ao criar uma animação, passe o valor final (se você passar apenas um Int, o sistema padroniza esse valor como o valor final)

        /**
         *          参数1   以 AnimationTemp 作为 动画对象
         *          参数2   改变 abcint 这个属性,
         *                  对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
         *          参数3   结束值
         * */
        val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 10)
        ofInt.duration = 100
        ofInt.start()

Então, AnimationTemp permanece inalterado

class AnimationTemp {

    private val TAG = "AnimationTemp"


    fun setAbcint(abcInt: Int) {
        Log.d(TAG, "调用 setAbcInt() abcInt :  $abcInt ")
    }

    fun getAbcint(): Int {
        Log.d(TAG, "调用 getAbcInt() ")
        return 100
    }
}

 

Veja os resultados:

Ok, é óbvio. Verifique a análise do código-fonte.

Quando o valor inicial da animação não é inicializado, o método get () da propriedade correspondente será encontrado no destino da animação como o valor inicial.

Aqui, retornamos 100 como o valor inicial e, em seguida, o valor final é 10

Então imprima de 100 a 10

Então, neste ponto, todos sabem basicamente o princípio da animação de atributos. Todos entendem porque o alvo da animação de atributos pode ser qualquer alvo! ! ! !

============ [ Animação de combinação ] ==================

Termine de falar sobre o princípio. Finalmente, vamos falar sobre várias animações juntos. . Animação de combinação. . .

AnimatorSet    usa esta classe. Você pode colocar um ou mais ValueAnimator e ObjectAnimator para jogar

Método principal:

play ()                            Tocar um single

playSequentially ()        reproduz sequencialmente, um após o outro

playTogether ()              reproduz todas as animações juntas

 

after (Animator animIn)             insere a animação existente na animação de entrada (ex: animIn ), execute
after (long delay)                      para atrasar  a animação existente por milissegundos especificados e execute
antes (Animator animIn)         para inserir a animação existente na animação de entrada (Por exemplo: animIn ) A execução anterior de
with (Animator anim)  executará               a animação existente e a animação passada (ou seja: animIn ) ao mesmo tempo

Demo1 ---- depois de

Ok, vamos executar o código:

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).after(rotationAnimator)
        animatorSet.start()

 

O efeito de execução é: rotationAnimator é executado primeiro, alphaAnimator é executado

 【Demo2 ---- com

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).with(rotationAnimator)
        animatorSet.start()

 

O efeito de execução é: rotationAnimator e alphaAnimator estão executando ao mesmo tempo

 

 【Demo3 ---- antes de

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).before(rotationAnimator)
        animatorSet.start()

O efeito de execução é: alphaAnimator é executado primeiro, e rotationAnimator é executado depois

 

Demo4 - depois (longo)

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).before(rotationAnimator).after(3000)
        animatorSet.start()

O efeito de execução é: atrasar 3 segundos, alphaAnimator executa primeiro, rotationAnimator executa depois

 

Demo5 ----  playTogether

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.playTogether(alphaAnimator, rotationAnimator)
        animatorSet.start()

O efeito de execução é: rotationAnimator e alphaAnimator estão executando ao mesmo tempo

 

Demo5 ----  playSequentially

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.playSequentially(alphaAnimator, rotationAnimator)
        animatorSet.start()

O efeito de execução é: alphaAnimator é executado primeiro, e rotationAnimator é executado depois

 

==================== [ Monitoramento de animação ] =================

Na verdade é um método

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.addListener(object : Animator.AnimatorListener{
            override fun onAnimationRepeat(animation: Animator?) {
                //重复
            }

            override fun onAnimationEnd(animation: Animator?) {
                //结束
            }

            override fun onAnimationCancel(animation: Animator?) {
                //取消
            }

            override fun onAnimationStart(animation: Animator?) {
                //开始
            }

        })
        animatorSet.playSequentially(alphaAnimator, rotationAnimator)
        animatorSet.start()

 

Na verdade, você não sabe se está confiante, você descobriu que a animação do atributo não encontrou setFillAfter () para definir se a animação é restaurada ao estado inicial.

Bem, podemos monitorar o final da animação e, em seguida, definir o estado de destino quando ela terminar

public class ResetAnimatorListenerAdapter extends AnimatorListenerAdapter {

    View animatorView;

    public ResetAnimatorListenerAdapter(View animatorView) {
        this.animatorView = animatorView;
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        //重置 animatorView 的状态
        animatorView.setTranslationY(0);
        animatorView.setAlpha(0.0f);
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        super.onAnimationCancel(animation);
        
    }
    
}

 

 

O acima mencionado que o código Chase define dinamicamente a animação, na verdade, ele também pode ser definido através de xml, sem falar nas especificidades. Como não é recomendado usar xml, ele está relativamente morto e não pode ser alterado.

 

Finalmente, as ressalvas da animação

1: Impeça OOM e evite usar animação de quadro com mais fotos. . .

2: Para evitar vazamentos de memória, pare a animação quando a visualização sair do invisível

3: O atributo de animação é usado acima do Android3.0, inferior a esta versão, você pode usar a biblioteca compatível

4: Visualizar animação, quando a visualização é movida, a configuração View.GONE às vezes se torna inválida.Neste momento, basta chamar View.clearAnimation () para limpar a animação.

5: Quando a animação está em andamento, você pode ativar a aceleração de hardware para melhorar a fluência.

 

Não há problema com o código acima, por favor deixe uma mensagem para corrigir quaisquer problemas, obrigado

Acho que você gosta

Origin blog.csdn.net/Leo_Liang_jie/article/details/90812632
Recomendado
Clasificación