本文目录
- 属性动画概述
- 属性动画用法
- 对象动画(ObjectAnimator)
- 方法1:Java代码实现对象动画
- 方法2:XML实现对象动画
- 值动画(ValueAnimator)
- PropertyValueHolder
- 动画组合(AnimatorSet)
- 差值器(Interpolator)
- 估值器(TypeEvaluator)
属性动画概述
前面给我们分析了Android
中的帧动画
和补间动画
的特点和用法
Android动画之补间动画用法最全详解
Android 动画之帧动画用法详解
Android
官方在Anrdoid 3.0
以后又推出了一种新的动画即属性动画
,既然前面的帧动画
和补间动画
能帮助我们实现大部分的Android动画效果,那么官方为什么还要推出这种新的属性动画
呢?
原因1:
补间动画
作用的对象是View
,也就是作用的对象是Android
中的控件,如ImageView
、Button
、TextView
等,也可以作用在布局上如LinearLayout
、ConstraintLayout
、RelativeLayout
等,但是对于一些不是View
的对象,无法对这些对象进行动画操作。比如我们要对某个控件的某个属性做进行动画操作,如其颜色,这个颜色也可以看成一个对象,但其并不是View
对象,补间动画
就无法实现,属性动画
可以对这个颜色值做动画, 能实现一些更加复杂的动画效果。
原因2:
补间动画只是改变了View
的视觉效果,而不会真正去改变View的属性
比如我们对一个图片进行AlphaAnimation
,并在动画前后打印其值
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
的属性值。
属性动画用法
1、属性动画都是通过ValueAnimator
类和ObjectAnimator
类来完成,其中ObjectAnimator
类是对对象做动画,ValueAnimator
类是对值做动画。
2、PropertyValueHolder类
可以同时执行多个动画,AnimatorSet
l类可以将多个动画按一定的秩序先后执行。
3、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
实现对象动画
1、在res
目录下新建animator
文件夹
2、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;
}
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;
}
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();
效果如下: