属性动画,顾名思义它是对于对象属性的动画。因此,所有补间动画的内容,都可以通过属性动画实现。
属性动画入门
private void RotateAnimation() {
ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
anim.setDuration(1000);
anim.start();
}
private void AlpahAnimation() {
ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f);
anim.setRepeatCount(-1);
anim.setRepeatMode(ObjectAnimator.REVERSE);
anim.setDuration(2000);
anim.start();
}
这两个方法用属性动画的方式分别实现了旋转动画和淡入淡出动画,其中setDuration、setRepeatMode及setRepeatCount和补间动画中的概念是一样的。
可以看到,属性动画貌似强大了许多,实现很方便,同时动画可变化的值也有了更多的选择,动画所能呈现的细节也更多。
当然属性动画也是可以组合实现的
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f);
ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360);
ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400);
ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750);
AnimatorSet set = new AnimatorSet();
set.playTogether(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
// set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
set.setDuration(3000);
set.start();
可以看到这些动画可以同时播放,或者是按序播放。
属性动画核心原理
在上面实现属性动画的时候,我们反复的使用到了ObjectAnimator 这个类,这个类继承自ValueAnimator,使用这个类可以对任意对象的任意属性进行动画操作。而ValueAnimator是整个属性动画机制当中最核心的一个类;这点从下面的图片也可以看出。
属性动画核心原理
属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。
从上图我们可以了解到,通过duration、startPropertyValue和endPropertyValue 等值,我们就可以定义动画运行时长,初始值和结束值。然后通过start方法开始动画。 那么ValueAnimator 到底是怎样实现从初始值平滑过渡到结束值的呢?这个就是由TypeEvaluator 和TimeInterpolator 共同决定的。
具体来说,TypeEvaluator 决定了动画如何从初始值过渡到结束值。觉得可以看作一个方向。
TimeInterpolator(估值器) 决定了动画从初始值过渡到结束值的节奏。觉得可以看作一个速度。
属性动画自定义实现
现在要去实现一个小球在屏幕上以 y=sin(x) 的数学函数轨迹运行,同时小球的颜色和半径也发生着变化。
这个动画最关键的三点就是 运动轨迹、小球半径及颜色的变化;我们就从这三个方面展开。最后我们在结合Interpolator说一下TimeInterpolator的意义。
用TypeEvaluator 确定运动轨迹
前面说了,TypeEvaluator决定了动画如何从初始值过渡到结束值。这个TypeEvaluator是个接口,我们可以实现这个接口。
PointSinEvaluator 继承了TypeEvaluator类,并实现了他唯一的方法evaluate;这个方法有三个参数,第一个参数fraction 代表当前动画完成的百分比,这个值是如何变化的后面还会提到;第二个和第三个参数代表动画的初始值和结束值。这里我们的逻辑很简单,x的值随着fraction 不断变化,并最终达到结束值;y的值就是当前x值所对应的sin(x) 值,然后用x 和 y 产生一个新的点(Point对象)返回。
这样我们就可以使用这个PointSinEvaluator 生成属性动画的实例了。
这样我们就完成了动画轨迹的定义,现在只要调用valueAnimator.start() 方法,就会绘制出一个正弦曲线的轨迹。
颜色及半径动画实现
之前我们说过,使用ObjectAnimator 可以对任意对象的任意属性进行动画操作,这句话是不太严谨的,这个任意属性还需要有get 和 set 方法。
public class PointAnimView extends View {
/**
* 实现关于color 的属性动画
*/
private int color;
private float radius = RADIUS;
.....
}
这里在我们的自定义view中,定义了两个属性color 和 radius,并实现了他们各自的get set 方法,这样我们就可以使用属性动画的特点实现小球颜色变化的动画和半径变化的动画。
这里,我们使用ObjectAnimator 实现对color 属性的值按照ArgbEvaluator 这个类的规律在给定的颜色值之间变化,这个ArgbEvaluator 和我们之前定义的PointSinEvaluator一样,都是决定动画如何从初始值过渡到结束值的,只不过这个类是系统自带的,我们直接拿来用就可以,他可以实现各种颜色间的自由过渡。
对radius 这个属性使用了ValueAnimator,使用了其ofFloat方法实现了一系列float值的变化;同时为其添加了动画变化的监听器,在属性值更新的过程中,我们可以将变化的结果赋给radius,这样就实现了半径动态的变化。
这里radius 也可以使用和color相同的方式,只需要把ArgbEvaluator 替换为FloatEvaluator,同时修改动画的变化值即可;使用添加监听器的方式,只是为了介绍监听器的使用方法而已。**
好了,到这里我们已经定义出了所有需要的动画,前面说过,属性动画也是可以组合使用的。因此,在动画启动的时候,同时播放这三个动画,就可以实现图中的效果了。
PointAnimView 源码
public class PointAnimView extends View {
public static final float RADIUS = 20f;
private Point currentPoint;
private Paint mPaint;
private Paint linePaint;
private AnimatorSet animSet;
private TimeInterpolator interpolatorType = new LinearInterpolator();
/**
* 实现关于color 的属性动画
*/
private int color;
private float radius = RADIUS;
public PointAnimView(Context context) {
super(context);
init();
}
public PointAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PointAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
mPaint.setColor(this.color);
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.TRANSPARENT);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.BLACK);
linePaint.setStrokeWidth(5);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
// StartAnimation();
} else {
drawCircle(canvas);
}
drawLine(canvas);
}
private void drawLine(Canvas canvas) {
canvas.drawLine(10, getHeight() / 2, getWidth(), getHeight() / 2, linePaint);
canvas.drawLine(10, getHeight() / 2 - 150, 10, getHeight() / 2 + 150, linePaint);
canvas.drawPoint(currentPoint.getX(), currentPoint.getY(), linePaint);
}
public void StartAnimation() {
Point startP = new Point(RADIUS, RADIUS);
Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
valueAnimator.setRepeatCount(-1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
postInvalidate();
}
});
//
ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
animColor.setRepeatCount(-1);
animColor.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
animScale.setRepeatCount(-1);
animScale.setRepeatMode(ValueAnimator.REVERSE);
animScale.setDuration(5000);
animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radius = (float) animation.getAnimatedValue();
}
});
animSet = new AnimatorSet();
animSet.play(valueAnimator).with(animColor).with(animScale);
animSet.setDuration(5000);
animSet.setInterpolator(interpolatorType);
animSet.start();
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, radius, mPaint);
}
public void setInterpolatorType(int type ) {
switch (type) {
case 1:
interpolatorType = new BounceInterpolator();
break;
case 2:
interpolatorType = new AccelerateDecelerateInterpolator();
break;
case 3:
interpolatorType = new DecelerateInterpolator();
break;
case 4:
interpolatorType = new AnticipateInterpolator();
break;
case 5:
interpolatorType = new LinearInterpolator();
break;
case 6:
interpolatorType=new LinearOutSlowInInterpolator();
break;
case 7:
interpolatorType = new OvershootInterpolator();
default:
interpolatorType = new LinearInterpolator();
break;
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public void pauseAnimation() {
if (animSet != null) {
animSet.pause();
}
}
public void stopAnimation() {
if (animSet != null) {
animSet.cancel();
this.clearAnimation();
}
}
}
TimeInterpolator 介绍(插值器)
Interpolator的概念其实我们并不陌生,在补间动画中我们就使用到了。他就是用来控制动画快慢节奏的;而在属性动画中,TimeInterpolator 也是类似的作用;TimeInterpolator 继承自Interpolator。我们可以继承TimerInterpolator 以自己的方式控制动画变化的节奏,也可以使用Android 系统提供的Interpolator。
下面都是系统帮我们定义好的一些Interpolator,我们可以通过setInterpolator 设置不同的Interpolator。
这里我们使用的Interpolator就决定了 前面我们提到的fraction。变化的节奏决定了动画所执行的百分比。不得不说,这么ValueAnimator的设计的确是很巧妙。