当前Android应用开发涉及的动画主要有三种,分别是:视图动画,逐帧动画,属性动画。
逐帧动画
是在 xml 中定义好一系列图片之后,使用AnimationDrawable来顺序播放的动画。
位置:/res/drawable/frame_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
</animation-list>
使用逐帧动画时,避免图片较多或图片较大,会引起OOM。
ImageView imageView = findViewById(R.id.image);
imageView .setBackgroundResource(R.drawable.rocket_thrust);
AnimationDrawable rocketAnimation =
(AnimationDrawable)imageView.getBackground();
rocketAnimation.start();//不可在onCreate中执行,要等view首次绘制完毕。
视图动画
针对View的影像进行缩放、透明度渐变、旋转、平移或组合使用,从而产生动画的效果。
Java类名 | xml关键字 | 描述 |
---|---|---|
AlphaAnimation | < alpha> | 渐进透明度动画效果 |
RotateAnimation | < rotate> | 旋转动画效果 |
ScaleAnimation | < scale> | 渐进尺寸伸缩动画效果 |
TranslateAnimation | < translate> | 平移动画效果 |
AnimationSet | < set> | 组合其他动画元素或set元素的容器 |
位置:res/anim/view_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillAfter="true"
android:shareInterpolator="true">
<rotate
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" />
<alpha
android:duration="200"
android:fillAfter="true"
android:fromAlpha="0"
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="-1"
android:repeatMode="reverse"
android:toAlpha="1" />
</set>
- duration:动画持续时间;
- fillAfter:动画结束时,是否保持最后一帧;
- repeatCount:动画循环的次数,默认0次不循环,-1为无线循环;
- repeatMode:动画循环模式,reverse指从动画结束处循环,restart指从动画开始处循环。
- interpolator:插值器,控制动画执行的速度。
- shareInterpolator:是否与set容器中其他动画元素共享插值器,false为各自使用自己的插值器。
- fromDegrees:旋转动画起始的角度,单位度,浮点值。
- pivotX:旋转中心的X坐标,n%表示相对于自身左边缘的 自身宽度的n%。
Animation animation= AnimationUtils.loadAnimation(this, R.anim.view_anim);
button.startAnimation(animation);
插值器
Interpolator是Animation类的一个xml属性,规定了从初始值过渡到结束值得渐变规律,视图动画中alpha、scale、rotate、translate、set动画元素都会继承此属性。
如下是系统内置的插值器实现:
插值器类名 | Resource ID | 描述 |
---|---|---|
AccelerateDecelerateInterpolator | @android:anim/accelerate_decelerate_interpolator | 系统默认插值器。在动画开始与结束时速度比较慢,在中间的时候加速。 |
AccelerateInterpolator | @android:anim/accelerate_interpolator | 在动画开始的地方速度比较慢,然后开始加速。 |
AnticipateInterpolator | @android:anim/anticipate_interpolator | 开始的时候向后然后向前甩。 |
AnticipateOvershootInterpolator | @android:anim/anticipate_overshoot_interpolator | 开始的时候向后然后向前甩一定值后返回最后的值。 |
BounceInterpolator | @android:anim/bounce_interpolator | 动画结束的时候弹起。 |
CycleInterpolator | @android:anim/cycle_interpolator | 动画循环播放特定的次数,速度沿着正弦曲线。 |
DecelerateInterpolator | @android:anim/decelerate_interpolator | 在动画开始的地方快然后慢。 |
LinearInterpolator | @android:anim/linear_interpolator | 常量速度。 |
OvershootInterpolator | @android:anim/overshoot_interpolator | 向前甩一定值后再回到原来位置。 |
PathInterpolator | @android:anim/path_interpolator | 新增,按照定义坐标路径动画。 |
属性动画
利用插值器和估值器,来计算出各个时刻 View 的属性,然后通过改变 View 的属性来实现 View 的动画效果。
- 时间插值器(Interpolator):作用是根据时间的流逝的百分比来计算属性改变的百分比。
- 类型估值器(TypeEvaluator):根据当前属性改变的百分比来计算改变后的属性值。
属性动画中的TimeInterpolator插值器兼容Interpolator接口,故视图插值器可在属性动画中使用。
系统内置的插值器上文已有介绍,系统内置的估值器有: ArgbEvaluator、FloatArrayEvaluator、FloatEvaluator、IntArrayEvaluator、IntEvaluator、PointFEvaluator、RectEvaluator。
如颜色估值器 ArgbEvaluator:
public class ArgbEvaluator implements TypeEvaluator {
private static final ArgbEvaluator sInstance = new ArgbEvaluator();
public static ArgbEvaluator getInstance() {
return sInstance;
}
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
}
ArgbEvaluator和颜色有关系的,evaluate函数里面分别对startValue和endValue做了拆分。int总共32位,8位8位的去拆分,分别对应A,R,G,B。 然后对A,R,G,B每个都做fraction转换,在组合到一起形成一个int值。
ValueAnimator:通过从初始值到结束值得平滑过渡实现动画效果。addUpdateListener可监听动画执行过程。
示例1:点击红球落下并弹起。
private final float RADIUS = 70F;
private float circleY = RADIUS;
public void startValueAnimation(){
circleY = RADIUS;
ValueAnimator anim = ValueAnimator.ofFloat(RADIUS, getHeight()/2-RADIUS);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circ= (float) animation.getAnimatedValue();
invalidate();
}
});
anim.setDuration(5000);
anim.setInterpolator(new BounceInterpolator());
anim.start();
}
onDraw()方法里重绘:
canvas.drawLine(0f,getHeight()/2,getWidth(),getHeight()/2,paint);//绘制中间横线
canvas.drawCircle(getWidth()/2,circleY,RADIUS,paint);//绘制圆饼
ValueAnimator类针对起始值和结束值进行动画操作,可借助动画进度监听回调,处理一些页面刷新动作。
ObjectAnimator:ObjectAnimator继承ValueAnimator,可以对任意对象的属性方法进行操作,达到动画效果。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
animator.setDuration(2000);
animator.start();
第一个参数表示动画目标对象,第二个参数表示对象属性名,后面的参数不固定,表示动画的初始和结束值。
组合动画:实际开发需求中,一般需要用到组合动画而不是单一动画。属性动画的组合借助AnimatotSet类实现。
- play(Animator anim):执行现有动画。
- after(Animator anim):将现有动画排在传入的动画之后执行。
- after(Animator anim):将现有动画延迟指定毫秒后执行。
- before(Animator anim):将现有动画排在传入的动画之前执行。
- with(Animator anim):将现有动画与传入的动画并行执行。
示例 2:先执行缩放动画,再并行执行旋转、平移、透明度动画。
ObjectAnimator scaleX = ObjectAnimator.ofFloat(textview, "scaleX", 1f, 0.5f, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0.5f, 1f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator translationX = ObjectAnimator.ofFloat(textview, "translationX",
textview.getTranslationX(), textview.getTranslationX()-150f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(translationX).with(alpha).after(scaleX);
animSet.setDuration(3000);
//监听动画状态
animSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//动画结束
}
});
//监听动画进度
animSet.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
animSet.start();
translationX是View左上角相对父容器左上角在X轴的偏移量,translationX默认值为0。
ViewPropertyAnimator
Android动画团队专门为View的常用属性方法封装的动画操作类,可以链式实现基本动画效果。
//将textview从原有颜色在5秒的时间内渐进到透明,同时缩小0.5倍。
textview.aniate().alpha(0f).scaleX(0.5f).setDruation(5000);
链式调用并行运行多种动画,但不支持更复杂的动画组合(如串式运行动画),适用于单个view的默认属性值的动画。
自定义估值器
属性动画的自定义估值器需要实现TypeEvaluator接口,并复写evaluate()方法。
- 自定义路径估值器 PathAnimEvaluator
/**
* 路径估值器
*/
public class PathAnimEvaluator implements TypeEvaluator<PointModel> {
PointModel pointModel = null;
/**
* 动画执行算法
*
* @param fraction 表示动画完成度,属性改变的百分比(可以大于1,可以小于0)
* @param startValue 动画初始值
* @param endValue 动画结束值
* @return 当前动画过渡值
*/
@Override
public PointModel evaluate(float fraction, PointModel startValue, PointModel endValue) {
//当前进度的x坐标 = 初始值+动画完成百分比*(结束值-初始值)。
float x = startValue.getX() + fraction * (endValue.getX() - startValue.getX());
float y = startValue.getY() + fraction * (endValue.getY() - startValue.getY());
pointModel.setX(x);
pointModel.setY(y);
return pointModel;//避免频繁new实例造成内存溢出。
}
}
从上述示例可知,对于任何实例对象,只要赋给他属性方法,并提供初始值和结束值,都可以实现从初始值到结束值的平滑过渡。
- 带动画的控件 AnimationView:从起点到终点,圆饼滑动。
public class AnimationView extends View {
private final float RADIUS = 100f;
//属性名
private PointModel pointModel;
private Paint paint;
public AnimationView(Context context) {
super(context);
init(context, null, 0);
}
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
paint = new Paint();
paint.setColor(Color.RED);
}
public PointModel getPointModel() {
return pointModel;
}
public void setPointModel(PointModel pointModel) {
this.pointModel = pointModel;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (pointModel == null) {
pointModel = new PointModel(RADIUS, RADIUS);
startAnimation();
}
canvas.drawCircle(pointModel.getX(), pointModel.getY(), RADIUS, paint);
}
private void startAnimation() {
//PointModel只是一个带x,y坐标的实体类
ObjectAnimator translate = ObjectAnimator.ofObject(this,
"pointModel",
new PathAnimEvaluator(),
new PointModel(RADIUS, RADIUS),
new PointModel(RADIUS, getHeight() - RADIUS));
translate.setDuration(10000);
translate.setInterpolator(new BounceInterpolator());
translate.start();
}
}
动画在执行过程中会多次反射调用setPointModel()方法并赋新值,在这里需要添加触发UI刷新的操作,确保动画有效。
如果动画没有初始值,那么就会使用get方法提供的初始值,若还没有则会使用该类型的系统默认值或者报错。
自定义插值器
属性动画自定义插值器需实现TimeInterpolator接口,并复写getInterpolation()方法。
从上文自定义估值器可知,fraction表示动画完成的百分比,这是系统调用插值器的getInterpolation()方法得出的。
- 自定义速率插值器 PathAnimInterpolator
public class PathAnimInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
//实现先减速后加速的效果
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
如上在AnimationView中替换即可。
插值器和估值器的关系
从动画绘制的流程角度来说:
- 插值器Interpolator会根据时间流逝的百分比计算出当前属性值改变的百分比,即为TypeEvaluator中的fraction;
- 然后估值器TypeEvaluator会根据属性值改变的百分比fraction并结合初始值和结束值,计算出动画当前进度的属性值;
- 然后估值器反射调用属性set方法更新属性值,并刷新UI触发onDraw重绘,就实现了动画的效果。
注意事项
- 在activity销毁的时候,一定确保动画关闭,资源回收,避免内存泄露。
- 视图动画不能改变view的属性,只是对其影像做动画,需要view.clearAnimation()后方可正常操作其属性。
- 逐帧动画避免图片过大、过多,容易造成内存泄露。
- 开启硬件加速,可以提升动画的流畅性。
- 动画操作里,尽量用dp,而不是px,处理好屏幕适配问题。
引用
Android属性动画完全解析(上),初识属性动画的基本用法
自定义控件三部曲之动画篇(七)——ObjectAnimator基本使用
Android属性动画ObjectAnimator源码简单分析