Android动画
在Android中,动画分类较多,最早的帧动画,补间动画,到Android3.0之后加入了属性动画。除此之外还有一些其他类型。
一、传统动画
1.帧动画
帧动画就是我们说的Frame动画,Frame动画是一系列图片按照一定顺序展示的过程。
Frame动画可以被定义在xml文件里,也可以完全编码实现。如果被定义在xml文件中,可以放在/res的anim或drawable目录下。如果完全编码实现,需要使用到AnimationDrawable对象。
动画实现:gakki_anim.xml
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true"> <item android:drawable="@drawable/gakki_1" android:duration="80" /> <item android:drawable="@drawable/gakki_2" android:duration="80" /> <item android:drawable="@drawable/gakki_3" android:duration="80" /> <item android:drawable="@drawable/gakki_4" android:duration="80" /> <item android:drawable="@drawable/gakki_5" android:duration="80" /> 。。。。。。。。。。。。 android:drawable="@drawable/gakki_36" android:duration="80" /> </animation-list>
ImageView imageView = (ImageView) findViewById(R.id.iv_frame_anim);
((AnimationDrawable) imageView.getBackground()).start();//启动
在有些代码中,我们还会看到android:oneshot="false" ,这个oneshot 的含义就是动画执行一次(true)还是循环执行多次。
private void initCodeAnim() { ImageView imageView = (ImageView) findViewById(R.id.iv_frame_anim); AnimationDrawable anim = new AnimationDrawable(); for (int i = 1; i < 37; i++) { //获取图片资源ID int id = getResources().getIdentifier("gakki_" + i, "drawable", getPackageName()); Drawable drawable = getResources().getDrawable(id); //将Drawable添加到帧动画中 anim.addFrame(drawable, 80); } anim.setOneShot(false); imageView.setBackground(anim); anim.start(); }
也可以用纯java代码来实现。
通过xml更为推荐,因为它将代码从复杂的java逻辑中隔离,易于维护复用。
2.补间动画
tween动画是操作某个控件让其展示出alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转) 的一种转换过程,成为补间动画。同样我们可以用xml来实现也可以用编码实现。
如果以xml形式定义一个动画,我们按照定义动画的语法完成xml,并放置于/res/anim目录下,文件可以作为资源id被引用;如果以编码形式完成,需要使用到Animation对象。
xml实现:
alpha_anim.xml
<?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:fromAlpha="1.0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toAlpha="0.0" />
Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim); img = (ImageView) findViewById(R.id.img); img.startAnimation(animation);
这样就可以实现ImageView alpha 透明变化的动画效果。
也可以使用set 标签将多个动画组合
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@[package:]anim/interpolator_resource" android:shareInterpolator=["true" | "false"] > <alpha android:fromAlpha="float" android:toAlpha="float" /> <scale android:fromXScale="float" android:toXScale="float" android:fromYScale="float" android:toYScale="float" android:pivotX="float" android:pivotY="float" /> <translate android:fromXDelta="float" android:toXDelta="float" android:fromYDelta="float" android:toYDelta="float" /> <rotate android:fromDegrees="float" android:toDegrees="float" android:pivotX="float" android:pivotY="float" /> <set> ... </set> </set>
可以看到组合动画是可以嵌套使用的。
<set>是一个动画容器,管理多个动画的群组,与之对应的Java对象是AnimationSet。它有两个属性主要说一下,interpolator (插值器资源)和shareInterpolar代表<set>中多个动画是否共享插值器。
Interpolator 主要作用是可以控制动画的变化速率 ,就是动画进行的快慢节奏。
Android 系统已经为我们提供了一些Interpolator ,比如 accelerate_decelerate_interpolator,accelerate_interpolator等。更多的interpolator 及其含义可以在Android SDK 中查看。同时这个Interpolator也是可以自定义的。
pivot属性:
pivot 决定了当前动画执行的参考位置
pivot 这个属性主要是在translate 和 scale 动画中,这两种动画都牵扯到view 的“物理位置“发生变化,所以需要一个参考点。而pivotX和pivotY就共同决定了这个点;它的值可以是float或者是百分比数值。
我们以pivotX为例,
pivotX取值 | 含义 |
---|---|
10 | 距离动画所在view自身左边缘10像素 |
10% | 距离动画所在view自身左边缘 的距离是整个view宽度的10% |
10%p | 距离动画所在view父控件左边缘的距离是整个view宽度的10% |
补间动画只能运用在View对象上,并且功能相对来说比较局限。例如旋转动画只能在x,y轴进行,而不能在Z轴方向进行旋转。因此,补间动画通常用于执行一些比较简单的动画,由于比较简单,就不加赘述了。
三、属性动画
属性动画机制不再是针对View来设计的,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种简单的动画操作,同时也不再只是一种视觉上的动画效果。它实际上是一种在一定时间段内不断修改某个对象的某个属性值的机制。我们只需要告诉系统动画要操作的属性、动画时长、需要执行的动画类型,以及初始值结束值,剩下的工作就可以全部交给系统来做了。
首先我们来看看如何用属性动画实现上面补间动画的效果
private void RotateAnimation(View view) { ObjectAnimator anim = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f); anim.setDuration(1000); anim.start(); } private void AlpahAnimation(View view) { ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f); anim.setRepeatCount(-1); anim.setRepeatMode(ObjectAnimator.REVERSE); anim.setDuration(2000); anim.start(); }
属性动画也是可以组合实现的
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f); ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f); ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f); ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360); ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400); ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750); AnimatorSet set = new AnimatorSet(); set.playTogether(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim); // set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim); set.setDuration(3000); set.start();
可以看到这些动画可以同时播放,或者是按序播放。
1.属性动画的核心类-- ValueAnimator
ValueAnimator是整个属性动画机制中最核心的一个类,它的作用是在一定的时间段内不断地修改对象地某个属性值。ValueAnimator地内部使用一种时间循环地机制来计算值与值之间地动画过渡,我们只需要将属性地取值范围、运行时长提供给ValueAnimator,它就会自动帮我们计算属性值在各个动画运行时段地取值,这些值会按照一定地计算方式来平滑过渡,除此之外,ValueAnimator还负责管理动画地播放次数、播放模式、以及对动画设置监听器等。
ValueAnimator地API也非常简单。通常我们都是通过ofFloat、ofInt等静态工厂函数来构建ValueAnimator。例如将数值从0.0过渡到1.0的动画:
ValueAnimator.AnimatorUpdateListener listener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float newValue = (Float) animation.getAnimatedValue(); Log.d("ValueAnimTest", "新的属性值:" + newValue); } }; private void startValueAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); animator.setDuration(1000); animator.addUpdateListener(listener); animator.start(); }
也可以用xml实现
<?xml version="1.0" encoding="utf-8"?> <animator xmlns:android="http://schemas.android.com/apk/res/android" android:valueFrom="0.0" android:valueTo="1.0" android:valueType="floatType" />
java代码中加载
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator( this, R.animator.value_anim);
启动动画之后每次更新属性值都会调用onAnimationUpdate函数,在这里可以获取到新的属性值。当然我们这里没有将这个值运用到具体的对象上。但它是非常灵活的,它之操作属性本身,这个值不属于某个具体对象,但它却能运用到任意对象上。例如通过ValueAnimator动画将某个View的透明度从0.0过渡到1.0,那么可以对onAnimationUpdate做如下修改:
ValueAnimator.AnimatorUpdateListener listener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float newValue = (Float) animation.getAnimatedValue(); Log.d("ValueAnimTest", "新的属性值:" + newValue); //将数字设置到具体对象 imageView.setAlpha(newValue); } };
这样一来,我们就将ValueAnimator与具体对象结合起来了,通过这种形式就能更自由地控制动画,完成各种各样地动画效果。
2.对任意属性进行动画操作--ObjectAnimator
valueAnimator 功能强大、自由度高,但是这也意味着开发人员需要做更多的工作来实现动画需求,效率不高。开发中用的更多地是ObjectAnimator,因为ValueAnimator只是对值进行了一个平滑地动画过渡,但实际开发中常常是需要对某个对象地某个属性进行修改,也就是对某个对象执行动画,当然用得最多地就是View的动画,而ObjectAnimator就是可以直接对任意对象的任意属性进行动画操作的类。
ObjectAnimator继承自ValueAnimator,动画实现机制一样。
如上面演示的:
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f); anim.setDuration(1000); anim.start();
ofxx这样的函数至少有4个参数,例如上面的,参数1是要操作的对象 ,参数2是要操作哪个属性,剩下的参数是可变参数,我们这里两个值就是起始值和目标值。如果是多个参数,那么在动画过程中将会诸葛过渡到各个值。
原理:在初始时设置目标对象和、目标属性以及要经历的属性值,然后通过内部的计算方式计算出在各个事件短该属性的取值,在动画运行期间通过目标对象属性的setter方法更新属性值,如果属性没有setter方法,那么会通过反射的形式更新目标属性值,在周期内不断地计算、更新新的属性值,从而达到对象地属性动画效果。
3.实现丰富多彩地动画效果--AnimatorSet
AnimatorSet可以将多个动画组合在一起执行。AnimatorSet类提供了一个play方法
public Builder play(Animator anim) { if (anim != null) { return new Builder(anim); } return null; }
传入一个Animator对象会返回一个AnimatorSet.Builder地实例,
public class Builder {/** * Sets up the given animation to play at the same time as the animation supplied in the * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object. * * @param anim The animation that will play when the animation supplied to the * {@link AnimatorSet#play(Animator)} method starts. */ public Builder with(Animator anim) {//将现有动画和传入地动画同时执行 Node node = getNodeForAnimation(anim); mCurrentNode.addSibling(node); return this; } /** * Sets up the given animation to play when the animation supplied in the * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object * ends. * * @param anim The animation that will play when the animation supplied to the * {@link AnimatorSet#play(Animator)} method ends. */ public Builder before(Animator anim) {//在anim动画执行完之前再执行调用after函数地动画 mReversible = false; Node node = getNodeForAnimation(anim); mCurrentNode.addChild(node); return this; } /** * Sets up the given animation to play when the animation supplied in the * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object * to start when the animation supplied in this method call ends. * * @param anim The animation whose end will cause the animation supplied to the * {@link AnimatorSet#play(Animator)} method to play. */ public Builder after(Animator anim) {//在anim动画执行完之后再执行调用after函数地动画 mReversible = false; Node node = getNodeForAnimation(anim); mCurrentNode.addParent(node); return this; } /** * Sets up the animation supplied in the * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object * to play when the given amount of time elapses. * * @param delay The number of milliseconds that should elapse before the * animation starts. */ public Builder after(long delay) {//将调用after地动画延迟制定地毫秒后执行 // setup dummy ValueAnimator just to run the clock ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(delay); after(anim); return this; } }
我们可以通过这几个方法实现各个动画组合一起执行:
ObjectAnimator anim1 = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f); anim1.setDuration(1000); ObjectAnimator anim2 = ObjectAnimator.ofFloat(view, "alpha", 0.0f, 1.0f); anim2.setDuration(1000); ObjectAnimator anim3 = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 0.0f,1.0f); anim3.setDuration(1000); ObjectAnimator anim4 = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, 0.0f,1.0f); anim4.setDuration(1000); ObjectAnimator anim5 = ObjectAnimator.ofFloat(view, "translationX", 0f, 1.0f); anim5.setDuration(1000); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(anim1).with(anim2).after(anim3).before(anim4); animatorSet.start();
当然我们也可以通过playTogether函数将几个动画一起执行
4.动画执行事件--TypeEvaluator与TimeInterPolator
TypeEvaluator 决定了动画如何从初始值过渡到结束值。
TimeInterpolator 决定了动画从初始值过渡到结束值的节奏。
TypeEvalutor(类型估值器),它的作用是根据当前动画已执行事件占总时间地百分比来计算新的属性值。TypeEvalutaor只有一个evalutor函数,该函数地指责就是计算出新的属性值
public interface TypeEvaluator<T> { public T evaluate(float fraction, T startValue, T endValue); }
示例:
private void useCustomEvaluator(View view) { ObjectAnimator animator = ObjectAnimator.ofObject(view, "y", new CustomEvaluator(), 0f, 200f); animator.setDuration(2000); animator.start(); } public class CustomEvaluator implements TypeEvaluator<Float> { @Override public Float evaluate(float fraction, Float startValue, Float endValue) { Float newValue = startValue + fraction * (endValue - startValue); Log.d("ValueAnimTest", "fraction= " + fraction + ",start= " + startValue + ",end= " + endValue + ", newValue= " + newValue); return newValue; } }
可以看到log信息
可以看到fraction是线性变化地,这个过程中属性值也从0慢慢增加到200。如果想改变这种效果,就要用到时间插值器了。
TimeInterpolator 时间插值器
它地作用是修改动画已执行时间和总时间地百分比,也就是改变fraction的参数值。
Interpolator的概念其实我们并不陌生,在补间动画中我们就使用到了。他就是用来控制动画快慢节奏的;而在属性动画中,TimeInterpolator 也是类似的作用;TimeInterpolator 继承自Interpolator。我们可以继承TimerInterpolator 以自己的方式控制动画变化的节奏,也可以使用Android 系统提供的Interpolator。
下面都是系统帮我们定义好的一些Interpolator,我们可以通过setInterpolator 设置不同的Interpolator。
系统预置了
我们也可以自定义时间插值器来实现需要的效果。这里省略。
XML 属性动画
这里提一下,属性动画当然也可以使用xml文件的方式实现,但是属性动画的属性值一般会牵扯到对象具体的属性,更多是通过代码动态获取,所以xml文件的实现会有些不方便。
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
使用方式:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
xml 文件中的标签也和属性动画的类相对应。
ValueAnimator --- <animator>
ObjectAnimator --- <objectAnimator>
AnimatorSet --- <set>
这些就是属性动画的核心内容。现在使用属性动画的特性自定义动画应该不是难事了。其余便签的含义,结合之前的内容应该不难理解了。
四、传统动画 VS 属性动画
相较于传统动画,属性动画有很多优势。那是否意味着属性动画可以完全替代传统动画呢。其实不然,两种动画都有各自的优势,属性动画如此强大,也不是没有缺点。
- 补间动画中,虽然使用translate将图片移动了,但是点击原来的位置,依旧可以发生点击事件,而属性动画却不是。因此我们可以确定,属性动画才是真正的实现了view的移动,补间动画对view的移动更像是在不同地方绘制了一个影子,实际的对象还是处于原来的地方。
- 当我们把动画的repeatCount设置为无限循环时,如果在Activity退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏,而补间动画却没有问题。因此,使用属性动画时切记在Activity执行 onStop 方法时顺便将动画停止。(对这个怀疑的同学可以自己通过在动画的Update 回调方法打印日志的方式进行验证)。
- xml 文件实现的补间动画,复用率极高。在Activity切换,窗口弹出时等情景中有着很好的效果。
- 使用帧动画时需要注意,不要使用过多特别大的图,容易导致内存不足。
参考:https://www.jianshu.com/p/420629118c10
ps:除了上述三种动画之外VectorDrawable、SVG以及Lottie都是应该知道的,我们会在后面另开一篇进行讲解