随着智能手机的普及,各种应用层出不穷。应用也越来越炫酷。很多炫酷的东西其实都是通过动画来实现。这里记录一下三天学习动画的过程。
为什么突然想起学习动画
原因:日常开发的时候用到了动画,列如activity之间的跳转,应用启动时候的闪屏页面。但是大多数时候只是去网上找一个现成的代码copy过来。
然后改改里面的属性。这样就满足了日常的需求。某天看手机里面的应用觉得某个动画好像很牛逼。可是网上搜索了下却没有找到类似的代码。这
时候一下就懵B了。怎么办???看来时候该学习学习动画了。于是开始了查阅资料。
Android动画的分类
1.补间动画(TweenAnimation)
2.帧动画(FrameAnimation)
3.属性动画(PropertyAnimation)
最初的Android只有补间动画和侦动画,随着版本的更新Android官方发现这两种动画并不能完全满足开发者的需求。于是在Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation),它的功能非常强大,弥补了之前补间动画的一些缺陷,几乎是可以完全替代掉补间动画了。
Android三种动画的分开学习
补间动画(TweenAnimation)
官方文档中是这样说的:Tween动画是操作某个控件让其展现出旋转、渐变、移动、缩放的这么一种转换过程。我们成为补间动画。我们可以以XML形式定义动画。也可以编码实现。
如果以XML形式定义一个动画,我们按照动画的定义语法完成XML,并放置于/res/anim目录下。文件名可以作为资源ID被引用;如果由编码实现。我们需要使用到Animation对象。建议使用XML文件定义,因为它更具可读性、可重用性。
其中包括alpha(透明度)、scale(缩放)、translate(移动)、rotate(翻转)
对应java code:
alpha->AlphaAnimation
scale->ScaleAnimation
translate->TranslateAnimation
rotate->RotateAnimation
Animation
属性详解
也就是说:无论我们补间动画的哪一种都已经具备了这种属性,也都可以设置使用这些属性中的一个或多个。
然后我们看看几个不同动画的自己的属性和用法
几种类型在xml的实际应用
1.首先alpha动画
2.scale动画
3.translate动画
4.rotate动画
在代码中通过AnimationUtils
调用。
Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.alpha); show.startAnimation(animation);
几种类型动画组合使用
AnimationSet
继承自Animation
是上面四种的组合容器管理类,没有自己特有的属性。他的属性继承自Animation
所以特别注意。当我们对set
标签使用Animation
的属性时会对该标签下的所有子控件都产生影响。在代码中可以这样使用:
Animation animation1 = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.rotate);
Animation animation2 = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.alpha);
Animation animation3 = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.translate);
Animation animation4 = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.scale);
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(animation1);
animationSet.addAnimation(animation2);
animationSet.addAnimation(animation3);
animationSet.addAnimation(animation4);
show.startAnimation(animationSet);
当然你也可以用组合的方式在xml
中将4个动画写在同一个set标签下面。像这样:
上面就是一个标准的使用我们定义的补间动画的模板。至于补间动画的使用,Animation
还有如下一些比较实用的方法介绍。
既然补间动画只能给View
使用,那就来看看View
中和动画相关的几个常用方法吧。如下:
提示重点:特别特别注意:补间动画执行之后并未改变View
的真实布局属性值。切记这一点,譬如我们在Activity
中有一个Button
在屏幕上方。我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的Button是没有任何反应的,而点击原来屏幕上方没有Button
的地方却响应的是点击Button的事件。
动画插值器的详解
动画插值器Interpolator
看上面的列子可以看见在rotate
里面有一句
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
Interpolator
是一个接口。官方已经给我们实现了常用的几种方式。让我们看看下图:
插值器的自定义
有时候你会发现系统提供的插值器不够用,可能就像View一样需要自定义。所以接下来我们来看看插值器的自定义,关于插值器的自定义分为两种实现方式:xml自定义实现(其实就是对现有的插值器的一些属性修改或者java代码实现方式。如下我们来说说
1.xml插值器自定义的用法
xml的方式主要是修改现有插值器的属性。但是有些属性是不能修改的。属性如下:
无可自定义的attribute
。
android:factor
浮点值,加速速率(默认值为1)。
android:tension
浮点值,起始点后拉的张力数(默认值为2)。
android:tension
浮点值,起始点后拉的张力数(默认值为2)。
android:extraTension
浮点值,拉力的倍数(默认值为1.5)。
无可自定义的attribute
。
android:cycles
整形,循环的个数(默认为1)。
android:factor
浮点值,减速的速率(默认为1)。
无可自定义的attribute
。
android:tension
浮点值,超出终点后的张力(默认为2)。
在xml
中定义
然后在xml
中调用
2.Java自定义插值器的(Java自定义插值器其实是xml自定义的升级,也就是说如果我们修改xml的属性还不能满足需求。那就可以选择通过Java来实现)方式。
我们可以实现Interpolator
接口:因为上面所有的Interpolator
都实现了Interpolator
接口。这个接口定义了一个方法:float getInterpolation(float input)
此方法由系统调用input
代表动画的时间,在0和1之间,也就是开始和结束之间。
线性(匀速)插值器定义如下:
public class CustomerInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
}
到此补间动画基本说完。
帧动画(FrameAnimation)
说明
贞动画按照名字理解就是动画一帧一帧的播放,这个动画相对来说也是很简单。
使用
大家经常用微信,在录音的时候会有一个语音的动画。效果图如下:
下面看看怎么使用,用法非常简单。
首先在xml
中定义
然后在代码中开始动画就可以了。
frame_img.setBackgroundResource(R.drawable.voice);
AnimationDrawable animationDrawable = (AnimationDrawable) frame_img.getBackground();
animationDrawable.start();
侦动画的使用就到此结束。
属性动画(PropertyAnimation)
属性动画引入的原因
前面说到的补间动画只能对view
进行渐变,移动,缩放,翻转等功能。如果你的需求不是这些,那么补间动画则完全用不上了。它的功能和局限性都很明显。来看看补间动画不能胜任的场景。如果我们想要对一个非View
的对象进行动画操作,补间动画就帮不上忙了。列如子一个自定义view
里面有一个Point
对象来管理坐标,然后在onDraw()
方法中通过坐标进行绘制。如果我们可以对Point
对象进行动画操作,那么整个自定义View
的动画效果就有了。
补间动画是不能完成这个操作的。补间动画的动画改变的只是view
的显示效果而不是改变其属性。而属性动画真是改变了其属性。新引入的属性动画机制已经不再是针对于view
来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再只是一种视觉上的动画效果了。它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性。所以我们仍然可以将一个view
进行移动或者缩放。但同时也可以对自定义view
中的point
对象进行动画操作了。我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去完成了。
ValueAnimator
ValueAnimator
是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator
这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator
并且告诉它动画所需运行的时长,那么ValueAnimator
就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外ValueAnimator
还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
但是ValueAnimator
的用法却一点都不复杂,我们先从最简单的功能看起吧。比如说想要将一个值从0平滑过渡到1,时长300毫秒,就可以这样写:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(3000);
valueAnimator.start();
通过ofFloat
方法可以创造出一个ValueAnimator的实列,然后这里设置了setDuration(3000)
意思是从0f-1f变化的时间为3000毫秒,然后调用start()
方法开始。
通过上面的代码已经设置好了动画,但是没看到动画作用在什么地方,到底有没有效果我们不得而知。试着运行了一下,发现不管是控制台还是界面上都没有效果。查看api
发现,可以对动画进行监听。下面我们这样改写代码:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float val = (float) valueAnimator.getAnimatedValue();
Log.i("joker", String.valueOf(val));
}
});
valueAnimator.setDuration(200);
valueAnimator.start();
代码中看到我们实现了addUpdateListener
其中有一个onAnimationUpdate
方法。在动画绘制的过程中会不断的进行回调。现在我们在回调的方法中对值进行了打印,可以看看控制台的信息。
从图中可以看出,在200毫秒内,从0f-1f完成了平滑的过度。让我们在来看看方法。查看源码
public static ValueAnimator ofFloat(float... values) {
throw new RuntimeException("Stub!");
}
从源码中可以发现传入的参数为可变参数,我们再次改写代码:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f,0f);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float val = (float) valueAnimator.getAnimatedValue();
Log.i("joker", String.valueOf(val));
}
});
valueAnimator.setDuration(200);
valueAnimator.start();
控制台信息如下:
可以看见动画从0f-1f然后又从1f-0f完成了平滑的过度。你有时候或许不需要小数。这时候你可以调用ofInt
方法。用法和ofFloat
一样。ValueAnimator
中最常用的就是这两个方法。当然还有别的方法可以通过api
查看。其中还有ofObject
方法。ofObject
方法使用比较前面两个方法稍微复杂画一些。后面在解释。
ObjectAnimator
相比起ValueAnimator
,ObjectAnimator
在我们的实际开发中更常用。按照名字来看就知道。这是对一个对象进行操作的动画类,可以直接关联到对象上面去。
让我们先看看查ObjectAnimator
的源码
从源码中可以发现ObjectAnimator
是继承ValueAnimator
类。Object类可以对对象的任意属性进行操作,列如
View的rotation
属性,下面我们通过对ImageView
的rotation
属性的改变来看看怎么用这个类。代码如下:
1.首先在xml
中定义了一个ImageView
2.然后在代码中使用
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(show, "rotation", 0f, 360f);
objectAnimator.setDuration(3000);
objectAnimator.start();
这段代码的意思是将show
对象进行0f-360f的旋转。效果如下图
当然你也可以对alpha
,scale
,translate
进行操作。
alpha
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(show, "alpha", 1f, 0f,1f);
objectAnimator.setDuration(3000);
objectAnimator.start();
效果如图:
scale
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(show, "scaleX", 1f, 2f,1f);
objectAnimator.setDuration(3000);
objectAnimator.start();
效果如图:
translate
float currentX = show.getTranslationX();
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(show, "translationX", currentX, 500, currentX);
objectAnimator.setDuration(3000);
objectAnimator.start();
效果如图:
这样看来ObjectAnimator
的用法看似没什么问题了。但是第二个参数到底应该怎么传入值?其实第二个参数的参数可以是任意的,因为ObjectAnimator
就是针对Object
进行设计的。他的目标就是通过对任意对象的熟悉进行操作,来达到改变从而决定该如何显示。
列如ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(show, "alpha", 1f, 0f,1f);
这句话就是不断改变show
的alpha
属性值从1f-0f-1f的改变。然后show
对象需要根据alpha
属性值的改变来不断刷新界面的显示,从而让用户可以看出淡入淡出的动画效果。这里的show
是一个ImageView
。
那么ImageView
对象中是不是有alpha
属性这个值呢?没有,不仅ImageView
没有这个属性,连它所有的父类也是没有这个属性的。这就奇怪了,ImageView
当中并没有alpha
这个属性,ObjectAnimator
是如何进行操作的呢?其实ObjectAnimator
内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get
和set
方法。因此alpha
属性所对应的get
和set
方法应该就是:
public void setAlpha(float value);
public float getAlpha();
那么ImageView
对象中是否有这两个方法呢?确实有,并且这两个方法是由View
对象提供的,也就是说不仅ImageView
可以使用这个属性来进行淡入淡出动画操作,任何继承自View
的对象都可以的。
既然alpha
是这个样子,相信大家一定已经明白了。前面我们所用的所有属性都是这个工作原理,那么View
当中一定也存在着setRotation()
、getRotation()
、setTranslationX()
、getTranslationX()
、setScaleY()
、getScaleY()
这些方法。
组合动画
前面所提到的动画都显得很单一,如果我要透明的同时进行旋转呢?当然了google
肯定不会这样简单的问题都想不到。所以google
提供了AnimatorSet
来实现,这个类提供了一个play()
方法。如果我们向这个方法中传入一个Animator
对象(ValueAnimator或ObjectAnimator)
将会返回一个AnimatorSet.Builder的实例。
AnimatorSet.Builder
中包括以下四个方法:
after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行
有了这几个方法后我们就可以组合先前的动画了,列如我们让图片先移动,然后再旋转,在旋转的时候渐变。
效果图如下:
代码如下:
float currentX = show.getTranslationX();
ObjectAnimator translation = ObjectAnimator.ofFloat(show, "translationX", currentX, 500, currentX);
ObjectAnimator rotation = ObjectAnimator.ofFloat(show, "rotation", 0f, 360f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(show, "alpha", 1f, 0f,1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(translation).after(rotation).with(alpha);
animatorSet.setDuration(5000);
animatorSet.start();
从图中可以看见三个动画按照预先的设计的方式进行了播放。
Animator监听器
在很多时候我们需要监听动画的事件,列如动画的开始和结束,然后在开始或者结束的时候去处理一些逻辑。Animator
类当中提供了一个addListener()
方法,这个方法接收一个AnimatorListener
,我们只需要去实现这个AnimatorListener
就可以监听动画的各种事件了。
大家已经知道ObjectAnimator
是继承自ValueAnimator
的,而ValueAnimator
又是继承自Animator
的,因此不管是ValueAnimator
还是ObjectAnimator
都是可以使用addListener()
这个方法的。另外AnimatorSet
也是继承自Animator
的,因此addListener()
这个方法算是个通用的方法。
animatorSet.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) {
}
});
可以看见我们需要重写4个方法,其中onAnimationStart
在动画开始的时候被调用,onAnimationEnd
在动画结束的时候被调用,onAnimationCancel
在动画被取消的时候调用,onAnimationRepeat
在动画被重复的时候调用。大多数时候我们并不需要这样多方法。这时候Android
给我们提供了一个适配器类,AnimatorListenerAdapter
使用这个类就可以实现对某一方法的监听。
animatorSet.addListener(new AnimatorListenerAdapter() {
});
因为AnimatorListenerAdapter
已经实现了每个接口所以我们不实现也不会有问题。我们要监听动画结束只需要监听onAnimationEnd
方法就可以。省去了很多无用的代码。
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
XML中编写属性动画
补间动画可以在xml
中编写,当然属性动画也可以在xml
中编写。要想使用xml
编写首先在res
目录下面建立一个animator
文件夹在xml
中我们一共可以使用三种标签。
对应代码中的ValueAnimator
对应代码中的ObjectAnimator
对应代码中的AnimatorSet
和先前一样我们要从0f平滑的过度到1f在xml中就可以这样编写。
可以看出非常的简单易懂可读性也很高。
我们如果要改变alpha
属性就可以这样写。
复杂的动画也可以用xml
来编写,这时候需要set
标签
在代码中使用编写好的动画。
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
ValueAnimator之TypeEvaluator
前面已经说了ValueAnimator
,那这个TypeEvaluator
到底是用来干嘛的?简单来说,就是告诉动画系统如何从初始值过度到结束值。我们在上一篇文章中学到的ValueAnimator.ofFloat()
方法就是实现了初始值与结束值之间的平滑过度,那么这个平滑过度是怎么做到的呢?其实就是系统内置了一个FloatEvaluator
它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator
的代码实现:
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
可以看到FloatEvaluator
实现了TypeEvaluator
接口,然后重写evaluate()
方法。evaluate()
方法当中传入了三个参数,第一个参数fraction
非常重要,这个参数用于表示动画的完成度的。我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction
这个系数再加上初始值,那么就得到当前动画的值了。
好的,那FloatEvaluator
是系统内置好的功能,并不需要我们自己去编写,但介绍它的实现方法是要为我们后面的功能铺路的。前面我们使用过了ValueAnimator
的ofFloat()
和ofInt()
方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator
中还有一个ofObject()
方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator
来告知系统如何进行过度。
前面说到的自定义view
用point
来管理坐标和ofObject
。相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator
来告知系统如何进行过度。
首先我们定义一个Point
类
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
然后我们自定义PointEvaluator
public class PointEvaluator implements TypeEvaluator {
@Override
public Point evaluate(float fraction, Point start, Point end) {
float startX = start.getX();
float startY = start.getY();
float endX = end.getX();
float endY = end.getY();
float x = startX + fraction * (endX - startX);
float y = startY + fraction * (endY - startY);
return new Point(x, y);
}
}
可以看到,PointEvaluator
同样实现了TypeEvaluator
接口并重写了evaluate()
方法。其实evaluate()
方法中的逻辑还是非常简单的,先是将startValue
和endValue
强转成Point
对象,然后同样根据fraction
来计算当前动画的x
和y
的值,最后组装到一个新的Point
对象当中并返回。
这样我们就将PointEvaluator
编写完成了。接下来我们就可以非常轻松地对Point
对象进行动画操作了,比如说我们有两个Point
对象,现在需要将Point1
通过动画平滑过度到Point2
就可以这样写:
Point point1 = new Point(0f, 0f);
Point point2 = new Point(500f, 500f);
ValueAnimator animator = ObjectAnimator.ofObject(new PointEvaluator(),point1,point2);
animator.setDuration(500);
animator.start();
代码很简单,这里我们先是new
出了两个Point
对象,并在构造函数中分别设置了它们的坐标点。然后调用ValueAnimator
的ofObject()
方法来构建ValueAnimator
的实例,这里需要注意的是,ofObject()
方法要求多传入一个TypeEvaluator
参数,这里我们只需要传入刚才定义好的PointEvaluator
的实例就可以了。
好的,这就是自定义TypeEvaluator
的全部用法。掌握了这些知识之后,我们就可以来尝试一下如何通过对Point
对象进行动画操作,从而实现整个自定义View
的动画效果。
下面是自定义view
的代码:
public class ShowView extends View {
private Point point;
private Paint paint;
private final float RADIUS = 50;
public ShowView(Context context) {
super(context);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
}
public ShowView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
}
public ShowView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (point == null) {
point = new Point(RADIUS, RADIUS);
canvas.drawCircle(point.getX(), point.getY(), RADIUS, paint);
Point point1 = new Point(0f, 0f);
Point point2 = new Point(getWidth(), getHeight());
ValueAnimator animator = ObjectAnimator.ofObject(new PointEvaluator(), point1, point2);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
point = (Point) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.setDuration(5000);
animator.start();
invalidate();
} else {
canvas.drawCircle(point.getX(), point.getY(), RADIUS, paint);
}
}
public class PointEvaluator implements TypeEvaluator {
@Override
public Point evaluate(float fraction, Point start, Point end) {
float startX = start.getX();
float startY = start.getY();
float endX = end.getX();
float endY = end.getY();
float x = startX + fraction * (endX - startX);
float y = startY + fraction * (endY - startY);
return new Point(x, y);
}
}
}
只需要在布局文件里面这样写就会用动画效果了:
效果如图:
ObjectAnimator之TypeEvaluator
ObjectAnimator之TypeEvaluator
的用法和ValueAnimator之TypeEvaluator
的用法基本相同。这里就不再解释。
Interpolator的用法
补间动画的时候已经讲到了Interpolator
的用法。只不过在属性动画中新增了一个TimeInterpolator
接口,这个接口是用于兼容之前的Interpolator
的,这使得所有过去的Interpolator
实现类都可以直接拿过来放到属性动画当中使用,那么我们来看一下现在TimeInterpolator
接口的所有实现类,如下图所示:
可以看到,TimeInterpolator
接口已经有非常多的实现类了。这些都是Android
系统内置好的并且我们可以直接使用的Interpolator
。每个Interpolator
都有它各自的实现效果,比如说AccelerateInterpolator
就是一个加速运动的Interpolator
,而DecelerateInterpolator
就是一个减速运动的Interpolator
。
在代码中设置:
anim.setInterpolator(new BounceInterpolator());
通过上面的一句代码就可以使用系统的Interpolator
了。
但是,只会用一下系统提供好的Interpolator
,我们显然对自己的要求就太低了,既然是学习属性动画的高级用法,那么自然要将它研究透了。下面我们就来看一下Interpolator
的内部实现机制是什么样的,并且来尝试写一个自定义的Interpolator
。
首先看一下TimeInterpolator
的接口定义,代码如下所示:
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
OK,接口还是非常简单的,只有一个getInterpolation()
方法。大家有兴趣可以通过注释来对这个接口进行详解的了解,这里我就简单解释一下,getInterpolation()
方法中接收一个input
参数,这个参数的值会随着动画的运行而不断变化,不过它的变化是非常有规律的,就是根据设定的动画时长匀速增加,变化范围是0到1。也就是说当动画一开始的时候input
的值是0,到动画结束的时候input
的值是1,而中间的值则是随着动画运行的时长在0到1之间变化的。
说到这个input
的值,我觉得有不少朋友可能会联想到我们在“中”篇文章中使用过的fraction
值。那么这里的input
和fraction
有什么关系或者区别呢?答案很简单,input
的值决定了fraction
的值。input的值是由系统经过计算后传入到getInterpolation()
方法中的,然后我们可以自己实现getInterpolation()
方法中的算法,根据input
的值来计算出一个返回值,而这个返回值就是fraction
了。
因此,最简单的情况就是input
值和fraction
值是相同的,这种情况由于input
值是匀速增加的,因而fraction
的值也是匀速增加的,所以动画的运动情况也是匀速的。系统中内置的LinearInterpolator
就是一种匀速运动的Interpolator
,那么我们来看一下它的源码是怎么实现的:
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
这里我们只看getInterpolation()
方法,这个方法没有任何逻辑,就是把参数中传递的input
值直接返回了,因此fraction
的值就是等于input
的值的,这就是匀速运动的Interpolator
的实现方式。
当然这是最简单的一种Interpolator
的实现了。现在要自己实现也很容易了。
ViewPropertyAnimator的用法
ViewPropertyAnimator
是3.1中引进的一个新功能。
我们都知道,属性动画的机制已经不是再针对于View
而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。但是,在绝大多数情况下,我相信大家主要都还是对View
进行动画操作的。Android
开发团队也是意识到了这一点,没有为View
的动画操作提供一种更加便捷的用法确实是有点太不人性化了,于是在Android 3.1
系统当中补充了ViewPropertyAnimator
这个机制。
那我们先来回顾一下之前的用法吧,比如我们想要让一个TextView
从常规状态变成透明状态,就可以这样写:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);
animator.start();
看上去复杂吗?好像也不怎么复杂,但确实也不怎么容易理解。我们要将操作的view
、属性、变化的值都一起传入到ObjectAnimator.ofFloat()
方法当中。虽然看上去也没写几行代码,但这不太像是我们平时使用的面向对象的思维。
那么下面我们就来看一下如何使用ViewPropertyAnimator
来实现同样的效果,ViewPropertyAnimator
提供了更加易懂、更加面向对象的API。如下所示:
textview.animate().alpha(0f);
果然非常简单,不过textview.animate()
这个方法是怎么回事呢?animate()
方法就是在Android 3.1
系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator
对象。也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,这里我们调用了alpha()
方法并转入0,表示将当前的textview
变成透明状态。
怎么样?比起使用ObjectAnimator
。ViewPropertyAnimator
的用法明显更加简单易懂吧。除此之外,ViewPropertyAnimator
还可以很轻松地将多个动画组合到一起,比如我们想要让textview
运动到500,500这个坐标点上,就可以这样写:
textview.animate().x(500).y(500);
用法很简单,同样也是使用连缀的方式。相信大家现在都已经体验出来了ViewPropertyAnimator
其实并没有什么太多的技巧可言,用法基本都是大同小异的,需要用到什么功能就连缀一下,因此更多的用法大家只需要去查阅一下文档,看看还支持哪些功能,有哪些接口可以调用就可以了。
那么除了用法之外,关于ViewPropertyAnimator
有几个细节还是值得大家注意一下的。
整个ViewPropertyAnimator
的功能都是建立在View
类新增的animate()
方法之上的,这个方法会创建并返回一个ViewPropertyAnimator
的实例,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。
大家注意到,在使用ViewPropertyAnimator
时,我们自始至终没有调用过start()
方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动。并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator
上设置的方法都执行完毕后,动画就会自动启动。当然如果不想使用这一默认机制的话,我们也可以显式地调用start()
方法来启动动画。
ViewPropertyAnimator
的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此调用完一个方法之后可以直接连缀调用它的另一个方法,这样把所有的功能都串接起来,我们甚至可以仅通过一行代码就完成任意复杂度的动画功能。
Android
动画基本也就完了。