简介
属性动画是API 11新加入的特性,和View动画不同,它对作用对象进行了扩展,属性动画可以对任意对象做动画,也不像View动画只支持四种简单的变化。
属性动画的默认时间是300ms,默认频率是10ms/帧
ObjectAniamtor
先看看一个简单例子的实现效果,使用ObjectAnimator,它使用起来比较简单
布局就是一个ImageView,看看实现代码
//透明度变化动画
public void alpha(View view) {
// 1、通过调用ofFloat()方法创建ObjectAnimator对象,并设置目标对象、需要改变的目标属性名、初始值和结束值;
ObjectAnimator mAnimatorAlpha = ObjectAnimator.ofFloat(mImageView, "alpha", 1.0f, 0.0f);
//2、设置动画的持续时间、是否重复及重复次数属性;
mAnimatorAlpha.setRepeatMode(Animation.REVERSE);
mAnimatorAlpha.setRepeatCount(1);
mAnimatorAlpha.setDuration(1000);
//3、启动动画
mAnimatorAlpha.start();
}
//翻转动画,翻转360度
public void flip(View view) {
ObjectAnimator visibleToInVisable = ObjectAnimator.ofFloat(mImageView, "rotationX", 0.0f, 360.0f);
//设置插值器
visibleToInVisable.setInterpolator(new LinearInterpolator());
visibleToInVisable.setDuration(1000);
visibleToInVisable.start();
}
//缩放动画
public void scale(View view) {
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scale_anim);
animator.setTarget(mImageView);
animator.start();
}
//平移动画
public void translate(View view) {
ObjectAnimator mAnimatorTranslateX = ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, screenWidth / 2);
mAnimatorTranslateX.setRepeatMode(Animation.REVERSE);
mAnimatorTranslateX.setRepeatCount(1);
mAnimatorTranslateX.setDuration(1000);
ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(mImageView, "translationY", 0.0f, screenHeight / 2);
mAnimatorTranslateY.setRepeatMode(Animation.REVERSE);
mAnimatorTranslateY.setRepeatCount(1);
mAnimatorTranslateY.setDuration(1000);
mAnimatorTranslateX.start();
mAnimatorTranslateY.start();
}
//旋转动画
public void rotate(View view) {
ObjectAnimator mAnimatorRotate = ObjectAnimator.ofFloat(mImageView, "rotation", 0.0f, 360.0f);
mAnimatorRotate.setRepeatMode(Animation.REVERSE);
mAnimatorRotate.setRepeatCount(1);
mAnimatorRotate.setDuration(2000);
mAnimatorRotate.start();
}
就是根据ObjectAnimator的ofFloat(Object target, String propertyName, float... values)构造得到一个ObjectAnimator,可以看到第一个参数是Object类型的,意味着我们可以传入自定义的类型,而不局限于补间动画只能用view来做,扩展性也就更好。第二个参数是动画名称,可以传递系统定义好的,比如上面的几个,也可以自己随便写,然后在属性变化的监听里改变。第三个参数就是属性值,如果只有一个的话会默认为结束值。
上面缩放动画是用xml实现的,需要在res下面建一个animator文件夹,然后创建相应的xml动画,如下
组合动画
现在要多个动画效果一起实现怎么办,有下面几种方式
1.PropertyValuesHolder
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f);
ObjectAnimator animator1 = ObjectAnimator.ofPropertyValuesHolder(mImageView, alpha, scaleX);
animator1.setDuration(1000);
animator1.start();
2.监听属性变化
ObjectAnimator animator = ObjectAnimator.ofFloat(mImageView, "lzy", 0.0f, 1.0f);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
mImageView.setAlpha(value);
mImageView.setScaleX(1 - value * 0.5f);
}
});
animator.start();
设置属性变化值在0-1之间,然后在onAnimationUpdate中监听得到,通过这个值调用view自身的方法改变。
3.AnimatorSet
AnimatorSet mAnimatorSet = new AnimatorSet();
ObjectAnimator mAnimatorSetRotateX = ObjectAnimator.ofFloat(mImageView, "rotationX", 0.0f, 360.0f);
mAnimatorSetRotateX.setDuration(3000);
ObjectAnimator mAnimatorSetRotateY = ObjectAnimator.ofFloat(mImageView, "rotationY", 0.0f, 360.0f);
mAnimatorSetRotateY.setDuration(3000);
ObjectAnimator mAnimatorScaleX = ObjectAnimator.ofFloat(mImageView, "scaleX", 1.0f, 0.2f);
mAnimatorScaleX.setRepeatCount(1);
mAnimatorScaleX.setRepeatMode(Animation.REVERSE);
mAnimatorScaleX.setDuration(1500);
ObjectAnimator mAnimatorScaleY = ObjectAnimator.ofFloat(mImageView, "scaleY", 1.0f, 0.2f);
mAnimatorScaleY.setRepeatCount(1);
mAnimatorScaleY.setRepeatMode(Animation.REVERSE);
mAnimatorScaleY.setDuration(1500);
ObjectAnimator mAnimatorScaleY2 = ObjectAnimator.ofFloat(mImageView, "scaleY", 1.0f, 0.2f);
mAnimatorScaleY2.setRepeatCount(1);
mAnimatorScaleY2.setRepeatMode(Animation.REVERSE);
mAnimatorScaleY2.setDuration(1500);
mAnimatorSet.play(mAnimatorSetRotateY)
.with(mAnimatorScaleX)
.with(mAnimatorScaleY)
.before(mAnimatorSetRotateX).before(mAnimatorScaleY2);
mAnimatorSet.start();
通过animationSet来实现,它提供了一个play()方法,传入Animator返回一个Builder,这个Builder中有以下四个方法
with(Animator anim),表示同时执行
before(Animator anim),表示将现有动画插入到传入动画之前执行,也就是后执行
after(Animator anim),与before相反
after(long delay),表示延迟多久执行
在测试的时候发现不能重复传入一个动画,所以又写了一个mAnimatorScaleY2 。
AnimationSet中还有playTogether(Animator... items)表示同时执行,playSequentially(Animator... items)表示异步执行。
下面做一个让Button增加宽度的动画效果
属性动画的大致原理:属性动画要求动画作用的对象提供该属性的get和set方法,就像上面的"scaleX"属性,它具有getScaleX和setScale方法,属性动画会根据传递的初始值和结束值,去调用set的方法设置当前的值,如果没有传递初始值会调用get方法去获取初始值。
然后Button中并没有我们想要的get和set方法,所以针对这种问题官方文档给出了下面的解决方法
1.如果有权限的话,给对象加上get和set方法
2.用一个类来包装原始对象,间接为其提供get和set方法
3.采用ValueAnimator,监听动画变化过程,自己实现属性的变化
我们现在以第二种方式来实现以上效果,代码如下
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 1000).setDuration(1000).start();
}
});
//用此类来封装View,间接提供get、set方法
private static class ViewWrapper {
private View mView;
public ViewWrapper(View view) {
mView = view;
}
public void setWidth(int width) {
mView.getLayoutParams().width = width;
mView.requestLayout();
}
public int getWidth() {
return mView.getLayoutParams().width;
}
}
在这个类中提供了width的get和set方法,getWidth()就是获取View当前的宽度,上面说过在没有指定初始值时会调用获得初始值,而setWidth()方法会不断被调用,直至动画结束去改变View的宽度。
ValueAnimator
ValueAnimator是ObjectAnimator的父类,也就可以理解为ObjectAnimator是一个封装好的ValueAnimator,使其使用起来更加的简单。但是作为父类的ValueAnimator也就使用起来更加的灵活多变。ValueAinamtor本身不会作用于任何对象,直接使用不会有动画效果,可以理解为它对一个值做动画,我们通过这个不断变化的值在监听函数中去修改相应的属性。
看看ValueAnimator的几种构造函数:
ofInt(int... values)
ofArgb(int... values)
ofFloat(float... values)
ofPropertyValuesHolder(PropertyValuesHolder... values)
ofObject(TypeEvaluator evaluator, Object... values)
前三种参数都是初始值到结束值的变化范围,很简单
ofPropertyValuesHolder代表多种Animator的集合,下面会介绍具体的使用
ofObject很明显它的参数类型是Object类型,大多是我们自定义的类型,第一个参数TypeEvaluator 是估值器,因为像上面的int之类的系统都有定义相应的估值器所以不需要我们传入,然后我们自定义的Object并没有,所以需要自己的来写。
来看看系统的IntEvaluator
public class IntEvaluator implements TypeEvaluator {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
估值器都需要继承自TypeEvaluator,实现里面的evaluate方法,参数分别是完成的百分比,开始值和结束值,文档解释也告诉我们返回的值只需要用结束值减去开始值乘以fraction 然后加上开始值就ok,从而实现值的过渡,所以自定义TypeEvaluator也很简单了。
下面看例子,还是先看效果在撸代码
private void marginValueAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, width - mImageView.getWidth());
//监听变化过程
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前值
int animatedValueMargin = (int) animation.getAnimatedValue();
ViewGroup.MarginLayoutParams
layoutParams = (ViewGroup.MarginLayoutParams) mImageView.getLayoutParams();
layoutParams.leftMargin = animatedValueMargin;
mImageView.setLayoutParams(layoutParams);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(3);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.setTarget(mImageView);
valueAnimator.start();
}
这是个简单的设置view距离左边的动画,构造了ValueAnimator后监听它的更新,在onAnimationUpdate中改变属性值,通过animation.getAnimatedValue()获得当前的值,然后用于更新属性状态,实现动画。
public void scaleValueAnimator() {
//1.设置目标属性名及属性变化的初始值和结束值
PropertyValuesHolder mPropertyValuesHolderScaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f);
PropertyValuesHolder mPropertyValuesHolderScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f);
ValueAnimator mAnimator = ValueAnimator.ofPropertyValuesHolder(mPropertyValuesHolderScaleX, mPropertyValuesHolderScaleY);
//2.为目标对象的属性变化设置监听器
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 3.根据属性名获取属性变化的值分别为ImageView目标对象设置X和Y轴的缩放值
float animatorValueScaleX = (float) animation.getAnimatedValue("scaleX");
float animatorValueScaleY = (float) animation.getAnimatedValue("scaleY");
mImageView.setScaleX(animatorValueScaleX);
mImageView.setScaleY(animatorValueScaleY);
}
});
//4.为ValueAnimator设置自定义的Interpolator
mAnimator.setInterpolator(new CustomInterpolator());
//5.设置动画的持续时间、是否重复及重复次数等属性
mAnimator.setDuration(2000);
mAnimator.setRepeatCount(3);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
//6.为ValueAnimator设置目标对象并开始执行动画
mAnimator.setTarget(mImageView);
mAnimator.start();
}
这里通过PropertyValuesHolder来控制实现两种属性的变化,其实这里x和y两个方向的缩放值都是1.0f到0.0f,可以用一个ValueAnimator.ofFloat()来完成,这里主要是为了演示
首先是要通过PropertyValuesHolder.ofFloat来创建了两个PropertyValuesHolder对象,其中第一个参数是属性名,可以任意取,可以看到在onAnimationUpdate中通过这个属性名来获取不同PropertyValuesHolder的变化值。然后再通过ValueAnimator.ofPropertyValuesHolder来构造ValueAnimator对象就可以,也很容易看懂。
上面那个类似抛物线的圆是采用ValueAnimator.ofObject来实现的。
1.首先要定义一个Point类,里面用于存放x、y坐标
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
之前说过要使用ValueAnimator.ofObject需要自定义TypeEvaluator,下面看看定义的PointEvaluator,可以看到和IntEvaluator一样,主要还是计算变化值
public class PointEvaluator implements TypeEvaluator {
//TypeEvaluator简单来说是实现了初始值和结束值的平滑过渡
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point start = (Point) startValue;
Point end = (Point) endValue;
//开始值减去结束值乘以fraction再加上开始值就是当前的值
float x = start.getX() + fraction * (end.getX() - start.getX());
float y = start.getY() + fraction * (end.getY() - start.getY());
Point point = new Point(x, y);
return point;
}
下面自定义一个CircleView类
public class CircleView extends View {
private static final float Radius = 40.0f;
private Point mPoint;
private Paint mPaint;
public CircleView(Context context) {
super(context);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.RED);
// mPoint = new Point(50, 50);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
if (mPoint == null) {
mPoint = new Point(50, 50);
canvas.drawCircle(mPoint.getX(), mPoint.getY(), Radius, mPaint);
startAnimation();
} else {
canvas.drawCircle(mPoint.getX(), mPoint.getY(), Radius, mPaint);
}
}
private void startAnimation() {
//创建开始和结束点坐标
Point start = new Point(50, 50);
Point end = new Point(getWidth(), getHeight());
ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//把每一帧的值传给mPoint,绘制界面
mPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(5000);
animator.start();
}
}
这个类也比较简单,主要就是在onDraw里面画一个圆,然后根据Point的变化不断调用onDraw方法来改变圆的位置,实现动画,这也就是ValueAnimator.ofObject的用法。