Android中的动画大致上分为视图动画(View Animation)和属性动画(Property Animation)两种,其中视图动画又分为补间动画(Tween Animation)和帧动画两种(Frame Animation)。其中每种动画有分很多情况,虽然用着都比较简单,但是经常容易忘,所以这里做一个总结。
1.帧动画
基本上属于最简单的一种动画,主要就是像幻灯片一样播放一组图片,形成动画效果。如下图一个进度条的动画就是许多张图片依次播放的效果:
帧动画有两种做法,一种利用xml文件,一种利用java代码。
1.1 xml文件写法
首先在res/drawable目录下创建一个animation-list类型的文件,虽然是动画文件(虽然是帧动画的这个文件再Android Studio中不能放在anim文件夹下)内容如下:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/indeterminate01"
android:duration="50" />
<item
android:drawable="@drawable/indeterminate02"
android:duration="50" />
<item
android:drawable="@drawable/indeterminate03"
android:duration="50" />
<item
android:drawable="@drawable/indeterminate04"
android:duration="50" />
<item
android:drawable="@drawable/indeterminate05"
android:duration="50" />
<item
android:drawable="@drawable/indeterminate06"
android:duration="50" />
</animation-list>
内容很简单,就是按照顺序将一张一张图片列出来,duration属性表示这张图片的暂留时间,oneshot表示是否播放一次就结束,循环播放的话用false。
帧动画的载体是ImageView,当然也可以是别的控件,如TextView等可以设置图片显示的控件,因为我们前面写的那个xml文件是作为一个Drawable使用的,也就是为什么要放在drawable目录下的。不过为了最佳的显示效果还是用ImageView。如下,设置为ImageView的src
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/anim"
android:src="@drawable/indeterminate"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
然后在代码中启动动画
ImageView iv = findViewById(R.id.anim);
AnimationDrawable drawable = (AnimationDrawable) iv.getDrawable();
drawable.start();
1.2 Java代码写法
使用xml文件的好处是将UI逻辑从代码中剥离,而且xml文件看起来条例比较清晰,当然如果我们需要动态控制的时候,还是需要用java代码:
ImageView iv = findViewById(R.id.anim);
AnimationDrawable drawable = new AnimationDrawable();
for (int i = 1; i < 7; i++)
drawable.addFrame(getDrawable(getResources().getIdentifier("indeterminate0"+i,"drawable",getPackageName())),50);
drawable.setOneShot(false);
iv.setImageDrawable(drawable);
drawable.start();
addFrame:添加一帧图片及设置显示时间
setOneShot:设置是否仅播放一次
start() :开始播放
stop() :停止播放
isRunning(): 是否在播放
2.视图动画
一般而言视图动画分以下四种类型:平移,缩放,旋转,透明度变化。
分别对应Java中以下4个类:TranslateAnimation,ScaleAnimation,RotateAnimation,AlphaAnimation。
在xml文件中体现为下面4个标签:<translate/>,<scale/>,<rotate/>,<alpha/>
当然几种动画也可以组合起来使用,对于AnimationSet类和<set/>。
一般建议在xml文件定义动画,动画文件存放在res/anim文件夹下,各种类型动画举例及属性说明如下:
2.1 平移动画
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="500"
android:toYDelta="0"
/>
android:fromXDelta / android:fromYDelta: 起始X,Y坐标
android:toXDelta / android:toYXDelta:结束X,Y坐标
2.2 缩放动画
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0"
android:fromYScale="0"
android:toXScale="2"
android:toYScale="2"
android:pivotX="50%"
android:pivotY="50%"
android:duration="3000"
/>
android:fromXScale / android:fromYScale :起始XY轴缩放倍数
android:toXScale / android:toYScale:最终XY缩放倍数
android:pivotX / android:pivotY:缩放中心点位置,50%代表中间
2.3 旋转动画
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fromDegrees="0"
android:toDegrees="300"
android:pivotX="50%"
android:pivotY="50%"
/>
android:fromDegrees:旋转起始度数
android:toDegrees:旋转结束度数
android:pivotX / android:pivotY:旋转中心
2.4 透明度变化
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fromAlpha="1"
android:toAlpha="0"
/>
android:fromAlpha:起始透明度,1为不透明,0为全透明
android:toAlpha:动画结束时透明度
2.5组合动画
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:shareInterpolator="true"
>
<rotate
android:duration="3000"
android:fromDegrees="0"
android:toDegrees="359"
android:pivotX="50%"
android:pivotY="50%"
/>
<scale
android:fromXScale="0"
android:fromYScale="0"
android:toXScale="2"
android:toYScale="2"
android:pivotX="50%"
android:pivotY="50%"
/>
</set>
android:shareInterpolator:表示组合动画中各个动画是否公用一个插值器
2.6一些共有属性
android:duration="3000" // 动画持续时间(ms)
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= "true"// 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = "anim/interpolator_resource" // 插值器
2.7 动画的应用
既然是view动画,就要实际应用到某个控件上,如下:
ImageView iv = findViewById(R.id.anim);
iv.startAnimation(AnimationUtils.loadAnimation(this,R.anim.set_anim));
2.8 Java代码形式
可以用xml书写的动画都可以用Java代码实现,便于动态修改,写起来也比较简单,只需实例化对应动画类型的对象,然后具体设置每种属性,简单例子如下:
ImageView iv = findViewById(R.id.anim);
ScaleAnimation scaleAnimation = new ScaleAnimation(0,0,2,2, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(3000);
RotateAnimation rotateAnimation = new RotateAnimation(0,300,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotateAnimation.setDuration(3000);
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(rotateAnimation);
iv.startAnimation(animationSet);
2.9 动画监听
animationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
2.10 自定义动画
自定义动画仅需继承Animation类即可,然后重写applyTransformation和initialize两个方法。如下:
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
public void initialize(int width, int height, int parentWidth, int parentHeight) {
}
initialize主要用于一些数据的初始化,applyTransformation则是动画中对view进行操作的实际地方,interpolatedTime表示动画进行的时间,从0到1递增,当值为1时,表示动画结束,Transformation 可以看成view的实体进行操作,通过getMatrix()可以获得变换矩阵,进行操作。动画的实现就是不停调用applyTransformation。
一些复杂的自定义动画实现起来还是比较复杂的,我们可以看一下最简单的透明度动画的实现源码,关键代码如下:
public AlphaAnimation(float fromAlpha, float toAlpha) {
mFromAlpha = fromAlpha;
mToAlpha = toAlpha;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
可以看到还是很简单的,甚至没有重写initialize方法,仅在构造中保存了fromAlpha和toAlpha。然后在applyTransformation根据时间的变化设置相应的透明度。
3.属性动画
视图动画虽然简单易用,但是有很大的局限性,动画只能针对整个view,而且动画种类有限,可操作性较低,而且仅改变了视觉效果,别没有真正改变view的属性,所以如果需要更加复杂的动画效果,我们就需要使用属性动画。
属性动画主要涉及ObjectAnimator、ValueAnimator、AnimatorSet等几个类,我们依次来学习:
3.1 ValueAnimator
ObjectAnimator是继承于ValueAnimator,所以我们先学习ValueAnimator。
ValueAnimator实现动画的主要原理是不断变化数值,然后由我们赋给控件的各种属性从而实现动画效果,涉及的操作主要有ofInt,ofFloat,ofObject三个方法。
3.1.1 ValueAnimator ofInt (int... values)
使用这个方法主要是以整型进行数值变化,举例:
final ImageView iv = findViewById(R.id.anim);
ValueAnimator animator = ValueAnimator.ofInt(0,270).setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
iv.setRotation(currentValue);
}
});
animator.start();
在例子中我们首先设置变化范围为0~270,动画时间为3000ms,然后添加一个监听,主要是为了在每次数值发生变化时进行相应的操作,这里我们对view进行旋转操作。
同样我们也可以在xml文件中定义动画:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="270"
android:duration="3000"
android:valueType="intType"
/>
需要注意的时这里的xml文件要放在res/animator目录下,而且需要指定valueType,不指定的话默认为float。之后在java文件中进行应用:
final ImageView iv = findViewById(R.id.anim);
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.anim);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
iv.setRotation(currentValue);
}
});
animator.start();
效果和前面是一样的。
3.1.2 ValueAnimator ofFloat (float... values)
和ofInt基本一样,只不过过渡值变为float类型,这里不多叙述
3.1.3 ValueAnimator ofObject (TypeEvaluator evaluator, Object... values)
这个相当于是一个开放性的接口,前两个只能局限于固定的数据类型,这个方法我们可以指定任何类型的数据来实现各种动画。在这个方法中我们要指定一个自定义的TypeEvaluator和起始结束值,
TypeEvaluator主要是为了计算动画过程中值变化的详细过程,如已经实现好的FloatEvaluator如下:
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
可见实现起来也是很简单的,根据起始和终止值,不断乘以系数fraction 最后计算出每次变化的值。
由此可见我们可以通过自定义所需TypeEvaluator,来实现各种对象的变化过程,最终来实现动画效果。
3.1.4 其余属性
当然和view动画类似,我们除了可以设置起始终止值和持续时间外,也可以设置如延迟,重复,插值器等属性,在XML文件中如下:
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = "true"// 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = "false"// 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= "true"// 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode="restart"// 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = "0"// 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator =" @[package:]anim/interpolator_resource" // 插值器
对应java代码中ValueAnimator类也有对应方法。
3.2 ObjectAnimator
ValueAnimator中主要是不断提供变化的值然后我们手动实现属性的变化,ObjectAnimator继承于ValueAnimator,对ValueAnimator进行了一定的简化。一些常用的操作,比如选择,渐变,我们每次都要加监听然后改变属性,比较繁琐。ObjectAnimator的出现可以让我们直接指定某些属性,然后划定起始终止值,然后一些交由系统完成。举例:
ObjectAnimator animator = ObjectAnimator.ofFloat(view,"rotation",0,270);
animator.setDuration(3000).start();
可见和ValueAnimator及其类似,只不过要指定一个属性而已,调用start后自动对该属性进行变化。
xml文件写法如下:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="270"
android:duration="3000"
android:propertyName="rotation"
/>
应用动画
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.object_anim);
animator.setTarget(view);
animator.setDuration(3000).start();
这个类使用的关键在于设置属性,一些基本的属性如下(图片来源于网络):
需要注意的是指定属性时,对应的值要正确,如我们进行旋转时只能用ofFloat。
当然一个控件的属性有很多,到底哪些属性可以通过ObjectAnimator 实现动画能,通过源码分析,那些有get,set方法的属性都是可以的,这也验证了ObjectAnimator 是继承于ValueAnimator,其实现原理都是一样的说法(都是不停地改变属性的值实现动画效果)。
另外ObjectAnimator 和ValueAnimator一样也有一些共有属性可以设置,这里也不详细叙述了。
最后通过文档我们可以知道ObjectAnimator 也有ofObject()方法,这样我们就可以通过包装或者直接添加get/set方法的形式,再配合自定义的估值器来实现各种动画效果。不过个人认为一些较复杂的效果还是ValueAnimator实现起来比较方便。
3.3 AnimatorSet
AnimatorSet 是用来实现组合动画的,本身并没有什么特殊效果,举例如下:
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(3000);
animatorSet.play(rotate).with(alpha);
animatorSet.start();
当然也可以用xml文件书写:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:valueFrom="0"
android:valueTo="1"
android:propertyName="alpha"
android:duration="3000"
/>
<objectAnimator
android:valueFrom="0"
android:valueTo="360"
android:propertyName="rotation"
android:duration="3000"
/>
</set>
应用动画:
AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.set_anim);
animatorSet.setTarget(view);
animatorSet.start();
AnimatorSet 相比AnimationSet有一个顺序的概念。体现在java代码中就是涉及下面几个方法:
AnimatorSet.play(Animator anim) :播放当前动画
AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) : 将现有动画插入到传入的动画之前执行
放在xml文件中,对应于set标签里的android:ordering属性,together表示标签内的动画同时执行,sequentially表示标签内的动画按次序执行。
3.4 ViewPropertyAnimator
这是一种更加简洁更加符合面向对象思想的动画。但其功能比较有限,基本和view动画的功能类似,但是这个属于属性动画。示例:
view.animate().alpha(0).setDuration(3000).start();
上面语句代表将透明度变为0的动画,实际应用中我们甚至不用调用start方法,系统会隐式的调用。
组合动画也很简单:
view.animate().alpha(0).rotation(270).setDuration(3000);
另外ViewPropertyAnimator还有一套以By结尾的方法,如alphaBy()。区别是不带by的表示变化到某个值,带by的表示变化量为某个值。
3.5 动画监听
一般动画监听的方法如下:
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
有时我们仅仅只是需要知道开始或结束的时刻,不必实现这么多方法,可以用下面方法:
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
AnimatorListenerAdapter是个抽象类,实现了AnimatorListener接口,我们仅需实现所需的方法即可。
4.插值器
所有类型的动画都涉及到了interpolator这个属性,就是插值器。通过学习ValueAnimator我们了解到动画的本质就是不断改变数值,然后修改相应的属性。数值的改变也分很多种,如匀速改变,变速改变等,控制这种规律的就称为插值器。
首先系统为我们内置了许多现成的插值器,可以满足我们大部分需要(图片来源于网络):
当然我们也可以自定义插值器,满足各种特殊需求.一般而言我们首先要实现Interpolator接口,通过源码发现,Interpolator继承于TimeInterpolator,但没做任何处理,所以我们可以实现Interpolator接口或者TimeInterpolator接口。或者是和系统内置的插值器一样,继承BaseInterpolator类。
不管如何继承或实现,最后都是在处理一个方法:
float getInterpolation(float input);
这个方法的参数input介于0和1之间,动画开始为0,结束为1,我们在这里对这个值进行加工后,就能按照我们的意愿得到各种动画改变规律。接下来我们看一下,系统内置的一些差值器的实现:
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
...
public float getInterpolation(float input) {
return input;
}
...
}
可见线性插值器没有做任何处理,怎么输入就怎么输出。
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
...
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
...
}
可见加速度插值器也很简单,默认mFactor为1.0,就对input去平方,随着值增大,结果呈二次递增。另外也可以自定义factor,自定义后就是调用pow方法,求一个幂,也能达到加速效果。
5.估值器
TypeEvaluator在我们在学习ValueAnimator的ofObject 方法时已经了解过。估值器和插值器是互相配合的。估值器中的系数fraction就是插值器传来的,估值器负责最后计算出实际的值。这里我们就不详细叙述了。
6.硬件加速
使用硬件加速会让动画绘制的更快,因为硬件会把图层缓存在GPU上。
开启方法:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.anim);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
view.setRotation(currentValue);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
animator.start();
基本就是动画开始前开启硬件加速,结束后关闭。
对于ViewPropertyAnimator,开启方法更加简单,调用withLayer()即可:
view.animate().alpha(0).withLayer();
7.总结
可见Android中的动画是非常丰富而且开放的,不仅提供了大量预置的效果,也开放了较底层的接口供我们实现各种丰富的动画效果。