Android动画学习记录二(属性动画、估值期和插值器)
Android动画学习记录二(属性动画、估值期和插值器)
自API 11 Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation
),它弥补了之前补间动画的一些缺陷,几乎可以完全替代掉补间动画了。关于补间动画可以看这篇,https://blog.youkuaiyun.com/qq_53749266/article/details/123571771?spm=1001.2014.3001.5502
如果只需要对View进行移动、缩放、旋转和淡入淡出操作,那么补间动画确实已经足够了。但是很显然,这些功能是不足以覆盖所有的场景的,那么下面我们就来看看补间动画所不能胜任的场景。
一、补间动画缺陷
补间动画:
- 补间动画是只能够作用在View上的使用补间我们其它任何继承自View的组件进行动画操作,但是如果我们想要对一个非View的对象进行动画操作,补间动画就帮不上忙了。
- 补间动画只能为 View 添加动画效果,但不能监听 View 相关属性的变化过程。
- 补间动画提供的动画能力较为单一,目前只支持缩放动画****(Scale)、平移动画(Translate)、旋转动画(Rotate)、透明度动画(Alpha)****以及这些动画的集合动画,扩展性很差。
- 补间动画改变的是 View 的绘制效果,View 的真正位置和相关属性并不会改变,这也就造成了点击事件的触发区域是动画前的位置而不是动画后的位置的原因。
二、属性动画
属性动画:
- 属性动画作用对象不局限在 View 上,而是任何提供了 Getter 和 Setter 方法的对象的属性上。
- 属性动画通过动态改变 View 相关属性的方式来改变 View 的显示效果。移动一个按钮,那么这个按钮就是真正的移动了,而不再是仅仅在另外一个位置绘制了而已。
- 属性动画对于
propertyName
需要自己去挖掘,或者自己通过Wrapper
的方式去自定义propertyName
。
那属性动画的使用场景有哪些呢?
- 基本上补间动画作用在 View 上的动画效果,属性动画都可以实现;
- 在自定义 View 时,需要实现一些复杂的动画效果,或对 View 的一些特殊属性值进行动画变更时,补间动画无法实现时;
- 另外,属性动画你也可以用在非动画场景,比如,你在自定义 View 需要一个有一定规律(根据特定差值器变化)且可监听的数值变化器,这个时候借助属性动画是再合适不过了。
三、三种常见的属性动画类
ValueAnimator
ValueAnimator
本质通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果,是整个属性动画机制当中最核心的一个类,初始值和结束值之间的动画过渡就是由ValueAnimator
类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,ValueAnimator
还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。可以将ValueAnimator
看着一个值变化器,即在给定的时间内将一个目标值从给定的开始值变化到给定的结束值。在使用ValueAnimator
时通常需要添加一个动画更新的监听器,在监听器中能够获取到执行过程中的每一个动画值。
ValueAnimator.ofInt()
将初始值 以整型数值的形式 过渡到结束值,即估值器是整型估值器 - IntEvaluator
。
ofInt()作用:
- 创建动画实例
- 将传入的多个Int参数进行平滑过渡:此处传入0和3,表示将值从0平滑过渡到3
如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值
java方式
// 步骤1:设置动画属性的初始值 & 结束值
ValueAnimator anim = ValueAnimator.ofInt(0, 3);
/ 步骤2:设置动画的播放各种属性
// 设置动画运行的时长
anim.setDuration(500);
// 设置动画延迟播放时间
anim.setStartDelay(500);
// 设置动画重复播放次数 = 重放次数+1
// 动画播放次数 = infinite时,动画无限重复
anim.setRepeatCount(0);
// 设置重复播放动画模式
anim.setRepeatMode(ValueAnimator.RESTART);
// ValueAnimator.RESTART(默认):正序重放
// ValueAnimator.REVERSE:倒序回放
// 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器
// 设置值的更新监听器,即:值每次改变、变化一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获得改变后的值
int currentValue = (Integer) animation.getAnimatedValue();
// 将改变后的值赋给对象的属性值,下面会详细说明
View.setproperty(currentValue);
// 刷新视图,即重新绘制,从而实现动画效果
View.requestLayout();
}
});
// 步骤4:启动动画
anim.start();
xml方式
res/animator/set_animation.xml
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0" // 初始值
android:valueTo="3" // 结束值
android:valueType="intType" // 变化值类型 :floatType & intType
android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度
/>
// 载入XML动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 设置动画对象
animator.setTarget(view);
// 启动动画
animator.start();
- android:propertyName——表示属性动画的作用对象的属性的名称;
- android:duration——表示动画的时长;
- android:valueFrom——表示属性的起始值;
- android:valueTo——表示属性的结束值;
- android:startOffset——表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正播放此动画;
- android:fillBefore——动画播放完后,视图是否会停留在动画开始的状态,默认为true
- android:fillAfter——动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
- android:repeatMode——选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
- android:fillEnabled——是否应用fillBefore值,对fillAfter值无影响,默认为true
- android:repeatCount——重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
- android:valueType——表示android:propertyName所指定的属性的类型,有“intType”和“floatType”两个可选项,分别表示属性的类型为整型和浮点型。另外,如果android:propertyName所指定的属性表示的是颜色,那么不需要指定android:valueType,系统会自动对颜色类型的属性做处理。
ValueAnimator.oFloat()
将初始值 以浮点型数值的形式 过渡到结束值,只展示与ValueAnimator.ofInt()
的区别之处
/*
* 设置方式1:xml
*/
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="3"
android:valueType="floatType" // 区别:设置为浮点型类型
... // 其余属性设置跟ValueAnimator.ofInt类似
/>
/*
* 设置方式2:Java
*/
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
//添加监听器
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//这个方法可以拿到当前的值
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();
ValueAnimator.ofInt()与ValueAnimator.oFloat()仅仅只是在估值器上的区别:(即如何从初始值 过渡 到结束值)
ValueAnimator.oFloat()采用默认的浮点型估值器 (FloatEvaluator)
ValueAnimator.ofInt()采用默认的整型估值器(IntEvaluator)
在使用上完全没有区别
ValueAnimator.ofObject()
将初始值 以对象的形式 过渡到结束值,即通过操作 对象 实现动画效果。
参数说明
参数1:自定义的估值器对象(TypeEvaluator)
参数2:初始动画的对象
参数3:结束动画的对象
// 1. 创建初始动画时的对象 & 结束动画时的对象
myObject object1 = new myObject();
myObject object2 = new myObject();
// 2. 创建动画对象
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2);
// 3. 设置参数
anim.setDuration(5000);
// 4. 启动动画
anim.start();
ValueAnimator
的作用:只是对值进行了一个平滑的动画过渡
ObjectAnimator
直接对对象的属性值进行改变操作,从而实现动画效果。ObjectAnimator
继承自ValueAnimator
,它是可以直接对任意对象的任意属性进行动画操作的,比如说View
的translate
(平移),scale
(缩放),rotate
(旋转),alpha
(渐变)属性。
ObjectAnimator.ofFloat(Object object, String property, float …values);
作用:
- 创建动画实例
- 参数设置:
Object object:需要操作的对象
String property:需要操作的对象的属性
float …values:动画初始值 & 结束值(不固定长度)
若是两个参数a,b,则动画效果则是从属性的a值到b值,若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值,以此类推。
至于如何从初始值过渡到结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator
Java代码
// 步骤1:创建ObjectAnimator对象
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
// 其实Button对象中并没有rotation这个属性值
// ObjectAnimator并不是直接对我们传入的属性名进行操作
// 而是根据传入的属性值"rotation" 去寻找对象对应属性名对应的get和set方法,从而通过set()、get()对属性进行赋值
// 因为Button对象中有rotation属性所对应的get & set方法
// 所以传入的rotation属性是有效的
// 所以才能对rotation这个属性进行操作赋值
// 步骤2:设置动画属性
// 设置动画运行的时长
public void setRotation(float value);
public float getRotation();
// 实际上,这两个方法是由View对象提供的,所以任何继承自View的对象都具备这个属性
// 设置动画延迟播放时间
anim.setStartDelay(500);
// 设置动画重复播放次数 = 重放次数+1
// 动画播放次数 = infinite时,动画无限重复
anim.setRepeatCount(0);
// 设置重复播放动画模式
anim.setRepeatMode(ValueAnimator.RESTART);
// ValueAnimator.RESTART(默认):正序重放
// ValueAnimator.REVERSE:倒序回放
// 步骤3:启动动画
animator.start();
xml代码:
//创建 res/animator/set_animation.xml设置动画参数
// ObjectAnimator 采用<animator> 标签
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1" // 初始值
android:valueTo="0" // 结束值
android:valueType="floatType" // 变化值类型 :floatType & intType
android:propertyName="alpha" // 对象变化的属性名称
/>
// 步骤3:启动动画
// 1. 载入XML动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);
// 2. 设置动画对象
animator.setTarget(view);
// 3. 启动动画
animator.start();
大部分方法与ValueAnimator类似。
可以理解为:ObjectAnimator更加智能、自动化程度更高
ValueAnimator
类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;ObjectAnimator
类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;
AnimatorSet
借助AnimatorSet这个类我们可以实现组合动画功能,实现组合大致有如下两种方法:
play()
方法
如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
方法名 说明
- after(Animator anim) 将现有动画插入到传入的动画之后执行
- after(long delay) 将现有动画延迟指定毫秒后执行
- before(Animator anim) 将现有动画插入到传入的动画之前执行
- with(Animator anim) 将现有动画和传入的动画同时执行
playTogether
方法
这个方法只能让所有的动画一起执行,相等于上面的with
使用:
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(textView,"rotationX",0,360,0), //在水平方向进行旋转
ObjectAnimator.ofFloat(textView,"rotationY",0,180,0), //在垂直方向进行旋转
ObjectAnimator.ofFloat(textView,"rotation",0,-90,0), //旋转
ObjectAnimator.ofFloat(textView,"translationX",0,90,0), //在水平方向平移
ObjectAnimator.ofFloat(textView,"translationY",0,90,0), //在垂直方向平移
ObjectAnimator.ofFloat(textView,"scaleX",1,1.5f,1), //在水平方向上进行缩放
ObjectAnimator.ofFloat(textView,"scaleY",0,0.5f,1), //在垂直方向上进行缩放
ObjectAnimator.ofFloat(textView,"alpha",1,0.25f,1) //透明度
);
set.setDuration(5000).start();
四、TypeEvaluator(估值器)和Interpolator(插值器)
TypeEvaluator(估值器)
在插值器(Interpolator
)决定 值 的变化规律(匀速、加速blabla),即决定是变化趋势后;接下来的具体变化数值则交给而估值器(Interpolator
),估值器(Interpolator
)的作用是告诉动画系统如何从初始值过度到结束值,根据当前属性改变的百分比来计算改变后的属性值。ValueAnimator.ofInt()
& ValueAnimator.ofFloat()
都具备系统内置的估值器,即FloatEvaluator
& IntEvaluator,
即系统已经默认实现了 如何从初始值 过渡到 结束值 的逻辑。
系统预设的有IntEvaluator(针对整型属性),FloatEvaluator(针对浮点型属性),和ArgbEvaluator(针对Color属性)。
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
第一个参数fraction
非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,然后乘以fraction这个系数,再加上初始值,就得到了当前动画的值。
但对于第三个方法ValueAnimator.ofObject()
,从上面的工作原理可以看出并没有系统默认实现,因为对对象的动画操作复杂 & 多样,系统无法知道如何从初始对象过度到结束对象
因此,对于ValueAnimator.ofObject()
,我们需自定义估值器(TypeEvaluator
)来告知系统如何进行从 初始对象 过渡到 结束对象的逻辑。
有一个非常简单的Point类,只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标。
public class PointEvaluator implements TypeEvaluator{
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
Point point = new Point(x, y);
return point;
}
}
evaluate()
中先是将startValue
和endValue
强转成Point
对象,然后同样根据fraction
来计算当前动画的x和y的值,最后组装到一个新的Point
对象当中并返回。ofObject()
方法要求多传入一个TypeEvaluator参数,这里我们只需要传入刚才定义好的PointEvaluator的实例就可以了。
//将Point1通过动画平滑过度到Point2
Point point1 = new Point(0, 0);
Point point2 = new Point(300, 300);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);
anim.setDuration(5000);
anim.start();
public class MyAnimView extends View {
public static final float RADIUS = 50f;
private Point currentPoint;
private Paint mPaint;
public MyAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
startAnimation();
} else {
drawCircle(canvas);
}
}
//在currentPoint的坐标位置画出一个半径为50的圆
private void drawCircle(Canvas canvas) {
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
private void startAnimation() {
Point startPoint = new Point(RADIUS, RADIUS);//View的左上角
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//右下角
ValueAnimator anim = ValueAnimator.ofObject(new **PointEvaluator**(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
//当Point值有改变的时候都会回调onAnimationUpdate()方法
public void onAnimationUpdate(ValueAnimator animation) {
//currentPoint对象进行了重新赋值,并调用了invalidate()方法,onDraw()方法就会重新调用,
//并且由于currentPoint对象的坐标已经改变了,绘制的位置也会改变,于是一个平移的动画效果也就实现了。
currentPoint = (Point) animation.getAnimatedValue();
//请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,
//并且只绘制那些“需要重绘的”
invalidate();
}
});
anim.setDuration(5000);
anim.start();
}
}
Interpolator(插值器)
主要作用是可以根据时间流逝的百分比来计算当前属性值的百分比,控制动画的变化速率。比如去实现一种非线性运动的动画效果
非线性运动:动画改变的速率不是一成不变的,如加速 & 减速运动都属于非线性运动
插值器在动画的使用有两种方式:在XML / Java代码中设置:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/overshoot_interpolator"
// 通过资源ID设置插值器
android:duration="3000"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="2"
android:toYScale="2" />
// 步骤1:创建 需要设置动画的 视图View
Button mButton = (Button) findViewById(R.id.Button);
// 步骤2:创建透明度动画的对象 & 设置动画效果
Animation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setDuration(3000);
// 步骤3:创建对应的插值器类对象
Interpolator overshootInterpolator = new OvershootInterpolator();
// 步骤4:给动画设置插值器
alphaAnimation.setInterpolator(overshootInterpolator);
// 步骤5:播放动画
mButton.startAnimation(alphaAnimation);
作用 | 资源ID | 对应的Java类 |
---|---|---|
动画加速进行 | @android:anim/accelerate_interpolator | AccelerateInterpolator |
快速完成动画,超出再回到结束样式 | @android:anim/overshoot_interpolator | OvershootInterpolator |
先加速再减速 | @android:anim/accelerate_decelerate_interpolator | AccelerateDecelerateInterpolator |
先退后再加速前进 | @android:anim/anticipate_interpolator | AnticipateInterpolator |
先退后再加速前进,超出终点后再回终点 | @android:anim/anticipate_overshoot_interpolator | AnticipateOvershootInterpolator |
最后阶段弹球效果 | @android:anim/bounce_interpolator | BounceInterpolator |
周期运动 | @android:anim/cycle_interpolator | CycleInterpolator |
减速 | @android:anim/decelerate_interpolator | DecelerateInterpolator |
匀速 | @android:anim/linear_interpolator | LinearInterpolator |
|
系统默认的插值器是AccelerateDecelerateInterpolator
,即先加速后减速
- 当在XML文件设置插值器时,只需传入对应的插值器资源ID即可
- 当在Java代码设置插值器时,只需创建对应的插值器对象即可
小结
- 估值器(
TypeEvaluator
)决定 值 的具体变化数值 - 插值器(
Interpolator
)决定 值 的变化模式(匀速、加速blabla)
五、属性动画的监听器
ObjectAnimator
是继承自ValueAnimator
的,而ValueAnimator
又是继承自Animator
的,因此不管是ValueAnimator
还是ObjectAnimator
都是可以使用addListener()
这个方法。
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
很多时候我们并不想要监听那么多个事件,可能我只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。没关系,为此Android提供了一个适配器类,叫作AnimatorListenerAdapter
。由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。我们可以单独重写我们需要的方法。
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
六、XML编写动画
过去的补间动画除了使用代码编写之外也是可以使用XML编写的,因此属性动画也提供了这一功能,即通过XML来完成和代码一样的属性动画功能。
通过XML来编写动画可能会比通过代码来编写动画要慢一些,但是在重用方面将会变得非常轻松,比如某个将通用的动画编写到XML里面,我们就可以在各个界面当中轻松去重用它。
如果想要使用XML来编写动画,首先要在res目录下面新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。然后在XML文件中我们一共可以使用如下三种标签:
- 对应代码中的ValueAnimator
- 对应代码中的ObjectAnimator
- 对应代码中的AnimatorSet
使用xml实现上面的组合动画:把文件加载进来并将动画启动
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
参考资料
https://blog.youkuaiyun.com/guolin_blog/article/details/43816093
https://carsonho.blog.youkuaiyun.com/article/details/79860980