Android 属性动画:一文让你完全了解和掌握属性动画用法

在这里插入图片描述

属性动画概述

前面给咱们分析了Android中的帧动画补间动画的特色和用法
Android动画之补间动画用法最全详解
Android 动画之帧动画用法详解java

Android官方在Anrdoid 3.0之后又推出了一种新的动画即属性动画,既然前面的帧动画补间动画能帮助咱们实现大部分的Android动画效果,那么官方为何还要推出这种新的属性动画呢?android

缘由1:web

补间动画做用的对象是View,也就是做用的对象是Android中的控件,如ImageViewButtonTextView等,也能够做用在布局上如LinearLayoutConstraintLayoutRelativeLayout等,可是对于一些不是View的对象,没法对这些对象进行动画操做。好比咱们要对某个控件的某个属性作进行动画操做,如其颜色,这个颜色也能够当作一个对象,但其并非View对象,补间动画就没法实现,属性动画能够对这个颜色值作动画, 能实现一些更加复杂的动画效果。canvas

缘由2:app

补间动画只是改变了View的视觉效果,而不会真正去改变View的属性
在这里插入图片描述
好比咱们对一个图片进行AlphaAnimation,并在动画先后打印其值ide

Log.i("MainActivity","动画开始前mImageView alpha="+mImageView.getAlpha());
animation = new AlphaAnimation(0, 1);
animation.setDuration(2000);
mImageView.startAnimation(animation);
Log.i("MainActivity","动画结束后mImageView alpha="+mImageView.getAlpha());

在这里插入图片描述
从打印的结果能够看出,补间动画并无改变View的属性值,而属性动画不但会帮助咱们实现View动画的一些视觉效果,并且还能改变View的属性值。svg

属性动画用法

一、属性动画都是经过ValueAnimator 类和ObjectAnimator 类来完成,其中ObjectAnimator类是对对象作动画,ValueAnimator 类是对值作动画。
二、PropertyValueHolder类能够同时执行多个动画,AnimatorSetl类能够将多个动画按必定的秩序前后执行。
三、TypeEvaluator估值器和Interpolator 差值器函数

咱们了解了下面6个类的基本用法,就基本完全掌握了属性动画布局

  • ObjectAnimator 对象动画
  • ValueAnimator 值动画
  • PropertyValueHolder 用于同时执行多个动画
  • TypeEvaluator 估值器
  • AnimatorSet 动画集合
  • Interpolator 差值器

对象动画(ObjectAnimator)

ObjectAnimator类是属性动画中很是重要的一个类,能够经过该类对View不只能够实现一些基本的移、旋转、缩放和透明度四种基本变换动画,还能实现一些其余属性值的变换动画。
实现方式既能够经过Java代码,也能够经过XML方式来实现,下面咱们来分别介绍下两种方式基本用法。动画

方法1:Java代码实现对象动画

首先咱们先来看一下ObjectAnimator类最基本的方法

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

方法中第一个参数Object target 的做用对象一般是View,也就是Android中的控件或布局。
方法中第二个参数String propertyName 一般是须要执行动画的属性,具体值以下表所示

属性 值的用法
rotation 以屏幕方向为轴的旋转度数
alpha 透明度
translationX / translationY X/Y方向的位移
scaleX /scaleY X/Y方向的缩放倍数
rotationX / rotationY 以X/Y轴为轴的旋转度数

方法中第三个参数float... values 表示属性的变换范围,该参数能够传多个值。

添加一些代码来看一下效果

ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();

该动画效果表示控件ImageView的透明度在5s内由1变换到0,再由0变回 1。效果以下:
在这里插入图片描述
ObjectAnimator的其余方法使用以下:

ImageView imageView = findViewById(R.id.imageView);
 ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
 animator.setDuration(2000);

 //动画延迟500ms执行
 animator.setStartDelay(500);

 //执行重复次数 +1
 animator.setRepeatCount(3);

 // 设置动画重复播放模式 RESTART -执行完一遍后从新执行
 // REVERSE -执行完一遍后 从末位置往前执行
 animator.setRepeatMode(ValueAnimator.RESTART);

 //监听值变换
 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         Log.i("MainActivity","value:" +animation.getAnimatedValue());
     }
 });
 animator.start();

运行效果:
在这里插入图片描述

onAnimationUpdate回调方法的部分值打印以下
在这里插入图片描述

方法2:XML实现对象动画

在这里插入图片描述

XML实现对象动画
一、在res目录下新建animator文件夹
二、animator文件夹下建立动画XML文件,如animator_alpha.xml
往该xml文件中输入以下代码

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="alpha"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType" />

Java代码中经过加载该xml启动动画

ImageView imageView = findViewById(R.id.imageView);
Animator animator = AnimatorInflater.loadAnimator(Main2Activity.this, R.animator.animator_alpha);
animator.setTarget(imageView);
animator.start();

值动画(ValueAnimator)

值动画经过控制值的变化,以后 手动赋值给对象的属性,从而实现动画。

ValueAnimator的核心方法以下

ValueAnimator ofFloat(float... values) -- 浮点型数值
ValueAnimator  ofInt(int... values) -- 整型数值
ValueAnimator  ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型

下面咱们来添加值动画,在值动画的监听函数里 来获取值得变化,根据值的变化对控件设置相应的属性。这里的属性能够是控件的任意属性

final ImageView imageView = findViewById(R.id.imageView);
 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
 anim.setDuration(5000);
 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         float currentValue = (float) animation.getAnimatedValue();
         Log.d("MainActivity", "cuurent value is " + currentValue);
         imageView.setAlpha(currentValue);
     }
 });
 anim.start();

效果以下
在这里插入图片描述
从下面的打印结果中也能够看出,值动画返回了一系列值。
在这里插入图片描述

PropertyValueHolder

PropertyValueHolder可让前面的一些动画同时执行。

ImageView imageView = findViewById(R.id.imageView);
 PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha", 0.5f, 1f);
 PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
 PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f);
 PropertyValuesHolder translationXProper = PropertyValuesHolder.ofFloat("translationX", -100, 100);
 PropertyValuesHolder translationYProper = PropertyValuesHolder.ofFloat("translationY", -100, 100);
 PropertyValuesHolder rotationProper = PropertyValuesHolder.ofFloat("rotation", 0, 360);
 ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alphaProper,
         scaleXProper, scaleYProper,translationXProper,translationYProper,rotationProper);
 animator.setDuration(5000);
 animator.start();

在这里插入图片描述

动画组合(AnimatorSet)

前面的PropertyValueHolder类能实现将多个动画同时执行,AnimatorSet类不只能让多个动画同时执行,还能让多个动画按必定的顺序执行,同时也能穿插多个动画同时执行。
主要的方法以下:

after(Animator anim)将现有动画插入到传入的动画以后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画以前执行
with(Animator anim) 将现有动画和传入的动画同时执行

ImageView imageView = findViewById(R.id.imageView);
 ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
 ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX", -100, 100f);
 ObjectAnimator translationY = ObjectAnimator.ofFloat(imageView, "translationY", -100, 100f);
 ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 1f);
 ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0, 1f);
 ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
 AnimatorSet animSet = new AnimatorSet();
 animSet.play(rotate)
         .with(alpha)
         .after(scaleX)
         .before(translationX)
         .after(1000)
         .before(translationY)
         .with(scaleY);
 animSet.setDuration(5000);
 animSet.start();

效果以下:
在这里插入图片描述

差值器(Interpolator)

前面的动画属性的变换都是均匀变换,能够经过差值器(Interpolator)来控制值变化的速率

ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha",  0f, 1f);
animator.setDuration(5000);
//加速查值器,参数越大,速度愈来愈快
animator.setInterpolator(new AccelerateInterpolator(5));
animator.start();

从下面的运行的效果能够看出,用了加速差值器之后,该图片alpha动画前期变化很慢,都后面变化愈来愈快。

在这里插入图片描述
系统咱们提供了九种默认的差值器分别以下:

动画名称 效果
AccelerateInterpolator 加速查值器,参数越大,速度愈来愈快
DecelerateInterpolator 减速差值起,和加速查值器相反
AccelerateDecelerateInterpolator 先加速后减速
AnticipateInterpolator 前后退在加速前进
AnticipateOvershootInterpolator 以X/Y轴为轴的旋转度数
BounceInterpolator 弹球效果插值
CycleInterpolator 周期运动插值
LinearInterpolator 匀速插值
OvershootInterpolator 先快速完成动画,再回到结束样式

咱们还能够经过自定义类实现Interpolator接口来实现一些自定义的差值器效果。

估值器(TypeEvaluator)

在前面的值动画(ValueAnimator)中和对象动画(ObjectAnimator)有一个传对象的方法:

ValueAnimator  ofObject(TypeEvaluator evaluator, Object... values)
ObjectAnimator ofObject(Object target, String propertyName,
            TypeEvaluator evaluator, Object... values)

这些方法动都须要传一个TypeEvaluator,咱们先来看下这个类的源码

public interface TypeEvaluator<T> {

    public T evaluate(float fraction, T startValue, T endValue);

}

TypeEvaluator估值器的源码能够看出该类的做用就是告诉动画,如何从起始值过分到结束值。
Android源码中有好几个类实现来该接口,也就是系统提供的一些默认估值器, 咱们以FloatEvaluator为例看下其实现代码。

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

FloatEvaluator的实现能够看出在evaluate方法中用结束值减去初始值,算出它们之间的差值,而后乘以fraction这个系数,再加上初始值,那么就获得当前动画的值了

咱们也能够以该方法为例 实现一个自定义的估值器实现一个背景颜色值的变化

咱们先定义一个默认估值器类MyTypeEvaluator,该类自定义了颜色过渡的方式

package com.lucashu.animation;

import android.animation.TypeEvaluator;

public class MyTypeEvaluator implements TypeEvaluator<String> {
    private int mCurrentRed = -1;
    private int mCurrentGreen = -1;
    private int mCurrentBlue = -1;

    @Override
    public String evaluate(float fraction, String startValue, String endValue) {

        
        int startRed = Integer.parseInt(startValue.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startValue.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startValue.substring(5, 7), 16);
        int endRed = Integer.parseInt(endValue.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endValue.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endValue.substring(5, 7), 16);
        // 初始化颜色的值
        if (mCurrentRed == -1) {
            mCurrentRed = startRed;
        }
        if (mCurrentGreen == -1) {
            mCurrentGreen = startGreen;
        }
        if (mCurrentBlue == -1) {
            mCurrentBlue = startBlue;
        }
        // 计算初始颜色和结束颜色之间的差值
        int redDiff = Math.abs(startRed - endRed);
        int greenDiff = Math.abs(startGreen - endGreen);
        int blueDiff = Math.abs(startBlue - endBlue);
        int colorDiff = redDiff + greenDiff + blueDiff;
        if (mCurrentRed != endRed) {
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
                    fraction);
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 将计算出的当前颜色的值组装返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
        return currentColor;
    }


    /** * 根据fraction值来计算当前的颜色。 */
    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor > endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor < endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor > endColor) {
                currentColor = endColor;
            }
        }
        return currentColor;
    }

    /** * 将10进制颜色值转换成16进制。 */
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }
}

再自定义一个View,在该类中画一个矩形框

public class MyView extends View {

    private String color;
    private Paint mPaint;
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
        mPaint.setColor(Color.parseColor(color));
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, 500,500, mPaint);
    }
}

自顶一个View在布局文件中添加以下

<com.lucashu.animation.MyView
        android:id="@+id/myview"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginBottom="100dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

将在估值器加到动画中,该动画做用在咱们自定义的View上

MyView imageView = findViewById(R.id.myview);
ObjectAnimator anim = ObjectAnimator.ofObject(
        imageView,"color", new MyTypeEvaluator(),
        "#0000FF","#FF0000");
anim.setDuration(5000);
anim.start();

效果以下:
在这里插入图片描述