qq群:614530228
Android动画机制
在Android 3.0之前的版本,我们能使用的动画类型有两种,分别是逐帧动画和补间动画;在Android 3.0发布时,Android SDK又为开发者带来了更加强大灵活的属性动画,使得实现复杂的动画效果更加容易;随着时间的推进,在Android 4.4中,Android SDK又为开发者带来了android.transition
框架,这使得开发者可以通过一种更直观的方式定义动画效果。
1.1 逐帧动画(Frame Animation)
逐帧动画也叫Drawable Animation
,是最简单最直观的动画类型,它利用人眼的视觉暂留效应----也就是光对视网膜所产生的视觉在光停止作用后,仍会保留一段时间的现象。
在Android中实现逐帧动画,就是由设计师给出一系列状态不断变化的图片,开发者可以指定动画中每一帧对应的图片和持续的时间,然后就可以开始播放动画了。具体而言,有两种方法可以定义逐帧动画,分别是采用XML资源文件和代码实现。
1.1.1 XML资源文件方式
这是最常使用的方式,首先我们将每一帧的图片放到res/drawable
目录中,然后在res/anim
目录中新建一个动画XML文件,在这个文件中使用
标签来定义动画帧序列,使用
标签来定义动画的每一帧,并在其中指定帧的持续时间等属性,格式如下。
其中android.oneshot
用来控制动画是否循环播放,如果取值为true,表示动画不会循环播放,否则动画将会循环播放;android:duration
用来指定每一帧的播放持续时间。
1.1.2 代码方式
在代码中定义逐帧动画也很简单,但不常用,语句如下。
AnimationDrawable animDrawable = new AnimationDrawable();
for (int i = 1; i < 5; i++) {
int id = getResources().getIdentifier("common_loading_" + i, "drawable", getPackageName());
Drawable drawable = getResources().getDrawable(id);
animDrawable.addFrame(drawable, 120);
}
imageView.setBackgroundDrawable(animaDrawable);
animDrawable.setOneShot(false);
定义好逐帧动画之后,可以在符合某个条件时除非或者停止动画的播放,伪代码如下。
// 获取AnimationDrawable对象实例,用来控制动画的播放和停止
AnimationDrawable animDrawable = (AnimationDrawable) imageView.getBackground();
//动画的播放
animDrawable.start();
//动画的停止
animDrawable.stop();
1.2 补间动画(Tween Animation)
补间动画是值开发者无须定义动画过程中的每一帧,只需要定义动画的开始和结束这两个关键帧,并指定动画变化的时间和方式等,然后交由Android系统进行计算,通过在两个关键帧之间插入渐变值来实现平滑过渡,从而对View的内容完成一系列的图形交换来实现的动画效果,主要包括四种基本的效果:透明度变化Alpha
、大小变化Scale
、位移变化Translate
及旋转变化Rotate
,这四种效果可以动态组合,从而实现复杂灵活的动画。同样,定义补间动画也可以分为XML资源文件和代码实现两种方式。不过在这之前,我们首先来认识一下插值器Interpolator
。
1.2.1 插值器Interpolator
前面说到的Android系统会在补间动画的开始和结束关键帧之间插入渐变值,它依据的便是Interpolator
。具体来说,Interpolator
会根据类型的不同,选择不同的算法计算出在补间动画期间所需要动态插入帧的密度和位置,Interpolator
负责控制动画的变化速度,使得前面所说的四种基本动画效果能够以匀速、加速、抛物线等多种速度进行变化。
具体到Android代码中,Interpolator类其实是一个空接口,它继承自TimeInterpolator
,TimeInterpolator
时间插值器允许动画进行非线性运动变换,如加速和减速等,该接口中只有float getInterpolation(float input)
这个方法。入参是一个0.0~1.0
的值,返回值可以小于0.0
也可以大于1.0
,代码如下。
public interface Interpolator extends TimeInterpolator {
}
public interface TimeInterpolator {
float getInterpolation(float input);
}
Android SDK默认提供了几个Interpolator的实现类,如下表所示。
插值器类型 | 功能说明 |
---|---|
AccelerateDecelerateInterpolator | 在动画开始与结束的地方速率改变比较慢,在中间的时候加速 |
AccelerateInterpolator | 在动画开始的地方速率改变比较慢,然后开始加速 |
AnticipateInterpolator | 开始的时候向后然后向前甩 |
AnticipateOvershootInterpolator | 开始的时候向后然后向前甩一定值后返回最后的值 |
BounceInterpolator | 动画结束的时候弹起 |
CycleInterpolator | 动画循环播放特定的次数,速率改变沿着正弦曲线 |
DecelerateInterpolator | 在动画开始的地方快然后慢 |
LinearInterpolator | 以常量速率改变 |
OvershootInterpolator | 向前甩一定值后再回到原来位置 |
PathInterpolator | 新增的,通过定义路径坐标,动画可以按照路径坐标来运行;注意这里的坐标并不是指十字坐标系,而是单方向,也就是可以从0~1,然后弹回0.6后再弹到0.8,知道最后时间结束。 |
当然,如果上述默认的插值器不符合我们的实际需求,开发者也可以通过实现Interpolator接口来编写自己的插值器。
Android SDK使用Animation类来表示动画类,上面介绍的补间动画四种基本类型分别对应所示的几个类。
动画类型 | 功能说明 |
---|---|
AlphaAnimation | 改变透明度的动画,创建动画时需要指定动画开始和结束的透明度,以及动画持续时间,透明度取值范围是0到1. |
ScaleAnimation | 缩放大小的动画,创建动画时需要指定动画开始和结束时在X轴和Y轴的缩放比,以及动画持续时间;同时由于缩放时以不同的点作为中心会产生不同的效果,因此也需要通过pivotX和pivotY指定缩放中心的坐标。 |
TranslateAnimation | 改变位置的动画,创建动画时需要指定动画开始和结束的X、Y坐标,以及动画的持续时间 |
RotateAnimation | 旋转动画,创建动画时需要指定动画开始和结束时的旋转角度,以及动画持续时间;同时由于旋转时以不同的点作为中心会产生不同的效果,因此也需要通过pivotX和pivotY指定旋转中心的坐标。 |
1.2.2 AlphaAnimation
XML方式就是在res/anim目录中新建xml文件,其中的内容如下。
AlphaAnimation的构造函数只有两个参数,分别是初始的透明度和结束的透明度,语句如下。
public AlphaAnimation(float fromAlpha, float toAlpha) {
mFromAlpha = fromAlpha;
mToAlpha = toAlpha;
}
在代码中实现透明度动画很简单,只需创建一个AlphaAnimation实例,然后设置动画持续时间即可,语句如下。
public void alpha() {
AlphaAnimation anim = new AlphaAnimation(0, 1);//透明度从0变化到1
anim.setDuration(500);//持续时间500毫秒
anim.setFillAfter(true);//动画结束后保留结束状态
mImageView.setAnimation(anim);
}
1.2.3 ScaleAnimation
XML实现示例如下。
在代码中实现,ScaleAnimation可用的构造函数有三个,代码如下。
public ScaleAnimation(float fromX, float fromY, float toX, float toY) {
}
public ScaleAnimation(float fromX, float fromY, float toX, float toY, float pivotX, float pivotY) {
}
public ScaleAnimation(float fromX, float fromY, float toX, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
}
public void scale() {
ScaleAnimation anim = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f);
anim.setDuration(2000);
anim.setFillAfter(true);
mImageView.setAnimation(anim);
}
可以看到,涉及到的入参比较多,具体函数如表所示。
参数 | 函数说明 |
---|---|
fromX | 动画开始时的X坐标的伸缩尺寸 |
toX | 动画结束时的X坐标的伸缩尺寸 |
fromY | 动画开始时的Y坐标的伸缩尺寸 |
toY | 动画结束时的Y坐标的伸缩尺寸 |
pivotX/pivotXValue | 缩放动画的中心点X坐标 |
pivotY/pivotYValue | 缩放动画的中心点Y坐标 |
pivotXType | 动画在X轴的伸缩模式,也就是中心点相对于哪个物件,取值是Animation.ABSOLUTE、Animation.RELATIVE_TO_SELF或Animation.RELATIVE_TO_PARENT |
pivotYType | 动画在Y轴的伸缩模式,也就是中心点相对于哪个物件,取值是Animation.ABSOLUTE、Animation.RELATIVE_TO_SELF或Animation.RELATIVE_TO_PARENT |
1.2.4 TranslateAnimation
XML实现示例如下
在代码实现中,TranslateAnimation可用的构造函数有两个,代码如下。
public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
}
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toYValue, int fromYType, float fromYValue, int toYType, float toYValue) {
}
其中,入参的含义如图所示
参数 | 函数说明 |
---|---|
fromXType | 动画开始时在X轴的位移模式,取值是Animation.ABSOLUTE、Animation.RELATE_TO_SELF或Animation.RELATIVE_TO_PARENT |
fromXValue/fromXDelta | 动画开始时当前View的X坐标 |
toXType | 动画结束时在X轴的位移模式,取值是Animation.ABSOLUTE、Animation.RELATE_TO_SELF或Animation.RELATIVE_TO_PARENT |
toXValue/toXDelta | 动画结束时当前View的X坐标 |
fromYType/fromYDelta | 动画开始时在Y轴的唯一模式,取值是Animation.ABSOLUTE、Animation.RELATE_TO_SELF或Animation.RELATIVE_TO_PARENT |
fromYValue/fromYDelta | 动画开始时View的Y坐标 |
toYType | 动画结束时在Y轴的位移模式,取值是Animation.ABSOLUTE、Animation.RELATE_TO_SELF或Animation.RELATIVE_TO_PARENT |
toYValue/toYDelta | 动画结束时当前View的Y坐标 |
TranslateAnimation的使用如下。
public void translate() {
TranslateAnimation anim = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 2f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 2f);
anim.setDuration(3000);
anim.setFillAfter(true);
mImageView.startAnimation(anim);
}
1.2.5 RotateAnimation
XML实现示例如下。
在代码实现中,RotateAnimation可用的构造函数有三个,代码如下。
public RotateAnimation(float fromDegrees, float toDegrees) {
}
public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) {
}
public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
}
其中,入参的含义如图所示
参数 | 含义说明 |
---|---|
fromDegrees | 动画开始时的旋转角度 |
toDegrees | 动画结束时的旋转角度 |
pivotXType | 动画在X轴的旋转模式,即相对于物件的位置类型,取值是Animation.ABSOLUTE、Animation.RELATE_TO_SELF或Animation.RELATIVE_TO_PARENT |
pivotXValue | 动画相对于物件的X坐标开始位置 |
pivotYType | 动画在Y轴的旋转模式,即相对于物件的位置类型,取值是Animation.ABSOLUTE、Animation.RELATE_TO_SELF或Animation.RELATIVE_TO_PARENT |
pivotYValue | 动画相对于物件的Y坐标开始位置 |
RotateAnimation的使用如下。
public void rotate() {
RotateAnimation anim = new RotateAnimation(0, -720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
anim.setDuration(1000);
anim.setFillAfter(true);
mImageView.startAnimation(anim);
}
1.2.6 自定义补间动画
在实际的项目中,往往会遇到使用上述四种基本动画无法实现的动画需求,这时可以考虑自定义补间动画,只需要继承Animation,并重写这个抽象基类中的applyTransfromation方法,在其中实现具体的动画变换逻辑,语句代码如下。
public class MyAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation transformation) {
super.applyTransformation(interpolatedTime, transformation);
}
}
其中,interpolatedTime
表示动画的时间进行比,无论动画的实际持续时间是多少,这个参数总是会从0变化到1。transformation
表示补间动画在不同时刻对View的变形程度。
1.3 属性动画(Property Animation)
属性动画是在Android 3.0 中引入的,在补间动画中,我们只能改变View的绘制效果,View的真实属性是没有变化的,而属性动画则可以直接改变View对象的属性值,同时属性动画几乎可以对任何对象执行动画,而不是局限在View对象上,从某种意义上讲,属性动画可以说是增强版的补间动画。与补间动画类似,属性动画也涉及如下基本属性。
动画属性 | 说明 |
---|---|
动画持续时间 | 默认值是300毫秒,在资源文件中通过android:duration 指定 |
动画插值方式 | 详见补间动画介绍,在资源文件中通过android:interpolator 指定 |
动画重复次数 | 指定动画重复播放的次数,在资源文件中通过android:repeatCount 指定 |
动画重复模式 | 指定一次动画播放结束后,重复下次动画时,是从开始帧再次播放到结束帧,还是从结束帧反方向播放到开始帧,在资源文件中通过android:repeatMode 指定 |
帧刷新频率 | 指定间隔多长时间播放一帧,默认值是10毫秒 |
动画集合 | 通过动画集合可以在将多个属性动画组合起来,同时通过指定android:ordering 属性可以控制这组动画是按次序播放还是同时播放,在资源文件中通过 来表示 |
属性动画的基类是Animator,它是一个抽象类,所以不会直接使用这个类,通常都是继承它并重写其中的相关方法,Android SDK为开发者默认提供了几个子类,大多数情况下使用这些子类就足够完成开发任务了。
1.3.1 Evaluator
在介绍Animator的子类之前,我们首先来了解一个名为Evaluator的概念,它是用来控制属性动画如何计算属性值的。它的接口定义是TypeEvaluator,其中定义了evaluate方法,供不同类型的子类实现。
public interface TypeEvaluator {
public T evaluate(float fraction, T startValue, T endValue);
}
常见的实现类有IntEvaluator、FloatEvaluator、ArgbEvaluator等。下面我们来看一下ArgbEvaluator的具体实现,可以看到实现逻辑很简单,就是根据输入的初始值和结束值及一个进度比,计算出每一个进度对应的ARGB值。
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) startValue;
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))));
}
}
1.3.2 AnimatorSet
AnimatorSet也是Animator的子类,用来组合多个Animator,并指定这些Animator是顺序播放还是同时播放。
1.3.3 ValueAnimator
ValueAnimator是属性动画最重要的一个类,继承自Animator。它定义了属性动画大部分的核心功能,包括计算各个帧的属性值、处理更新事件、按照属性值的类型控制计算规则等。
一个完整的属性动画由以下两部分组成。
- 计算动画各个帧的相关属性值。
- 将这些属性值设置给指定的对象。
ValueAnimator为开发者实现了第一部分的功能,第二部分功能由开发者自行设置。ValueAnimator的构造函数是空实现,一般都是使用如下的静态工厂方法来进行实例化。
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
public static ValueAnimator ofArgb(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
anim.setEvaluator(ArgbEvaluator.getInstance());
return anim;
}
public static ValueAnimator ofFloat(float... values) {
ValueAnimator anim = new ValueAnimator();
anim.setFloatValues(values);
return anim;
}
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
ValueAnimator anim = new ValueAnimator();
anim.setValues(values);
return anim;
}
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
ValueAnimator anim = new ValueAnimator();
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}
获取到实例后,接着需要设置动画持续时间、插值方式、重复次数等属性值,然后启动动画,最后还需要为ValueAnimator注册AnimatorUpdateListener监听器,并在这个监听器的onAnimationUpdate方法中将计算出来的属性值设置给指定对象。我们从React Native这个开源框架中可以看到用法如下。
int curColor = activity.getWindow().getStatusBarColor();
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), curColor, color);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
activity.getWindow().setStatusBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.setDuration(300).setStartDelay(0);
colorAnimation.start();
1.3.4 ObjectAnimator
ObjectAnimator 是 ValueAnimator 的子类,封装实现了上面所说的第二部分的功能。因此,在实际开发中用得最多的就是ObjectAnimator,只有在ObjectAnimator实现不了的场景下,才考虑使用ValueAnimator。ObjectAnimator和ValueAnimator在构造实例时最大的不同是需要指定动画作用的具体对象和对象属性名,而且一般不需要注册AnimatorUpdateListener监听器,示例如下。
public class CustomCircleProgressBar extends CircularProgressBar {
public CustomCircleProgressBar (Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setProgressWithAnimation (float progress, int duration) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "progress", progress);
objectAnimator.setDuration(duration);
objectAnimator.start();
}
}
使用ObjectAnimator有以下几点需要注意。
- 需要为对象对应的属性提供setter方法。例如,上面的progress属性在父类CircularProgressBar提供了如下设置方法。
public void setProgress (float progress) {
this.progress = (progress <= 100) ? progress : 100;
invalidate();
}
- 如果动画的对象是View,那么为了能显示动画效果,在某些情况下,可能还需要注册AnimatorUpdateListener监听器,并在其回调方法onAnimationUpdate中调用View的invalidate方法来刷新View的显示。
上面所说的都是在代码中定义属性动画。事实上,与补间动画类似,属性动画也可以在XML文件中定义,在工程的res/animator目录中存放的就是属性动画XML文件,例子scale.xml如下。
使用代码加载如下。
public void scaleY(View view) {
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);
anim.setTarget(mImageView);
anim.start();
}
1.4 过渡动画(Transition Animation)
过渡动画是在Android 4.4引入的新的动画框架,它本质上还是属性动画,只不过是对属性动画做了一层封装,方便开发者实现Activity或者View的过渡动画效果。和属性动画相比,过渡动画最大的不同是需要为动画前后准备不同的布局,并通过对应的API实现两个布局的过渡动画,而属性动画只需要一个布局文件。
在使用Transition Animation框架实现动画效果之前,我们先来了解这个框架的几个基本概念。
- Scene:定义了页面的当前状态信息,Scene的实例化一般通过静态工厂方法实现。
public static Scene getSceneForLayout (ViewGroup sceneRoot, int layoutId, Context context) {
}
- Transition: 定义了界面之间切换的动画信息,在使用TransitionManager时没有指定使用哪个Transition,那么会使用默认的AutoTransition,源码如下。可以看出AutoTransition的动画效果就是先隐藏对象变透明,然后移动指定的对象,最后显示出来。
public class AutoTransition extends TransitionSet {
public AutoTransition() {
init();
}
public AutoTransition(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).addTransition(new ChangeBounds()).addTransition(new Fade(Fade.IN));
}
}
- TransitionManager:控制Scene之间切换的控制器,切换常用的方法有一下两个,其中sDefaultTransition就是前面说的AutoTransition的实例。
public static void go(Scene scene) {
changeScene(scene, sDefaultTransition);
}
public static void go(Scene scene, Transition transition) {
changeScene(scene, transition);
}
过渡动画的使用很简单,首先定义同一个页面的两个布局,分别是动画前的布局和动画后的布局,我们将其命名为fragment_transition_scene_before.xml和fragment_transition_scene_after.xml,这两个布局文件的根布局具有相同的android:id值,动画前的布局文件内容如下。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
if(savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.container, new TransitionFragment()).commit();
}
//XML文件方式
ViewGroup container = (ViewGroup)findViewById(R.id.container);
TransitionManager transitionInflater = TransitionInflater.from(this);
mTransitionManager = transitionInflater.inflateTransitionManager(R.id.transition.transition_manager, this);
mScene1 = Scene.getSceneForLayout(container, R.layout.fragment_transition_scene_before, this);
mScene2 = Scene.getSceneForLayout(container, R.layout.fragment_transition_scene_after, this);
}
private void goToScene(Scene scene) {
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setDuration(2000);
Fade fadeOut = new Fade(Fade.OUT);
fadeOut.setDuration(2000);
Fade fadeIn = new Fade(Fade.IN);
fadeIn.setDuration(2000);
TransitionSet transition = new TransitionSet();
transition.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
transition.addTransition(fadeOut).addTransition(changeBounds).addTransition(fadeIn);
TransitionManager.go(scene, transition);
}
上面的代码分别演示了XML布局和代码实现两种方式,过渡动画的XML文件需要放在res/transition目录中,其中transition_manager.xml定义如下。
其中的slow_auto_transition也是定义在res/transition目录中的自定义Transition动画文件。