1. 前言
这是动画的第三篇属性动画,属性动画是android动画里面用的最多的, 也是最重要的。android中三种动画的使用文章如下:
2. 介绍
- 属性动画是指通过不断的改变对象的属性,进行动画。 其中涉及到三个类:ValueAnimator、ObjectAnimator、ViewPropertyAnimator 的使用。
- 属性动画能作用于任何对象, 不像补间动画一样, 只能作用在view上。 总结来说就是属性动画可以通过改变任何对象的任何属性来操作动画。
3. 使用
3.1 ValueAnimator使用
先来一张动画效果图

3.1.1 ValueAnimator 介绍
- ValueAnimator是动画中最重要的一个类,后面要学的ObjectAnimator也是继承于它实现的。
- ValueAnimator,从名字就可以看出,这是一个通过值来操作动画的类。 我们只需要指定一个开始值和结束值,那么这个类就会自动帮我们计算好, 在指定的动画时间内, 每个时间点对应的值是多少。
3.1.2 ofInt、ofFloat、ofArgb
通过调用ValueAnimator.ofInt这个方法, 我们就能构建一个整型的ValueAnimator,如下:
private void valueAnimatorOfInt() {
//获取到屏幕宽度
DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
int widthPixels = displayMetrics.widthPixels;
//起始值为btn最开始的坐标的x位置, 结束值为 屏幕的最右边,
ValueAnimator valueAnimator = ValueAnimator.ofInt((int) btnOfInt.getTranslationX(), widthPixels);
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(2);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setStartDelay(500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int currentValue = (int) valueAnimator.getAnimatedValue();
btnOfInt.setTranslationX(currentValue);
System.out.println("current value:" + currentValue);
}
});
valueAnimator.start();
}
解释:
- ValueAnimator.ofInt: 传入一个int类型的开始值和结束值,构建动画对象。
- valueAnimator.setDuration:设置动画时间
- valueAnimator.setRepeatCount: 设置动画重复次数。
- valueAnimator.setRepeatMode:设置动画重复模式, 正序重复或者是逆序重复。
- valueAnimator.setStartDelay:设置动画开始后的延迟执行时间。
- valueAnimator.addUpdateListener: 设置动画监听器,监听数值的变化,获取到当前的数值,然后去设置view的属性。这里设置的是view的x位置。效果如上面的gif图。
- valueAnimator.start:启动动画
使用十分简单,ValueAnimator.ofFloat,ValueAnimator.ofArgb 都是同理,传入开始值和结束值, 然后监听数值的变化, 设置给你要进行动画的view。 下面定义了两个方法,是对ofFloat,ofArgb的使用:
private void valueAnimatorOfFloat() {
//获取到屏幕宽度
DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
int widthPixels = displayMetrics.widthPixels;
//起始值为btn最开始的宽度, 结束值为 屏幕的宽度。
ValueAnimator valueAnimator = ValueAnimator.ofFloat(btnofFloatWidth,widthPixels);
valueAnimator.setDuration(2000);
// valueAnimator.setRepeatCount(2);
// valueAnimator.setRepeatMode(ValueAnimator.RESTART);
// valueAnimator.setStartDelay(500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float currentValue = (float) valueAnimator.getAnimatedValue();
btnOfFloat.getLayoutParams().width = (int) currentValue;
//重新绘制
btnOfFloat.requestLayout();
System.out.println("current value:" + currentValue);
}
});
valueAnimator.start();
}
private void valueOfRGB(){
ValueAnimator valueAnimator = ValueAnimator.ofArgb(Color.BLUE, Color.RED);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int currentValue = (int) valueAnimator.getAnimatedValue();
btnOfRgb.setBackgroundColor(currentValue);
System.out.println("current value:" + currentValue);
}
});
valueAnimator.start();
}
3.1.3 ValueAnimator.ofObject
在介绍中说过, 属性动画可以作用在任意对象。 比如我有一个Point类,类中有x,y两个属性用来表示坐标。 通过Point创建两个对象,一个pointA,PointB。如何从pointA变化到pointB呢?
public class MyView extends View {
private float RADIUS = 50f;
private Point currentPoint;
private Paint mPaint ;
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
currentPoint = new Point(RADIUS,RADIUS);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas);
}
private void drawCircle(Canvas canvas){
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x,y,RADIUS,mPaint);
}
public void startAnimation (){
Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Point point = (Point)valueAnimator.getAnimatedValue();
currentPoint = point;
invalidate();
System.out.println("current point,x:"+currentPoint.getX()+"y:"+currentPoint.getY());
}
});
valueAnimator.start();
}
}
自定义了一个view画一个圆。startAnimation方法可以使这个圆从startPoint移动到endPoint。
解释:
-
ValueAnimator.ofObject: 通过ofObject创建一个动画对象, 传入估值器、开始对象和结束对象。
-
估值器是个什么东西?
估值器就是用来描述动画逻辑的,说白了就是计算在对应的时间点上, 当前的值是多少。 之前的ofInt、ofFloat、ofArgb 这些, 系统都帮我们写好了,计算好了。 但是如果是从一个任意对象变化到另一个对象。 就需要我们自己写估值器了。
/**
* point 估值器
*/
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;
}
}
实现TypeEvaluator接口就行了, 接口会传当前动画进度、开始对象和结束对象进来。 你只需要根据这三个东西来计算出当前动画进度的对象并返回就可以了。
3.1.4 PropertyValuesHolder
上面都是对一个属性变化进行动画, 如果是多个呢,比如我要改一个按钮的宽和高。 那就要用到PropertyValuesHolder。
private void valueOfPropertyValues(){
DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
int widthPixels = displayMetrics.widthPixels;
int heightPixels = displayMetrics.heightPixels;
PropertyValuesHolder holder1 = PropertyValuesHolder.ofInt("width", btnWidth, widthPixels);
PropertyValuesHolder holder2 =PropertyValuesHolder.ofInt("height",btnHeight,heightPixels-btnOfValuesProperty.getTop());
ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(holder1, holder2);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int width = (int) valueAnimator.getAnimatedValue("width");
int height = (int) valueAnimator.getAnimatedValue("height");
btnOfValuesProperty.getLayoutParams().width = width;
btnOfValuesProperty.getLayoutParams().height = height;
btnOfValuesProperty.requestLayout();
System.out.println("width:"+width+"height:"+height);
}
});
valueAnimator.start();
}
解释:
-
PropertyValuesHolder.ofInt: 和刚刚的ValueAnimator.ofInt 一样, 都是传入一个开始值和结束值。创建出来一个要变化的属性对象。
-
ValueAnimator.ofPropertyValuesHolder: 把创建的多个要变化的属性对象传进来,构建出一个动画对象。
-
valueAnimator.getAnimatedValue: 通过属性名, 获取到属性的当前值。拿到多个属性值后, 我们就可以来重新设置按钮的宽高形成动画啦。
3.1.4 ValueAnimator使用总结
我们传入一个开始值和结束值给到ValueAnimator, 它会帮我们计算出在当前时间,这个值是多少。 我们通过监听获取到这个值,重新设置给view等对象,让view重绘。这就是ValueAnimator做动画的原理。
3.2 ObjectAnimator使用
上面说的ValueAnimator我们是通过监听值的变化来给view设置属性,重新绘制,完成动画。 那么ObjectAnimator就是我们把属性名给它,让他自己来根据属性值的变化完成动画。

3.2.1 translate、rotate、scale、alpha
比如我们要通过ObjectAnimator来改变view的属性,让view平移、选择、缩放、透明:
private void translate() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btnTranslate, "translationX", 0, 500, 200);
objectAnimator.setDuration(3000);
objectAnimator.start();
}
private void rotate() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btnRotate, "rotation", 0, 200, 300);
objectAnimator.setDuration(3000);
objectAnimator.start();
}
private void scale() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btnScale, "ScaleX", 0f, 0.5f, 1.1f);
objectAnimator.setDuration(3000);
objectAnimator.start();
}
private void alpha() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(btnAlpha, "Alpha", 0, 0.5f, 1f);
objectAnimator.setDuration(3000);
objectAnimator.start();
}
解释:
- ObjectAnimator.ofFloat: 传入要动画的对象,和对象动画要变化的属性名,属性的开始值和结束值。 ofInt、ofArgb等同理。
3.2.2 自定义属性
上面那些是view本地有自带的setTranslationX、setRotation等方法,才可以通过translationX、rotataion这些属性来完成动画。 如果我们自定义的view,要变化其他属性呢,比如一个进度条, 通过变化进度属性值来完成进度的动画。那就需要我们自己定义属性和属性对应的setter方法,并在setter方法中,重绘view。
- 先自定义view,给progress添加setter方法,在setter中重绘。
public class ProgressView extends View {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float progress = 0;
private float RADIUS = 80;
public ProgressView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public float getProgress() {
return progress;
}
public void setProgress(float progress) {
this.progress = progress;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float centerX = getWidth()/2;
float centerY = getHeight()/2;
mPaint.setColor(Color.RED);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(25);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(centerX - RADIUS, centerY - RADIUS, centerX + RADIUS, centerY + RADIUS);
canvas.drawArc(rectF,0,3.6f*progress,false,mPaint);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText((int) progress + "%", centerX, centerY - (mPaint.ascent() + mPaint.descent()) / 2, mPaint);
}
}
- 变化属性值,完成动画。
private void custom() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(progressView, "progress", 0, 90);
objectAnimator.setDuration(5000);
objectAnimator.setInterpolator(new FastOutSlowInInterpolator());
objectAnimator.start();
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
objectAnimator.addListener(new AnimatorListenerAdapter() {
// @Override
// public void onAnimationStart(Animator animation) {
// super.onAnimationStart(animation);
// }
});
}
这样我们就可以对我们自定义view中的属性变化并完成动画了。
3.2.2 指定动画的关键帧
这里的关键帧就是用来指定,在某一帧的时候的属性。 比如动画到一半时候的那一帧, 我要把属性设置成什么什么。下面例子还是用上面那个进度条, 当动画到一半的时候,把进度设置成100。
private void keyFrame() {
//设置关键帧, 第一个参数是 完成度, 第二个参数是 属性值, 比如当完成度一半的时候(0.5),属性值(progress)给100;
Keyframe keyframe = Keyframe.ofFloat(0, 0);
Keyframe keyframe1 = Keyframe.ofFloat(0.5f, 100);
Keyframe keyframe2 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe, keyframe1, keyframe2);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(progressView, holder);
objectAnimator.setDuration(5000);
objectAnimator.start();
}
解释:
- Keyframe.ofFloat: 设置一个关键帧,传入动画进度,和属性值。
- PropertyValuesHolder.ofKeyframe:通过这些关键帧来创建多属性的对象。
- ObjectAnimator.ofPropertyValuesHolder: 通过多属性对象来创建动画对象。
3.3 ViewPropertyAnimator使用
ViewPropertyAnimator, 是直接通过view的animate来对view的一些自带属性进行动画。 animate返回的对象就是ViewPropertyAnimator类型。
private void viewProperty(){
btnViewProperty.animate().alpha(0).setDuration(2000).rotation(360).translationX(300);
}
3.4 组合动画
很多时候, 单个动画满足不了需求。 需要将多个动画变成一连串的组合动画。 比如讲移动、旋转、透明组合起来, 一起或者先后完成动画。
private void animatorSet() {
// AnimatorSet.playTogether(Animator... anim) : 将动画组合一起执行
// AnimatorSet.playSequentially(Animator... anim) : 将动画组合有序执行
// AnimatorSet.play(Animator anim) :播放当前动画
// AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行
// AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行
// AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行
// AnimatorSet.before(Animator anim) : 将现有动画插入到传入的动画之前执行
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int widthPixels = displayMetrics.widthPixels;
ObjectAnimator translationX = ObjectAnimator.ofFloat(btnSet, "translationX", 0, widthPixels);
ObjectAnimator rotation = ObjectAnimator.ofFloat(btnSet, "rotation", 0, 360);
ObjectAnimator alpha = ObjectAnimator.ofFloat(btnSet, "alpha", 1, 0, 1);
AnimatorSet animatorSet = new AnimatorSet();
// animatorSet.playTogether(translationX,rotation,alpha);
// animatorSet.playSequentially();
animatorSet.play(rotation).with(alpha).before(translationX);
animatorSet.setDuration(5000);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.start();
}
解释:
- AnimatorSet: 组合动画类
- AnimatorSet.playTogether:将动画组合一起执行
- AnimatorSet.playSequentially: 将动画组合有序执行
- AnimatorSet.play:播放当前动画
- AnimatorSet.after:将现有动画延迟x毫秒后执行
- AnimatorSet.with(Animator:将现有动画和传入的动画同时执行
- AnimatorSet.after:将现有动画插入到传入的动画之后执行
- AnimatorSet.before:将现有动画插入到传入的动画之前执行
3.5 xml中编写动画
在/res/animator/目录下新建xml文件:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
<objectAnimator
android:duration="2000"
android:propertyName="translationX"
android:valueFrom="-200"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<set android:ordering="together" >
<objectAnimator
android:duration="3000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" >
</objectAnimator>
<set android:ordering="sequentially" >
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" >
</objectAnimator>
</set>
</set>
</set>
<!-- 将一个视图先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作-->
xml中使用的比较少,这里也不详细写。可以参考下面郭霖文章写的。
4.总结
- 属性动画可以作用在任意对象,任意属性。
- 属性动画更改的是真实的对象的属性, 不像补间动画一样,没有更改对象属性。
- 属性动画的使用主要涉及到三个类:ValueAnimator、ObjectAnimator、ViewPropertyAnimator。
- ValueAnimator和ObjectAnimator的原理都是通过不断改变对象的属性后重绘对象。来使对象完成动画。 ValueAnimator需要我们自己重绘,ObjectAnimator是自动重绘。
- 属性动画可以操作多个属性、关键帧、组合动画等。
- 系统View的对象还提供了ViewPropertyAnimator让我们直接操作view自带的一些属性的动画。
- xml编写动画的优点是可读性、复用性高。缺点是灵活性差。
5. 完整demo地址
6. 参考文章
7. 历史文章目录