【安卓基础重点知识10】帧动画、属性动画

0 阅读8分钟

1. 帧动画

1.1 简介

将一系列的图片按照顺序播放,每一张图片就是动画中的一帧,连续播放后就形成了动画,使用起来比较简单,缺点是当图片过多或者过大是,容易导致 OOM。

1.2 使用

  • 把素材加入drawable,我这里只是为了测试效果,随便加的图片

在drawable中新建from.xml文件,用于将图片进行汇总

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/cat" android:duration="120" />
    <item android:drawable="@drawable/cow" android:duration="120" />
    <item android:drawable="@drawable/dog" android:duration="120" />
    <item android:drawable="@drawable/horse" android:duration="120" />
</animation-list>

在activity中编写相关代码

public class FrameAnimationActivity extends AppCompatActivity implements View.OnClickListener {

    private boolean flag;
    private AnimationDrawable anim;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_frame_animation);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        ConstraintLayout constraintLayout = findViewById(R.id.main);
        anim = (AnimationDrawable) constraintLayout.getBackground();
        constraintLayout.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (flag){
            anim.start();
            flag = false;
        }else {
            anim.stop();
            flag = true;
        }
    }
}

2. 属性动画

2.1 前言

是在 Android 3.0(API 11)才提供动画库。属性动画不仅可以使用自带的 API 来实现最常用的动画,而且通过自定义 View 的方式来做出定制化的动画,相比于 View 动画功能更强大。

补充:

  • View 动画定义

动画变化分为 4 种,平移、缩放、旋转、透明度,通过这 4 种动画其中的一种变换或者组合变换,使视图完成一种渐进式的动画效果。

  • View动画的缺点

    • View 动画只能作用在单一视图上,即只对一个 Button、TextView、或者 ViewGroup,不能作用于非 View 对象的属性,如改变视图颜色属性、自定义 View 时的路径改变的动画效果等,这些通过 View 动画难以实现,通过属性动画可以很好的完成。

    • View 动画的效果只有 4 种,很难完成更复杂的动画效果。

    • View 动画不能改变控件的属性,如通过 View 动画移动一个 Button,移动后点击 Button 显示的位置,并不能触发点击事件,但是点击 Button 原来的位置,可以触发点击事件,可见 View 动画不能改变控件的属性,只是显示的效果改变了而已。

2.2 使用

按照常用的排序:

ViewPropertyAnimator --> ObjectAnimator --> ValueAnimator

当然这是单一动画的选择顺序,按照这个顺序使用起来会很方便,如果是使用 AnimationSet,组合动画中每个动画,一般使用 ObjectAnimator 来构建

2.2.1 ValueAnimator

  • ofInt

方式一:代码编写

public class ValueAnimationActivity extends AppCompatActivity implements ValueAnimator.AnimatorUpdateListener, View.OnClickListener {

    private TextView textView;
    private ValueAnimator valueAnimator;
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_value_animation);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        textView = findViewById(R.id.tv_value_animation);
        //设置变化范围
        valueAnimator = ValueAnimator.ofInt(16,48,60,16);
        valueAnimator.addUpdateListener(this);
        button = findViewById(R.id.btn_ofInt);
        button.setOnClickListener(this);
    }

    //ofInt动画改变大小
    @Override
    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
        int animatedValue = (int) valueAnimator.getAnimatedValue();
        textView.setTextSize(animatedValue);
    }

    @Override
    public void onClick(View v) {
        //设置变化时间
        valueAnimator.setDuration(1000);
        //开始变化
        valueAnimator.start();
    }
}

方式二:使用xml

在res/animato文件夹下创建value_animator.xml

<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="4000"            
    android:fillAfter="false"     //当动画结束后是否保持动画的最终状态
    android:fillBefore="true"     //动画开始前是否保持动画开始时的状态
    android:fillEnabled="true"    //是否启用 fillBefore  fillAfter 属性
    android:repeatCount="1"       //动画重复的次数
    android:repeatMode="restart"  //动画重复时的模式 restart动画从头开始重复reverse动画在重复时倒退播放
    android:valueFrom="16"        //
    android:valueTo="48"
    android:valueType="intType" />

然后加载动画,设置监听 AnimatorUpdateListener

valueAnimatorXml = (ValueAnimator)AnimatorInflater.loadAnimator(this, R.animator.value_animator);
valueAnimatorXml.setTarget(textView);
  • ofObject

对于 ofInt 方法,没有估值器参数,实际上已经具备系统内置的估值器 IntEvaluator,内置的估值器已经实现从开始值到结束值的过渡过程,能够得到不同时刻的 int 值,同理,对于 ofFloat()方法,也内置了 FloatEvaluator。

对于 ofObject() 方法,系统没有提供估值器,因为系统不知道我们我们要传入的对象的类型,所以需要我们自己来实现一个估值器。

  • 自定义一个类 MyPoint,通过 MyPoint 对象来表示控件的位置
public class MyPoint {
    private int x;
    private int y;

    public MyPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}
  • 然后实现一个估值器,通过这个估值器来得到从开始值到结束值之间的不同时刻下的值,其中 fraction 由插值器给出,代表动画的进度, startValue 和 endValue 表示开始值和结束值。
public class PointEvaluator implements TypeEvaluator<MyPoint> {
    @Override
    public MyPoint evaluate(float fraction, MyPoint startValue, MyPoint endValue) {
        int x = (int) (fraction * (endValue.getX() - startValue.getX()) + startValue.getX());
        int y = (int) (fraction * (endValue.getY() - startValue.getY()) + startValue.getY());
        return new MyPoint(x, y);
    }
}
  • 接下来就可以使用自定义的估值器通过 ofObject 来构建 ValueAnimator,这里只给出通过代码实现的方式
valueAnimatorOfObject = ValueAnimator.ofObject(new PointEvaluator(),
        new MyPoint(30,30), new MyPoint(400,400));
valueAnimatorOfObject.addUpdateListener(this);
button_of_object = findViewById(R.id.btn_ofObject);
button_of_object.setOnClickListener(this);

2.2.2 ObjectAnimator

ObjectAnimator.ofFloat(image_object, "rotation", 0, 360.0f)
        .setDuration(4000)
        .start();

这样就开启了动画,不需要像 ValueAnimator 一样设置更新监听,手动赋值并刷新 View。只需要给出需要更改的属性即可

属性作用数值类型
Alpha控制View的透明度float
TranslationX控制X方向的位移float
TranslationY控制Y方向的位移float
ScaleX控制X方向的位移float
ScaleY控制Y方向的位移float
Ration控制以屏幕方向为轴的旋转度数float
RationX控制以X轴为轴的旋转度数float
RationY控制以Y轴为轴的旋转度数float

只要将这些属性参数设置到 ObjectAnimator.ofFloat() 方法中,ObjectAnimator 就会根据属性参数找到对应的属性(前提是 该对象存在这个属性),然后进行自动赋值,实现动画效果。另外,我们还可以通过 ObjectAnimator 改变自定义 的属性,下面就来展示一下自定义属性的改变效果。

2.2.3 ViewPropertyAnimator

直接使用

TextView textView = findViewById(R.id.tv_view_property);
textView.animate()
        .translationYBy(-100)
        .alphaBy(-0.1f)
        .scaleX(1.5f)
        .rotationBy(180)
        .setDuration(4000)
        .start();

带By的表示在原有位置的基础上进行的偏移

2.2.4 AnimationSet

有时我们在改变一个控件或者视图时,可能需要改变多个属性的动画效果,而且不同的属性改变时的先后顺序,也有一定的要求,比如先拉伸,然后改变颜色,最后再旋转,这时就需要 AnimationSet 来完成一系列的动画组合。

AnimatorSet.play(Animator anim)   // 播放当前动画
AnimatorSet.after(long delay)     // 将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim)   // 将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim)  // 将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) // 将现有动画插入到传入的动画之前执行
AnimatorSet.playSequentially      // 各个动画按照顺序执行,前一个执行完,后面的再开始执行
public class AnimationSetActivity extends AppCompatActivity implements View.OnClickListener {

    private ObjectAnimator animator1;
    private ObjectAnimator animator2;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_animation_set);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        ImageView image_animator_set = findViewById(R.id.image_animator_set);
        animator1 = ObjectAnimator.ofFloat(image_animator_set, "scaleX", 1, 2)
                .setDuration(2000);
        animator2 = ObjectAnimator.ofFloat(image_animator_set, "rotation", 0, 450.0f)
                .setDuration(3000);

        findViewById(R.id.btn_animator_set).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(animator1, animator2);
        animatorSet.start();
    }
}

2.2.5 估值器

估值器完成的工作就是给出不同进度下的值

public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

2.2.6 插值器

插值器是来获取进度的,所有的插值器都是实现 TimeInterpolator 接口,随着时间的发展,给出不同时刻属性变化的百分比,这个百分比就是进度,插值器给出这个百分比之后,最后给到估值器,估值器通过这个百分比计算对应时刻的值

当需要改变插值器时,通过 setInterpolator(Interpolator interpolator) 设置 Interpolator, Interpolator 其实就是速度设置器,在参数里填入不同的 Interpolator ,动画就会以不同的速度模型来执行

AccelerateDecelerateInterpolator  // 先加速再减速
LinearInterpolator // 匀速A
ccelerateInterpolator // 加速
DecelerateInterpolator // 持续减速直到 0
AnticipateInterpolator // 回拉一下再进行正常动画轨迹O
vershootInterpolator // 动画会超过目标值一些,然后再弹回来
AnticipateOvershootInterpolator // 上面这两个的结合版:开始前回拉,最后超过一些然后回弹
BounceInterpolator // 在目标值处弹跳
CycleInterpolator // 正弦 / 余弦曲线模型
PathInterpolator // 自定义动画完成度 / 时间完成度曲线。
FastOutLinearInInterpolator // 加速模型,曲线公式是用的贝塞尔曲线
FastOutSlowInInterpolator // 先加速再减速。用的是贝塞尔曲线
LinearOutSlowInInterpolator // 持续减速

2.2.7 动画监听

(1)基类 Animation 中有一个监听 AnimatorListener ,可以监听动画开始、结束、重复、取消时刻,从而来进行一系列操作。ObjectAnimator、ValueAnimator、AnimatorSet 都是继承自 Animation,所以都可以设置该监听。

(2) ValueAnimator 还有另外一个监听 ValueAnimator.AnimatorUpdateListener,在值变化时,通过该监听得到不同时刻的值,从而对对象设置值,改变对象属性。

(3)有时我们可能不需要监听动画个多个时刻,如仅仅需要监听结束时刻,然后执行我们想要执行的一个动作,那对于其他时刻就没必要重写,AnimatorListenerAdapter 就是来满足这个需求的,我们可以根据需要的监听时刻进行重写。

public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
        Animator.AnimatorPauseListener {

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAnimationCancel(Animator animation) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAnimationEnd(Animator animation) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAnimationStart(Animator animation) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAnimationPause(Animator animation) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onAnimationResume(Animator animation) {
    }
}

属性动画详解

本系列【安卓基础重点知识】是刚开始学习android的时候记录的,其中部分内容来自网页,忘记记录来源了,如需添加引用,联系我即可