1. 分类
动画必不可少,因为它可以让交互更加流畅、自然,增强用户满意度。在Android中动画主要有以下4类:
- 帧动画
- 补间动画
- 属性动画(Android3.0)
- VectorDrawable(Android5.0)(后期会单独更新博客讲解)
2. 原理
动画实际上就是在指定的时间段内持续地修改某个属性的值,使得该值在在指定的范围之内平滑的过渡。
看图可知:动画就是在某个时间点根据一定的计算方式计算出属性的取值,并且设置给目标对象。在动画的执行周期内持续执行这个过程,形成动画的效果。
3. 帧动画(Frame动画)
帧动画是一系列图片按照一定的顺序展示的过程。它的原理是在一定的时间段内切换多张有细微差异的图片从而达到动画的效果。
帧动画可以在xml中定义,也可以编码实现。如果再xml文件中定义的话,可以放置在res下的anim或drawable目录中,文件名可以作为资源id在代码中引用,如果完全编码实现,需要使用到AnimationDarawable对象。
在xml中定义帧动画时,<animation-list>元素必须为根元素,它可以包含一或多个<item>元素。
android:onshot如果定义为true时,此动画只会执行一次,如果为false则一直循环。
<item>元素代表一帧动画,android:drawable指定此帧动画所对应的图片资源,android:duration代表此帧持续的时间,单位为毫秒。
3.1 栗子:首先在XML中定义名为test_drawable.xml的文件,这里就不再讲解在代码中定义帧动画了,推荐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/cancel"
android:duration="1000"></item>
<item
android:drawable="@drawable/hiss"
android:duration="1000"></item>
<item
android:drawable="@drawable/night"
android:duration="1000"></item>
<item
android:drawable="@drawable/ok"
android:duration="1000"></item>
</animation-list>
- 然后把动画设置给某个View:例如:将该动画设置为某个ImageView的背景:
<ImageView
android:layout_centerInParent="true"
android:id="@+id/iv_anim"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/test_drawable"
/>
- 然后还需要通过Java代码启动该动画:
((AnimationDrawable) mImageView.getBackground()).start();
4. 补间动画(tween动画或者View动画)
补间动画就是操作某个控件让其展示出旋转、渐变、移动、缩放的一种转换过程,这成为补间动画。
可以在XML中定义,也可以编码实现,后者就不讲解了,推荐XML定义。
在XML中定义并放置于/res/anim目录下,文件名可以作为资源的id被引用;如果编码实现,需要使用到Animation对象或者AnimationSet。
Animation对象有四个:AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation四种动画方式,这里只讲解AnimationSet动画集合。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/cycle_interpolator"
android:shareInterpolator="true">
<alpha
android:duration="5000"
android:fromAlpha="1.0"
android:toAlpha="0.0"></alpha>
<scale
android:duration="5000"
android:fromXScale="0.2"
android:fromYScale="0.2"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.5"
android:toYScale="1.5"></scale>
<translate
android:duration="5000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="500"></translate>
<rotate
android:duration="5000"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="-1"
android:repeatMode="restart"
android:startOffset="0"
android:toDegrees="360">
</rotate>
</set>
AnimationSet animationSet = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.test_tween);
mImageView.startAnimation(animationSet);
说明:
<set>是一个动画容器,管理多个动画的数组,与之相对应的Java对象是AnimationSet。
它有两个属性:android:interpolator代表的是一个插值器的资源,可以引用系统自带的插值器资源,也可以自定义插值器资源,默认值死活匀速插值器。android:shareInterpolator代表<set>里面的多个动画是否要共享插值器,默认值是true,即共享插值器,如果是false,那么<set>插值器就不再起作用,我们要在每个动画中加入插值器。
alpha:透明度 淡入淡出效果 fromAlpha:开始透明度 toAlpha结尾透明度 浮点值。
scale:缩放 调整空间尺寸 fromXScale:起始的X方向上相对自身的缩放比例:比如1.0代表无变化 0.5代表起始时缩小一倍 2.0代表放大一倍。toXScale fromYScale toYScale 不再讲解 pivotX代表缩放的中轴点X坐标,pivotY代表缩放的中轴点Y坐标。 如果想以中轴点为图像的中心,可以把两个属性值定义为0.5或者50%。 浮点值 。
translate:移动 水平、垂直的位移 fromXDelta代表起始X方向的位置 fromYDelta代表起始Y方向的位置 toXDelta代表结尾X方向的位置 toYDelta代表结尾Y方向的位置。 3种表示方式:浮点数(相对自身原始位置的像素值)、num%(代表相对于自己的百分比)、num%p(代表相对于父类控件的百分比) 以num%为例:toXDelta定义为100% 就表示在X方向上移动自己的1倍距离。
rotate: 旋转动画 fromDegrees起始角度 toDegrees结尾的角度 pivotX 旋转中心的X坐标 pivotY旋转中心的Y坐标 坐标有3种表示方式:数字方式代表相对于自身左边缘的像素值,num%方式代表相对于自身左边缘或顶边缘的百分比,num%p代表相对于父容器的左边缘或顶边缘的百分比。
注意:
- 补间动画只能运用在View上,功能较为局限:比如旋转只能在x、y轴,不能在z轴。
- 另外补间动画的View的位置,系统认定始终在起始位置。
5. 属性动画
补间动画的最大缺陷就是动画改变的只是显示,但View位置并没有发生变化,View移动后不能响应点击事件。
属性动画解决了补间动画的缺陷,其经常使用的两个类是ObjectAnimator和AnimatorSet。
属性动画通过调用属性get、set方法真实地控制一个View的属性值。
5.1 ObjectAnimator
ObjectAnimator是属性动画最重要的类:创建一个ObjectAnimator只需通过静态工厂类直接返还一个ObjectAnimator对象。
参数包括一个对象和对象的属性名字,但这个属性必须有get和set方法,其内部会通过Java发射机制调用set方法修改对象的属性值。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mImageView,"translationX",200);
objectAnimator.setDuration(3000);
objectAnimator.start();
说明:通过ObjectAnimator的静态方法,创建一个ObjectAnimator对象,查看ObjectAnimator.java的静态方法ofFloat():
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
说明:第一个参数是Object的目标View;第二个参数是要操作的属性;最后一个参数是一个可变的float类型的数组,需要传进去该属性变化的取值过程,这里设置了一个参数,变化到200.
- 属性动画也可以设置市场、插值器等属性。下面是一些常用的属性动画的属性值:
translationX 和 translationY : 用来沿着X轴或者Y轴进行平移。
rotation、rotationX、rotationY:用来围绕View的支点进行旋转。
PrivotX 和 PrivotY :控制VIew对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认该支点位置是View对象的中心点。
alpha :透明度,默认是1(不透明),0代表完全透明
x和y: 描述View对象在其容器中的最终位置。
- 需要注意ObjectAnimator使用时,要操作的属性必须有set和get方法,不然失效。当然可以自定义一个属性类或者包装类来间接地给这个属性增加get和set方法:
private static class MyView {
private View mTarget;
private MyView(View mTarget) {
this.mTarget = mTarget;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
MyView myView = new MyView(mImageView);
ObjectAnimator.ofInt(myView, "width", 5000).setDuration(5000).start();
- ObjectAnimator 动画的监听
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
说明:大部分的时候我们只关心onAnimationEnd事件,Android也提供了AnimatorListenerAdapter来让我们选择必要的事件进行监听。
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
5.2 ValueAnimator
ValueAnimator是在一定的时间段内不断地修改对象的某个属性值。
我们只需要将属性的取值范围、运行时长提供给ValueAnimator,那么它就会自动帮我们计算属性值在各个动画运行时段的取值,这些值会按照一定的计算方式来实现平滑过渡。
此外:ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。
ValueAnimator 不仅功能强大,API设计也非常简单:通过ofFloat、ofInt等静态工厂函数构建ValueAnimator:例:下面就是我们将数值从1.0 过渡到0.0的动画:
//ValueAnimator
private void startValueAnimation() {
final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
animator.setDuration(5000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float newValue = (Float) valueAnimator.getAnimatedValue();
Log.e("","startValueAnimation newValue :"+newValue);
mImageView.setAlpha(newValue);
}
});
animator.start();
}
说明:
- ValueAnimator不提供任何动画效果,它更像一个数值发生器,用来产生一定规律的数字,从而让调用者控制动画的实现过程。
- 通常:在ValueAnimator的AnimatorUpdateListener中监听数值的变化,从而完成动画的变化。
强调:
- 启动动画后,每次更新属性值时就会调用AnimatorUpdate函数,在这里获取新的属性值。
- 这里并没有将这个值运动到具体的对象上,它只操作属性值本身,这个值不属于某个具体的对象,所以它的优势在于可以运用到任意的对象上。
5.3 AnimatorSet
- 先看代码:
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, 200.0f, 0f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageView,"scaleX",1.0f,2.0f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageView,"rotationX",0.0F,90.0F,0.0F);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.play(animator1).with(animator2).after(animator3);
说明:
- 首先创建了3个ObjectAnimator,分别是animator1 animator2 animator3,然后创建了AnimatorSet。
- 先执行animator3,然后同时执行animator1和animator2。
- 也可以调用set.playTogether(animator1,animator2)来使者两种动画同时执行。
- AnimatorSet类提供了一个play()方法,我们将Animator对象(ValueAnimator或ObjectAnimator),将会返回一个AnimatorSet.Builder实例,我们看看play方法的源码:
public Builder play(Animator anim) {
if (anim != null) {
mNeedsSort = true;
return new Builder(anim);
}
return null;
}
- 我们可以看到play方法中创建了一个AnimatorSet.Builder类,这个Builder类是AnimatorSet的内部类,我们看看Builder类中有什么:
public class Builder {
private Node mCurrentNode;
Builder(Animator anim) {
mCurrentNode = mNodeMap.get(anim);
if (mCurrentNode == null) {
mCurrentNode = new Node(anim);
mNodeMap.put(anim, mCurrentNode);
mNodes.add(mCurrentNode);
}
}
public Builder with(Animator anim) {
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
node.addDependency(dependency);
return this;
}
public Builder before(Animator anim) {
mReversible = false;
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
node.addDependency(dependency);
return this;
}
public Builder after(Animator anim) {
mReversible = false;
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(node, Dependency.AFTER);
mCurrentNode.addDependency(dependency);
return this;
}
public Builder after(long delay) {
// setup dummy ValueAnimator just to run the clock
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(delay);
after(anim);
return this;
}
}
- 从Builder源码中我们可以看出来它采用了建造者模式,每次调用方法时都返回Builder自身用于继续构建。AnimatorSet.Builder中包括以下4个方法:
after(Animator anim):将现有的动画插入到传入的动画之后执行。
after(long delay):将现有动画延迟指定毫秒之后执行。
before(Animator anim):将现有动画插入到传入的动画之前执行。
with(Animator anim):将现有的动画和传入的动画同时执行。