动画优化的实践总结

本文总结了动画优化的实践,包括帧动画、补间动画和属性动画的实现与优化。重点介绍了属性动画的优化策略,如使用ValueAnimator、ObjectAnimator和animatorSet,以及关键帧的运用。最后提出了通过自定义View和Canvas结合动画对象,以实现最佳性能的渲染优化方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动画类型

  1. 帧动画:通过序列帧实现,间隔一段时间播放一张图片。实现简单,但是性能最差
  2. 补间动画:输入动画类型(透明度,大小,移动,旋转),开始参数和结束参数。通过插值器控制变化速度。实现相对简单,但是并没有真正改变view的所在位置,只是显示变化。
  3. 属性动画:ValueAnimator,ObjectAnimator和animatorSet。动画优化的核心,见后续详情

帧动画优化

实现

动画xml文件:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="200" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="300" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="400" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="600" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="700" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="800" />

</animation-list>

调用

        imageView.setImageResource(R.drawable.anim_test);
        AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
        animationDrawable.start();

关注点:

  • oneshot控制是否循环,建议不用试hanlder控制循环
  • 每帧的时间都可以不一样。可以用于控制停顿
  • 代码动态添加帧:AnimationDrawable.addFrame(Drawable,time)
  • 所有的view的绘制,都需要获取焦点才能成功(详见window机制)。所以需要调用
    animationDrawable.start();才能执行动画

优化

帧动画实际上是很难优化的,因为固定要读取bitmap。主要有以下两种优化方式:

  • 内存优化:控制bitmap的总数,不使用的bitmap主动回收了,但是这样会导致性能的下降。适合与低内存的手机;另一方面,动画人员给的序列帧是通过工具生成的,有可能生成相同的图片。我们可以复用他们,比如上下左右对称的图片,旋转一周,肯定有存在相同的4张图片。
  • 读取优化:提前读取一些图片。

补间动画

类型:直接引用他人总结的

实现:

  1. 在xml中配置,然后通过 AnimationUtils.loadAnimation(this, R.anim.view_animation)加载
  2. 代码中设置起始参数,结束参数和插值器

详情参考:https://www.jianshu.com/p/733532041f46

个人认为关于补间动画的实现已经写的很详细了。

 

关注点

  1. 补间动画适合于效果单一的动画,比如单纯的平移,旋转等
  2. 如果效果复杂。建议修改为属性动画再优化,因为每个类型,每个view就需要一个相应动画。这样就会出现很多的动画实例

属性动画

  1. ValueAnimator:值动画,实际上只是根据时间规律生成一系列数值(int,float,alpha,argb等),然后我们就可以把这些值设置给相应的view操作。让view动起来
  2. ObjectAnimator:对象动画。是ValueAnimator的实现类之一,需要绑定view对象,通过set/get方法自动把生成的一系列数值设置给相应的参数。set/get方法支持原有的方法,也支持自定义。
  3. animatorSet:动画组合。用户组合多个属性动画,让他们同时进行或者排序进行。比如多个动画同时进行,通过animatorSet就可以一时间产生多种数值,提高效率。原来的方式为各自的动画各自产生序列。

适用场景:

ObjectAnimator适用于只有一个view。如果多个view,为了更好的性能,则建议适用ValueAnimator

ValueAnimator使用相对麻烦一些,适合于多view

优化

原则:尽量减少动画对象

居于这个原则有以下优化方法:

  1. 使用animatorSet:通过set统一控制的动画的执行和顺序,但是animatorSet并不支持循环。如果非要循环,建议外部加一层handle,因为动画有可能被中断。
  2. 对于单view,多效果使用PropertyValuesHolder结合ObjectAnimator。可以只用一个动画对象实现多个效果。以下实现消失动画(逐渐变小,逐渐透明直至完全消失)
            PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 1,0);
            PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1,0);
    
            ObjectAnimator.ofPropertyValuesHolder(this, scaleValuesHolder, alphaValuesHolder).start();
  3. 对于多view,多效果使用PropertyValuesHolder结合ValueAnimator。可以只用一个动画对象实现多个view,多个效果。以下实现两个view的消失动画(逐渐变小,逐渐透明直至完全消失)
     final TextView textView1 = findViewById(R.id.tv_test);
            final TextView textView2 = findViewById(R.id.tv_test2);
    
            PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 1,0);
            PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1,0);
    
            ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleValuesHolder,alphaValuesHolder);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    textView1.setAlpha((float)animation.getAnimatedValue("alpha"));
                    textView1.setScaleX((float)animation.getAnimatedValue("scale"));
                    textView1.setScaleY((float)animation.getAnimatedValue("scale"));
    
                    textView2.setAlpha((float)animation.getAnimatedValue("alpha"));
                    textView2.setScaleX((float)animation.getAnimatedValue("scale"));
                    textView2.setScaleY((float)animation.getAnimatedValue("scale"));
                }
            });

     

  4. 如果动画更复杂,比如先执行scale(500ms),动画开始后100ms执行透明度动画(400ms),然后停顿1.5s再循环。这样的效果以上的方式都无法通过一个动画实现。可以使用keyframe。
            final TextView textView1 = findViewById(R.id.tv_test);
            Keyframe scaleKf0 = Keyframe.ofFloat(0f, 0);
            Keyframe scaleKf1 = Keyframe.ofFloat(0.25f, 1);
            Keyframe scaleKf2 = Keyframe.ofFloat(1f, 1);
            PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofKeyframe("scale", scaleKf0, scaleKf1, scaleKf2);
    
            Keyframe alphaKf0 = Keyframe.ofFloat(0f, 0);
            Keyframe alphaKf1 = Keyframe.ofFloat(0.05f, 0);
            Keyframe alphaKf2 = Keyframe.ofFloat(0.25f, 1);
            Keyframe alphaKf3 = Keyframe.ofFloat(1f, 1);
            PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofKeyframe("alpha",alphaKf0, alphaKf1, alphaKf2, alphaKf3);
    
            ObjectAnimator.ofPropertyValuesHolder(textView1,scaleValuesHolder,alphaValuesHolder);

    keyframe第一个参数为进度百分比(受插值器控制,如果不是LinearInterpolator,可能很快就到达一半了),第二参数为value。上一帧到下一帧如果不一样可以实现动画,如果一样不处理。还可以独立设置每一个关键帧的插值器。同样,单view使用ObjectAnimator,多view使用valueAnimator。再复杂的动画都可以通过keyframe用最少的动画实现效果。

最终篇-渲染优化

以上的优化思路还停留在不改变view渲染的。有几个动画元素就用多少个view。只是减少了动画对象。但是根据view的渲染机制,并没有优化多少,就是生成插值的内存和性能优化了。效果并不是很好。

以下实践总结的优化:

  • 通过canvas,Paint画图/画圆/画矩形等等,结合valueAnimator,改变值以改变位置,大小或者形状。调用invalidate刷新view。
  • 通过canvas,Paint画图/画圆/画矩形等等,结合ObjectAnimator,提供相应的set/get方法。

该方式使用较为复杂,可以结合以上的所以情况,无论多么复杂,都可以用一个view,最少的动画对象实现。需要深刻了解自定义view,view渲染和动画机制。将三者结合,实现最佳的性能。

还有很多细节,set/get方法不能混淆。使用keyframe时,如果效果没有发现变化,就不用调用invalidate

以下是实现两个圆,同时平移和缩放。

package com.example.admin.myapplication;

import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class MyAnimView extends View {
    private Paint mPaint;

    private float mX;
    private float mY;
    private float mRadius;

    public MyAnimView(Context context) {
        super(context);
        init();
    }


    public MyAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
    }

    public void startAnim() {
        PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 0, 100);
        PropertyValuesHolder moveValuesHolder = PropertyValuesHolder.ofFloat("move", 0, 200);

        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleValuesHolder, moveValuesHolder);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRadius = (float) animation.getAnimatedValue("scale");

                mX = (float) animation.getAnimatedValue("move");
                mY = (float) animation.getAnimatedValue("move");
                invalidate();
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.start();
    }

    @Override
    public void onDraw(Canvas canvas) {
        canvas.drawCircle(mX, mY, mRadius, mPaint);
        canvas.drawCircle(mX + 100, mY + 100, mRadius, mPaint);
    }
}

总结

场景实现方式说明
时间紧张,没追求帧动画实现简单,性能极差
一个view,简单的动效

补间动画

实现简单,性能一半,不能改变点击响应区域
一个view,简单的动效,需要改变点击响应区域ObjectAnimator实现较简单,性能一般
一个view,多个动效ObjectAnimator结合PropertyValuesHolder实现难度一般,性能优秀
多个view,多个动效valueAnimator结合PropertyValuesHolder实现难度一般,性能优秀
一个view,多个动效,但是启动时间,结束时间不一样ObjectAnimator,PropertyValuesHolder结合keyframe实现难度较难,性能很优秀
多个view,多个动效,但是启动时间,结束时间不一样valueAnimator,PropertyValuesHolder结合keyframe实现难度较难,性能很优秀
复杂的动画的最佳性能优化canvas,paint结合valueAnimator或者PropertyValuesHolder实现最难,性能最佳

如果能用简单的动画实现就简单实现,追求性能的时候再考虑复杂的实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值