最近的项目用到动画比较多,然后就想写一篇总结性的东西,一是巩固一下知识,二是巩固一下知识,三是巩固一下知识….加深自己对动画的理解。其中内容很多都是结合了很多大神的博客结合到一起的,当然也有我自己的理解。上面有目录,大家也可以直接跳到自己感兴趣的部分。
Android提供了三种动画类型:
- View Animation 最简单,只支持简单的缩放、平移、旋转、透明度基本的动画。
- Drawable Animation 比较有针对性,只是图片的替换。
- Property Animation 是通过动画的方式来改变View的属性。
动画其实就是一帧帧的画面顺着时间顺序,在我们眼中形成视觉残留的效果。所以在动画中,时间的概念是很重要的,只有时间的变化,才能形成动画效果。
Alpha,渐变
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:startOffset="500"
android:duration="500"/>
set>
rotate,旋转
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<rotate
android:fromDegrees="0"
android:toDegrees="+360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"/>
set>
scale缩放
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="0.0"
android:fromYScale="1.0"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"/>
set>
translate位移
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<translate
android:fromXDelta="0%"
android:toXDelta="100%"
android:fromYDelta="0%"
android:toYDelta="100%"
android:duration="2000"/>
set>
调用
Animation animation = AnimationUtils.loadAnimation(Animation1Activity.this, R.anim.alpha);
// 启动动画
image.startAnimation(animation);
当然,你也可以直接使用代码来创建出一个动画的实例:
// 这里都指定了Animation.RELATIVE_TO_SELF这个参数,相对于自己,如果不指定,默认是相对父控件。
TranslateAnimation translateAnimation =
new TranslateAnimation(
Animation.RELATIVE_TO_SELF,0f,
Animation.RELATIVE_TO_SELF,100f,
Animation.RELATIVE_TO_SELF,0f,
Animation.RELATIVE_TO_SELF,100f);
translateAnimation.setDuration(1000);
view.startAnimation(translateAnimation);
这个动画是在没啥子好说的,The End…
Drawable Animation 可以让我们按顺序加载一系列的资源来创建一个动画。动画的创建和传统意义上电影胶卷的播放一样,是通过加载不同的图片,然后按顺序进行播放来实现的。
和上面一样,我们在drawable下创建一个XML文件:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/scan1" android:duration="100" />
<item android:drawable="@drawable/scan2" android:duration="100" />
<item android:drawable="@drawable/scan3" android:duration="100" />
<item android:drawable="@drawable/scan4" android:duration="100" />
animation-list>
通过上面的代码我们可以看到,这个动画包含 4 帧。另外我们可以通过设置 节点下的 android:oneshot 属性来控制动画的循环次数,如果将 android:oneshot 的属性设置为 true,那么这个动画只会循环一次并停留在最后一帧。如果设置为 false,那么这个动画将会不停的循环下去。将这个文件命名文 scan.xml 并保存到项目的 /res/drawable/ 目录下,然后它就可以当作背景图片被添加到另一个视图上,并被调用显示。参见如下代码:
view.setBackgroundResource(R.drawable.scan);
animationDrawable= (AnimationDrawable) view.getBackground();
animationDrawable.start();
animationDrawable.stop();
同样的,他可以使用代码来实现:
mAnimationDrawable = new AnimationDrawable();
for (int i = 1; i <= 15; i++) {
int resourcesId = getResources().getIdentifier("image"+i, "drawable", mContext.getPackageName());
mDrawable = getResources().getDrawable(resourcesId);
mAnimationDrawable.addFrame(mDrawable, 500);
}
mAnimationDrawable.setOneShot(false);
view.setBackgroundDrawable(mAnimationDrawable);
mAnimationDrawable.start();
属性动画,顾名思义就是:沿着一定的时间顺序,通过改变View的属性,从而得到的动画的效果。(为我的总结能力点个赞!)引入属性动画最大的作用就是为了“眼见为实”,对于ViewAnimation,动画的移动和缩放并没有真正的改变控件的位置和热区,而属性动画则可以做到这一点。而同样重要的一点则是他的灵活性,属性动画几乎可以定义出你想要的所有动效。
ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
首先来认识下他的相关类。
Interface
Interface | 说明 |
---|---|
Animator.AnimatorListener | 动画监听器从一个动画接收通知消息。 |
Animator.AnimatorPauseListener | A pause listener receives notifications from an animation when the animation is paused or resumed. |
LayoutTransition.TransitionListener | 此接口用来监听转变的开始事件与结束事件。 |
TimeAnimator.TimeListener | 此接口实现可将自身设置为TimeAnimator实例的更新监听器并接受回调,在每一帧上获得自动画开始以来的时间和自上一帧以来的时间间隔。 |
TimeInterpolator | 时间内插器定义了一个动画的变化的速率。 |
TypeEvaluator | 使用setEvaluator(TypeEvaluator)功能需要的接口。 |
ValueAnimator.AnimatorUpdateListener | 动画每一帧都会对此接口进行回调 |
ValueAnimator | 实例的更新监听器并接受回调, 当已经为该ValueAnimator计算出当前帧的值以后。 |
Class
Class | 说明 |
---|---|
Animator | 是那些具有启动、结束且包含AnimatorListeners的基础支持功能的类的超级类。 |
AnimatorInflater | 此类用来将Animator XML文件实例化并注入Animator类。 |
AnimatorListenerAdapter | 此适配器为Animator.AnimatorListener的方法提供了空的实现。 |
AnimatorSet | 此类将一组Animator对象按指定的顺序播放。 |
AnimatorSet.Builder | 为方便向AnimatorSet加入animation对象(连同不同animation对象之间的关系)工具类。 |
ArgbEvaluator | 此计算器可作为类型内插器,于代表ARGB颜色的整型值之间。 |
FloatEvaluator | 此计算器可作为类型内插器,于浮点型数值之间。 |
IntEvaluator | 此计算器可作为类型内插器,于整型数值之间。 |
Keyframe | 该类包含了一个animation类的time/value值对。 |
LayoutTransition | 该类在 ViewGroup类中使得背景可以是自动的动画。 |
ObjectAnimator | ValueAnimator的子类,提供了对目标对象的属性动画的支持。 |
PropertyValuesHolder | 该类载有一个属性的信息,以及此属性在动画过程需要用到的值。 |
RectEvaluator | This evaluator can be used to perform type interpolation between Rect values. |
TimeAnimator | 该类为已经与系统中所有其他动画对象同步的监听器提供一个简单的回调机制。 |
ValueAnimator | 该类为运行中的动画对象提供一个简单的时间引擎,以便计算动画的值并将结果设置于目标对象。 |
这里要着重记录的是这几个类:
- ValueAnimator 属性动画的核心类
- ObjectAnimator 继承 ValueAnimator 对ValueAnimator进行了一层封装
- AnimatorSet 可以同时运行一组动画
- PropertyValuesHolder 他代表一个在动画运行中需要过度到的值。
- TypeEvaluator 实现此接口的实例,将决定AnimatorUpdateListener接收到的值。
这里有必要说明一下,上面对动画的描述是“运行”,而不是“播放”。因为属性动画的本质是在指定的时间内于指定的值之间过度。这就意味着他并不仅限于View控件。举例来说,他可以是一个不断运动的看不见的点,而你在需要的时候可以通过回调知道在某一时间点对应的值,从而进行canvas的绘制。
API摘自这里…
ObjectAnimator
示例代码:
private void runAnimator(View view){
// 旋转
String rotationX = "rotationX";
String rotationY = "rotationY";
// 渐变
String alpha = "alpha";
// 缩放
String scale = "scale";
String scaleX = "scaleX";
String scaleY = "scaleY";
// 移动
String translationX= "translationX";
String translationY= "translationY";
// 第二个参数传入相应的动画名称就OK了
ObjectAnimator.ofFloat(view , rotationX , 0.0f , 360f)
.setDuration(2000)
.start();
}
ObjectAnimator 提供了ofInt、ofFloat、ofObject
他们都是设置(动画作用的元素、动画名称、动画开始、结束、以及中间的任意个属性值),这就意味着可以传任意多个值,动画会依次作用在对应的值上。
可以看出,他除了是以动画方式改变View形态之外,跟ViewAnimator没什么区别了。
他可以看做是Animator家族中的上层了,被封装过,使用简单,又兼备了属性动画的特性,
如果想做出一个可以渐变,而同时又在放大的动画效果呢,我们就要自定义他的父类了。
ValueAnimator
Class Overview:
This class provides a simple timing engine for running animations which calculate animated values and set them on target objects.
There is a single timing pulse that all animations use. It runs in a custom handler to ensure that property changes happen on the UI thread.
By default, ValueAnimator uses non-linear time interpolation, via the AccelerateDecelerateInterpolator class, which accelerates into and decelerates out of an animation. This behavior can be changed by calling setInterpolator(TimeInterpolator).
API中是这么描述他的,借助翻译软件……大致意思就是,他是一个简单的定时引擎,在动画运行时计算他的值,并设置给指定对象。
前面我们就提到了,在动画中,时间概念是非常重要的,所以接下来就来通过动画运行的时间来自定义一个属性动画。
这里只讲述3种获得实例的方法,分别是ofFloat(); ofInt(); ofObject(); 代码如下:
示例代码:
// 空构造方法
ValueAnimator animator = new ValueAnimator();
// 创建一个实例 ,开始点0f,结束点位100f,期间经过50
ValueAnimator animator = ValueAnimator.ofFloat(0, 50 ,100);
// 创建一个实例 ,开始点0f,结束点位100
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
//创建一个实例 ,开始点0,结束点位100,自定义返回的类型
ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 这里的三个值分别代表了:一个0~1的float, 开始点, 结束点
return new XXX()
}
} , 0 , 100);
到这,我们先记住有这么一茬,一张嘴表不了两家话,紧接着,就得引出下面这个重要的回调:AnimatorUpdateListener 。
AnimatorUpdateListener :实现这个接口的实例,在每个动画帧都会收到回调,在回调中,你可以得到ValueAnimator在当前帧的值。这样就能在每一帧的时候做出对应的操作,得到一个我们想要的动画效果。
示例代码:
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("animator" , animation.getAnimatedValue()+ "");
}
});
上面对AnimatorUpdateListener的说明中提到了:你可以得到ValueAnimator在当前帧的值,那么这个值是谁呢?回到刚才提到的构造函数。在构造函数中,就可以决定在这里你接收到的是一个什么值。而空的构造方法,也可以在后续的操作中,通过
PropertyValuesHolder holder = PropertyValuesHolder.ofInt("" , 0 , 1);
animator.setValues(holder);
设置他的值。第一种和第二种函数,分别会在这里返回float和int类型的值,而第三种则是你实现TypeEvaluator接口时重写evaluate()方法所返回的值。
这位看官要问了,PropertyValuesHolder又是个什么鬼,从字面上来讲呢,他是属性值的Holder,也就是ValueAnimator在运行时所需要的值。
其实呢,我们看下源码:
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
// 调用了setIntValues();
anim.setIntValues(values);
return anim;
}
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
// 在这里,同样是调用了setValues(PropertyValuesHolder)方法
setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
可以看到,你传进去的int数值也是转成了PropertyValuesHolder,再通过setValue();方法设置给了ValueAnimator。
总而言之,ValueAnimator在运行的过程中,是需要过渡点的(PropertyValuesHolder),你必须为他设置一些他要用来过度的点,否则回报空指针……
到了这里,我们就能做出一些高难度的动作了。
沿贝塞尔曲线移动的动画
示例代码:
ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 得到了与时间相关的从0~1的数,以及开始点和结束点。
// 就可以通过贝塞尔曲线的公式计算出不同时间XY所对应的点
// 让空间沿着这些点移动就是一条曲线移动的动画了
final float t = fraction;
float oneMinusT = 1.0f - t;
PointF point = new PointF();
PointF point0 = (PointF)startValue;
PointF point1 = new PointF();
point1.set(width, 0);
PointF point2 = new PointF();
point2.set(0, height);
PointF point3 = (PointF)endValue;
point.x = oneMinusT * oneMinusT * oneMinusT * (point0.x)
+ 3 * oneMinusT * oneMinusT * t * (point1.x)
+ 3 * oneMinusT * t * t * (point2.x)
+ t * t * t * (point3.x);
point.y = oneMinusT * oneMinusT * oneMinusT * (point0.y)
+ 3 * oneMinusT * oneMinusT * t * (point1.y)
+ 3 * oneMinusT * t * t * (point2.y)
+ t * t * t * (point3.y);
return point;
}
} , 0 , 100);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF point = (PointF) animation.getAnimatedValue();
// 接收到了在TypeEvaluator中计算出的PointF对象
view.setX(point.x);
view.setY(point.y);
}
});
animator.setDuration(2000);
animator.start();
使用XML定义属性动画
前面我们看到了Animation可以通过XML方便的定义出一个动画,同样Animator也可以做到。你需要做的就是:
在res下建立animator文件夹,然后建立res/animator/scalex.xml
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType" >
objectAnimator>
代码:
// 加载动画
Animator anim = AnimatorInflater.loadAnimator(mContext,R.animator.scalex);
anim.setTarget(mMv);
anim.start();
单个动画就这么定义完毕了。下面是多个动画:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together" >
<objectAnimator
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="0.5" >
objectAnimator>
<objectAnimator
android:duration="1000"
android:propertyName="scaleY"
android:valueFrom="1"
android:valueTo="0.5" >
objectAnimator>
set>
代码:
Animator anim = AnimatorInflater.loadAnimator(mContext, R.animator.scale);
view.setPivotX(0);
view.setPivotY(0);
//显示的调用invalidate
view.invalidate();
anim.setTarget(view);
anim.start();
属性动画就这些了,如果后期发现新的东西还会继续补充….
有些场景会需要多中类型的动画一起播放,或者按照顺序播放,怎么搞?就要用到组合/顺序动画了。
Android提供了一套非常丰富的API,让我们可以将多个动画按照指定的顺序来播放,这里需要借助AnimatorSet和AnimationSet.
AnimatorSet 和 AnimationSet 都是动画集合。这里简单介绍下他们的异同,了解这些后在设计动画实现时才能得心应手。
- AnimationSet 我们最常用的是调用其 addAnimation 将一个个不一样的动画组织到一起来,然后调用view 的 startAnimation 方法触发这些动画执行。功能较弱不能做到把集合中的动画按一定顺序进行组织然后在执行的定制。
AnimatorSet
这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
示例代码
private void runAnimatorSet(View view){
ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
// 这里需要注意的是,如果是多个动画,强烈推荐这么写,否则会错乱。
set.play(animator1).before(animator2);
set.play(animator3).with(animator3_);
set.play(animator3).after(animator2);
set.play(animator4).after(animator3);
set.play(animator5).after(animator4);
set.start();
}
AnimationSet
这个类就比较简单了,他继承于animation属于ViewAnimation的范畴,并且他只能将多个Animation动画放在一起同时执行。
示例代码
// 传入一个boolean值,他决定了你使用animationSet的插值器还是动画自身的
AnimationSet set = new AnimationSet(true);
set.setDuration(2000);
TranslateAnimation translate = new TranslateAnimation(0,100,0,100);
AlphaAnimation alpha = new AlphaAnimation(0 , 1);
set.addAnimation(translate);
set.addAnimation(alpha);
view.startAnimation(set);
在属性动画一章中,我们提到最多的就是动画的时间概念,插值器就是对时间进行修改的东西,比如说你闲的蛋疼,走斑马线的时候只踩白块,这是线性的运动轨迹,而这时候突然在其中某些白块中间又插入了几个距离不相等的白块,你的步伐就不一样了,移动速度也收到了影响,这就是插值器所做的勾当,下面Android定义好的一些常用插值器:
AccelerateDecelerateInterpolator 慢 - 快- 慢
AccelerateInterpolator 慢 - 快
DecelerateInterpolator 快 - 慢
AnticipateInterpolator 开始的时候向后然后向前甩
AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
BounceInterpolator 动画结束的时候弹起
CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
LinearInterpolator 以常量速率改变
OvershootInterpolator 向前甩一定值后再回到原来位置
使用起来也如同弯腰捡钱搬愉悦和方便:
TranslateAnimation translate = new TranslateAnimation(0,100,0,100);
AccelerateInterpolator interpolator = new AccelerateInterpolator();
translate.setInterpolator(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);
}
那么你已经知道该怎么做了:
/**
* 我真的用我的名字为他命名了 。屌不屌?怕不怕?
* Created by R on 2016/3/12.
*/
public class LongFaceInterpolator implements Interpolator{
@Override
public float getInterpolation(float input) {
return 0;
}
}
参数input:
input参数是一个float类型,它取值范围是0到1,表示当前动画的进度,取0时表示动画刚开始,取1时表示动画结束,取0.5时表示动画中间的位置,其它类推。
返回值:
表示当前实际想要显示的进度。取值可以超过1也可以小于0,超过1表示已经超过目标值,小于0表示小于开始位置。
到了这里,一个自定义的插值器就完成了,你需要做的就是像做一道填空题一样将getInterpolation()方法中补全,并在动画拿到实际的时间进度前对他进行修改,然后骗着动画按照你所给的时间运行。The End~
在Android API 12以后,View增加了一个新的方法animate(),他用来返回一个ViewPropertyAnimation()的实例,并且可以对他进行一系列的链式操作,就像这样:
示例代码
// API 12
view.animate()
.alpha(1)
.translationX(100)
.y(30)
.setDuration(300)
// API 16
.withStartAction(new Runnable() {
@Override
public void run() {
// 在动画开始之前,做你想做的,他是在主线程中的
}
})
// API 16
.withEndAction(new Runnable() {
@Override
public void run() {
// 在动画结束时,做你想做的,他是在主线程中的
}
})
.start();
爽到没朋友有木有?这里只列出了几个方法,他还有很多自己去探索吧~
// 这里传递的第一个值和第二个值都是相对于View原点的,就是X从原点增加100
PropertyValuesHolder translationX =
PropertyValuesHolder.ofFloat("translationX", 0 ,100);
// 这句的意思就是,X从原点+100的地方 移动到 原点+500的地方,实际是移动的400的距离
PropertyValuesHolder translationX =
PropertyValuesHolder.ofFloat("translationX", 100 ,500);
Android属性动画完全解析
Android属性动画详细解析
Android动画解析 (一) ~ (十一)
贝塞尔曲线公式
Android沿赛贝尔曲线移动的动画效果
Android Canvas绘制
通过硬件提高Android动画性能
如何学习Android Animation
探索Android中有意义的动画
我自己…..啊哈哈~O(∩_∩)O哈哈~