属性动画
前边咱们也说了,android动画除了补间动画还有帧动画,现在又多了一种选择就是属性动画。
首先啊,补间动画还有帧动画存在一定的缺点;
1、作用对象局限:View
即补间动画 只能够作用在视图View
上,即只可以对一个Button
、TextView
、甚至是LinearLayout
、或者其它继承自View
的组件进行动画操作,但无法对非View
的对象进行动画操作。
有些情况下的动画效果只是视图的某个属性 & 对象而不是整个视图,如,现需要实现视图的颜色动态变化,那么就需要操作视图的颜色属性从而实现动画效果,而不是针对整个视图进行动画操作。
2.没有改变View的属性,只是改变视觉效果
补间动画只是改变了View
的视觉效果,而不会真正去改变View
的属性。如,将屏幕左上角的按钮 通过补间动画移动到屏幕的右下角,点击当前按钮位置(屏幕右下角)是没有效果的,因为实际上按钮还是停留在屏幕左上角,补间动画只是将这个按钮绘制到屏幕右下角,改变了视觉效果而已。
3.动画效果单一
补间动画只能实现平移、旋转、缩放 & 透明度这些简单的动画需求,一旦遇到相对复杂的动画效果,即超出了上述4种动画效果,那么补间动画则无法实现。
基于这些缺点,才有了属性动画,Property Animation,下面开始讲解:
原理:
在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。
属性动画分类:
- ValueAnimator
- ObjectAnnimator
ValueAnimator类:
实现动画的原理:通过不断控制“值“”的变化,再不断手动赋给对象的属性,从而实现动画效果。
其中他有三个重要的方法:
ValueAnimator.ofInt(int... values)
ValueAnimator.ofFloat(float... values)
ValueAnimator.ofObject(
TypeEvaluator evaluator,Object... values)
ValueAnimator.ofInt():
// 步骤1:设置动画属性的初始值 & 结束值
ValueAnimator anim = ValueAnimator.ofInt(0, 3);
// ofInt()作用有两个
// 1. 创建动画实例
// 2. 将传入的多个Int参数进行平滑过渡:此处传入0和1,表示将值从0平滑过渡到1
// 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
// ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值
// 关于自定义插值器我将在下节进行讲解
// 下面看看ofInt()的源码分析 ->>关注1
// 步骤2:设置动画的播放各种属性
anim.setDuration(500);
// 设置动画运行的时长
anim.setStartDelay(500);
// 设置动画延迟播放时间
anim.setRepeatCount(0);
// 设置动画重复播放次数 = 重放次数+1
// 动画播放次数 = infinite时,动画无限重复
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();
// 获得改变后的值
System.out.println(currentValue);
// 输出改变后的值
// 步骤4:将改变后的值赋给对象的属性值,下面会详细说明
View.setproperty(currentValue);
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
View.requestLayout();
}
});
anim.start();
// 启动动画
}
// 关注1:ofInt()源码分析
public static ValueAnimator ofInt(int... values) {
// 允许传入一个或多个Int参数
// 1. 输入一个的情况(如a):从0过渡到a;
// 2. 输入多个的情况(如a,b,c):先从a平滑过渡到b,再从b平滑过渡到C
ValueAnimator anim = new ValueAnimator();
// 创建动画对象
anim.setIntValues(values);
// 将传入的值赋值给动画对象
return anim;
}
赋值和view绘制从而产生的动画的过程,可以参考的另一篇文章:
Android 自定义view 定制一个带比例的环形进度控件
当然也有XML形式的:
// ValueAnimator采用<animator> 标签
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0" // 初始值
android:valueTo="100" // 结束值
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 // 插值器,即影响动画的播放速度,下面会详细讲
/>
使用的话,
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 载入XML动画
animator.setTarget(view);
// 设置动画对象
animator.start();
// 启动动画
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
// 获得改变后的值
System.out.println(currentValue);
// 输出改变后的值
// 步骤4:将改变后的值赋给对象的属性值,下面会详细说明
View.setproperty(currentValue);
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
View.requestLayout();
//invalidate();
}
});
ValueAnimator.ofFloat(float... values)
使用上除了参数是浮点类型的之外,其他的都跟ValueAnimator.ofInt(int... values)一样
ValueAnimator.ofObject(
TypeEvaluator evaluator,Object... values)
即通过操作对象来实现动画效果
具体使用:
// 创建初始动画时的对象 & 结束动画时的对象
myObject object1 = new myObject();
myObject object2 = new myObject();
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2);
// 创建动画对象 & 设置参数
// 参数说明
// 参数1:自定义的估值器对象(TypeEvaluator 类型参数)
// 参数2:初始动画的对象
// 参数3:结束动画的对象
anim.setDuration(5000);
anim.start();
这里有同学要问了,为啥ofInt 和 ofFloat都是单一传入数值,而这个不近需要对象,还有一个TypeEvaluator,下面说下一这个估值器吧
为什么ofInt 和 ofFloat他们没有,是因为内置了一个IntEvaluatoror FloatEvaluator,看下源码
public class FloatEvaluator implements TypeEvaluator {
// FloatEvaluator实现了TypeEvaluator接口
// 重写evaluate()
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 参数说明
// fraction:表示动画完成度(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
// 初始值 过渡 到结束值 的算法是:
// 1. 用结束值减去初始值,算出它们之间的差值
// 2. 用上述差值乘以fraction系数
// 3. 再加上初始值,就得到当前动画的值
}
}
- 但对于
ValueAnimator.ofObject()
,从上面的工作原理可以看出并没有系统默认实现,因为对对象的动画操作复杂 & 多样,系统无法知道如何从初始对象过度到结束对象 - 因此,对于
ValueAnimator.ofObject()
,我们需自定义估值器(TypeEvaluator
)来告知系统如何进行从 初始对象 过渡到 结束对象的逻辑 - 自定义实现的逻辑如下
// 实现TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator{
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 参数说明
// fraction:表示动画完成度(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
... // 写入对象动画过渡的逻辑
return value;
// 返回对象动画过渡的逻辑计算后的值
}
实例搞一下吧:
新建自定义对象类:
public class Point {
// 设置两个变量用于记录坐标的位置
private float x;
private float y;
// 构造方法用于设置坐标
public Point(float x, float y) {
this.x = x;
this.y = y;
}
// get方法用于获取坐标
public float getX() {
return x;
}
public float getY() {
return y;
}
}
自定义估值器,实现TypeEvaluator
// 实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
// 根据fraction来计算当前动画的x和y的值
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
// 将计算后的坐标封装到一个新的Point对象中并返回
Point point = new Point(x, y);
return point;
}
}
- 上面步骤是根据需求自定义
TypeEvaluator
的实现 - 下面将讲解如何通过对
Point
对象进行动画操作,从而实现整个自定义View的动画效果。
将属性属性动画用到自定义的View当中
public class MyView extends View {
// 设置需要用到的变量
public static final float RADIUS = 70f;// 圆的半径 = 70
private Point currentPoint;// 当前点坐标
private Paint mPaint;// 绘图画笔
// 构造方法(初始化画笔)
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
// 复写onDraw()从而实现绘制逻辑
// 绘制逻辑:先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果
@Override
protected void onDraw(Canvas canvas) {
// 如果当前点坐标为空(即第一次)
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
// 创建一个点对象(坐标是(70,70))
// 在该点画一个圆:圆心 = (70,70),半径 = 70
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
// (重点关注)将属性动画作用到View中
// 步骤1:创建初始动画时的对象点 & 结束动画时的对象点
Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70)
Point endPoint = new Point(700, 1000);// 结束点为(700,1000)
// 步骤2:创建动画对象 & 设置初始值 和 结束值
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
// 参数说明
// 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口)
// 参数2:初始动画的对象点
// 参数3:结束动画的对象点
// 步骤3:设置动画参数
anim.setDuration(5000);
// 设置动画时长
// 步骤3:通过 值 的更新监听器,将改变的对象手动赋值给当前对象
// 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象
// 设置 值的更新监听器
// 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
// 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
// 从而更新当前坐标值(currentPoint)
// 步骤4:每次赋值后就重新绘制,从而实现动画效果
invalidate();
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
// 所以坐标值每改变一次,就会调用onDraw()一次
}
});
anim.start();
// 启动动画
} else {
// 如果坐标值不为0,则画圆
// 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
// 在该点画一个圆:圆心 = (30,30),半径 = 30
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
}
}
接下来 就是将自定义view用到自己的xml中,就可以实现上边效果。
总结一下:其实ValueAnimator.ofObject()
的本质还是操作 值,只是是采用将 多个值 封装到一个对象里的方式 同时对多个值一起操作而已。
ObjectAnimator
1.与 ValueAnimator相同点
二者都属于属性动画,本质上都是一致的:先改变值,然后 赋值 给对象的属性从而实现动画效果。
2.与 ValueAnimator不同点
ValueAnimator
类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;- 而
ObjectAnimator
类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;
下边结合AnimatorSet类,实例操作一下ObjectAnimator
AnimatorSet是属性动画的组合动画控制器,AnimationSet是补间动画的组合动画控制器
这里我们要操作一个Button,
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btnShow,"translationX",0,300);
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(btnShow,"alpha",0.1f,1f);
这里写了实例出来两个属性动画,然后通过AnimatorSet整合起来
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(objectAnimator,objectAnimator1);
animatorSet.setDuration(2000);
animatorSet.setInterpolator(new BounceInterpolator());
animatorSet.start();
这里有两点要注意:
1.AnimationSet 要操作的对象,是以animatorSet.setTarget(btnStart)为准的,比如加上animatorSet.setTarget(btnStart),那么被操作的对象就是btnStart了;
2、playSequentially()表示 按照顺序执行动画
playTogether()表示一起执行
以上可以看到,AnimationSet只提供了两种playSequentially和playTogether两种形式,但是AnimationSet有一个内部类AnimationSet.Builder,他却可以自由组合各种方式:
AnimationSet.Builder
使用方法:
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btnShow, "translationX", 0, 300);
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(btnShow, "rotation", 0, 360);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000);
//通过animatorSet的play方法获取 感兴趣的话 可以都试试 看一下效果
animatorSet.play(objectAnimator).after(objectAnimator1);
animatorSet.start();
AnimationSet监听
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.i(TAG, "onAnimationStart: ");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG, "onAnimationEnd: ");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.i(TAG, "onAnimationCancel: ");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.i(TAG, "onAnimationRepeat: ");
}
});
以上就是属性动画的讲解了,权当笔记了,还望各位大佬批评指正。
AnimationSet XMl形式
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:valueFrom="0"
android:valueTo="360"
android:propertyName="rotation"/>
<objectAnimator
android:valueFrom="0"
android:valueTo="360"
android:propertyName="translationX"/>
</set>
// android:ordering="together"两个值
// equentially和together,分别对应playSequentially和playTogether两个方法的效果。
使用
AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(context,R.animator.anim_set);
animatorSet.setTarget(btnShow);
animatorSet.setDuration(2000);
animatorSet.start();