属性动画是在 Android 3.0
后才提供的一种全新动画模式,那么为什么要提供属性动画呢?或者说属性动画的优势在哪里呢?
帧动画:
由于逐帧动画的帧序列内容不一样,不仅增加制作负担而且最终输出的文件量也很大。
补间动画:
1.只能够作用在视图View
上,即只可以对一个Button
、TextView
、甚至是LinearLayout
、或者其它继承自View
的组件进行动画操作,但无法对非View
的对象进行动画操作
2.不会改变View的属性,只是改变视觉效果(比如说视觉上一个按钮从左边移动到了右边,但是他的点击监听却还是在左边)
属性动画不但可以作用在任何对象上,甚至没有对象也可以,而且动画效果多种多样
属性动画最重要的类有两个,一个是ValueAnimator,一个是ObjectAnimator类,我们先说ValueAnimator
ValueAnimator类
动画到底是如何执行的呢?看图:
图比文字更容易理解,简单来说就是按照一个函数不断地变化要执行动画的对象的属性值,在其中我们可以看到,动画之所以可以执行还是依靠一个核心类ValueAnimator,最重要的三个方法:
ofInt 按照整数来改变指定对象的属性值
ofFloat 按照浮点数来改变指定对象的属性值
offObject 按照给定的属性值名称来改变指定对象的给定属性值
操作值的方式可以使用xml或者java设置,不过开发中经常使用java动态设置,因为设置时可能开始只和结束值需要传参数进来设置,下面是使用ofInt的示例,offFloat的用法类似
// 步骤1:设置动画属性的初始值和结束值
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 3);
// ofInt()作用有两个
// 1. 创建动画实例
// 2. 将传入的多个Int参数进行平滑过渡:此处传入0和1,表示将值从0平滑过渡到1
// 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
// ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值
// 步骤2:设置动画的播放各种属性
valueAnimator.setDuration(500);
// 设置动画运行的时长
valueAnimator.setStartDelay(500);
// 设置动画延迟播放时间
valueAnimator.setRepeatCount(0);
// 设置动画重复播放次数 = 重放次数+1,默认为0
// 动画播放次数 = infinite时,动画无限重复
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
// 设置重复播放动画模式
// ValueAnimator.RESTART(默认):正序重放
// ValueAnimator.REVERSE:倒序回放
// 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器
// 设置值的更新监听器
// 即:值每次改变、变化一次,该方法就会被调用一次
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (int) animation.getAnimatedValue();
// 获得改变后的值
Log.e("当前值:",currentValue+"");
// 输出改变后的值
// 步骤4:将改变后的值赋给对象的属性值
// View.setproperty(currentValue);
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
//View.requestLayout();
}
});
valueAnimator.start();
// 启动动画
ofInt运行结果:
ofFloat运行结果:
xml设置:
<animator
xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0" //初始值
android:valueTo="100" //结束值
android:valueType="intType" //变化值类型
android:duration="2000" //持续时间
android:startOffset="500" //延迟时间
android:fillBefore="true" //动画执行之后是否停留在初始状态,默认为true
android:fillAfter="false" //动画执行之后是否停留在结束状态,默认为false,优先于fillBefore
android:fillEnabled="true" //是否应用fillBefore属性,默认为true
android:repeatMode="restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
android:repeatCount="0"
/>
java设置
Animator animator = AnimatorInflater.loadAnimator(MainActivity.this, R.animator.set_animator);
// 载入XML动画
animator.setTarget(view);
// 设置动画对象
animator.start();
// 启动动画
让我们结合控件来尝试一下
private class ValueAnimatorButtonListener implements View.OnClickListener {
@Override
public void onClick(final View v) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(v.getLayoutParams().width, v.getLayoutParams().width+500);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
Log.e("当前值:",currentValue+"");
v.getLayoutParams().width=currentValue;
v.requestLayout();
}
});
valueAnimator.start();
}
}
我们知道动画的执行是和值的变化相关的,那么这些值是如何变化的呢?这就涉及到了估值器,TypeEvaluator,系统内置的两种估值器
// type evaluators for the primitive types handled by this implementation
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
如果你不设置自己的估值器,系统便会在init的时候判断你的值并为你添加相应的默认估值器
/**
* Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
* to calculate animated values.
*/
void init() {
if (mEvaluator == null) {
// We already handle int and float automatically, but not their Object
// equivalents
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
}
if (mEvaluator != null) {
// KeyframeSet knows how to evaluate the common types - only give it a custom
// evaluator if one has been set on this class
mKeyframes.setEvaluator(mEvaluator);
}
}
让我们来看看这两种估值器
整数类型:
public class IntEvaluator implements TypeEvaluator {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
浮点类型:
public class FloatEvaluator implements TypeEvaluator {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
大体的思路是这样的先算出来初始值和结束值的差值,乘上一个系数再加上初始值,得到当前值
那就让我们自己写一个估值器来执行动画
private class PointEvaluatorButtonListener implements View.OnClickListener {
@Override
public void onClick(final View v) {
Point startPoint=new Point(v.getX(),v.getY());
Point endPoint=new Point(v.getX(),v.getY()-500);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.setDuration(2000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point currentPoint=(Point)animation.getAnimatedValue();
Log.e("当前坐标",currentPoint.getX()+" "+currentPoint.getY());
v.setX(currentPoint.getX());
v.setY(currentPoint.getY());
v.requestLayout();
}
});
anim.start();
}
}
private class PointEvaluator implements TypeEvaluator{
@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;
}
}
我们还可以将其应用到自定义视图当中:
public class MyView extends View {
private Point leftTop;//矩形左上角点
private Point rightDown;//矩形右下角点
private Paint mPaint;// 绘图画笔
private ValueAnimator anim;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(final Canvas canvas) {
// 如果当前点坐标为空(即第一次)
if (leftTop == null) {
leftTop = new Point(0, 0);
rightDown=new Point(canvas.getWidth()/2,canvas.getHeight());
canvas.drawRect(leftTop.getX(),leftTop.getY(),rightDown.getX(),rightDown.getY(),mPaint);
// (重点关注)将属性动画作用到View中
// 步骤1:创建初始动画时的对象点 & 结束动画时的对象点
Point startPoint = leftTop;
Point endPoint = new Point(canvas.getWidth()/2, 0);
// 步骤2:创建动画对象 & 设置参数
anim = ValueAnimator.ofObject(new AnimationActivity.PointEvaluator(), startPoint, endPoint);
// 参数说明
// 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口)
// 参数2:初始动画的对象点
// 参数3:结束动画的对象点
anim.setDuration(2000);
// 设置动画时长
// 设置 属性值的更新监听器
// 即每当坐标值(Point值)更新一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
leftTop = (Point) animation.getAnimatedValue();
rightDown.setX(leftTop.getX()+canvas.getWidth()/2);
// 返回当前值到当前坐标值(currentPoint)
// 从而更新当前坐标值(currentPoint)
invalidate();
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
// 所以坐标值每改变一次,就会调用onDraw()一次
}
});
} else {
// 如果坐标值不为0,则画矩形
canvas.drawRect(leftTop.getX(), leftTop.getY(), rightDown.getX(),rightDown.getY(), mPaint);
}
}
public void play(){
anim.start();
// 启动动画
}
}
ObjectAnimator
我们之前说属性动画可以控制很多属性,包括大小,位置,颜色等等属性,只要你在对应的对象中存在get和set方法,那么就都可以控制,get方法也只是为了初始化,如果已经设定好初始值,那么只需要set方法即可
它的使用方法和前面讲的大体相似,如果想控制较为复杂的属性就需要自己多写点代码了,我这里放一个简单的小例子
public class MyView2 extends View {
private Paint mPaint;
private float radius;
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}
public MyView2(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.GREEN);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(canvas.getWidth()/2,canvas.getHeight()/2,radius, mPaint);
}
}
private class MyView2Listener implements View.OnClickListener {
@Override
public void onClick(View v) {
final ObjectAnimator anim = ObjectAnimator.ofObject((MyView2)v, "radius", new RadiusEvaluator(),
0.0f, 200.0f);
anim.setDuration(5000);
anim.start();
}
}
private class RadiusEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startRadius=(float)startValue;
float endRadius=(float)endValue;
float currentRadius=startRadius+fraction*(endRadius-startRadius);
return currentRadius;
}
}
ofObject方法第一个参数是要执行动画的对象,不局限view对象,第二个是要改变属性的名字,会根据这个名字找对应的get和set方法,第三个是估值器,第四个和第五个参数分别是开始至和结束值
未完待续。。。