其实动画这个东西我已经了解过很长一段时间了,但是一直没系统的整理过。关于android中的各种动画虽然都会用,但总怕自己会慢慢遗忘。这回看了几篇动画分析的文章,自己也学到了一些东西,在此就梳理一下。
参考博文如下,感谢大神们的分享:
http://www.open-open.com/lib/view/open1329994048671.html
http://www.tuicool.com/articles/yeM3my
http://blog.csdn.net/singwhatiwanna/article/details/17841165
http://blog.csdn.net/lmj623565791/article/details/38067475
http://blog.csdn.net/lmj623565791/article/details/38092093
注意:所有view的动画就是被限制在它的父控件中的,即使你做了view的移动,它也不可能显示在父控件的外边。也就是说父控件是一个舞台,演员可以在舞台上到处走动,但如果超过了舞台,那么观众就看不到了。
一、View Animation(Tween Animation)
View Animation(Tween Animation):也可称为补间动画(Tween Animation),给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。
View animation只能应用于View对象,而且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。
对于View animation,它只是改变了View对象绘制的位置,注意这是“绘制”,而不是实际的位置。比如你让一个button变成它的两倍大小,但是它能接受你点击的区域还是原来的button区域,没有做任何改变。
View Animation支持设定多种动画样式,也可以设定这些动画的执行顺序,支持通过xml和代码两种方式来设置动画效果。
动画举例
【 By XML 】
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true"
android:startOffset="50"> <alpha
android:duration="200"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>
Animation aimation = AnimationUtils.loadAnimation(this, R.anim.anim);
final ImageView imageView = (ImageView) findViewById(R.id.imageView_id);
imageView.startAnimation(aimation);
可以对动画添加监听器
aimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) { } @Override
public void onAnimationEnd(Animation animation) {
animation = null; } @Override
public void onAnimationRepeat(Animation animation) { }
});
如果你是用AnimationSet设置动画的话,animationSet也是继承自Animation,所以也有setAnimationListener的方法
详细代码可以参考:http://www.cnblogs.com/tianzhijiexian/p/3983616.html
【 By JAVA 】
通过java代码来做的,可以参考这篇文章。其实也就是各种类继承Animation,然后有自己独特的方法,也可以通过AnimationSet进行各种设置。
http://www.cnblogs.com/tianzhijiexian/p/3981241.html
二、Drawable Animation(Frame Animation)
Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果。在XML中的定义方式如下:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
必须以<animation-list>为根元素,以<item>表示要轮换显示的图片,duration属性表示各项显示的时间。XML文件要放在/res/drawable/目录下。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView = (ImageView) findViewById(R.id.imageView1);
imageView.setBackgroundResource(R.drawable.drawable_anim);
AnimationDrawable anim = (AnimationDrawable) imageView.getBackground();
} public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
anim.stop();
anim.start();
return true;
}
return super.onTouchEvent(event);
}
我在实验中遇到两点问题:
- 要在代码中调用Imageview的setBackgroundResource方法,如果直接在XML布局文件中设置其src属性当触发动画时会FC。
- 在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次。
- 最后一点是SDK中提到的,不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()。
三、Property Animation
属性动画,这个是在Android 3.0中才引进的,它更改的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象(Object)。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。
在Property Animation中,可以对动画应用以下属性:
- Duration:动画的持续时间,单位ms
- TimeInterpolation:属性值的计算方式,如先快后慢,是一个接口。用来设置插值器
- TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出“当前”时间的属性值,这个值可以在AnimationUpdate中得到
- Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
- Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
- Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响
四、ValueAnimator
ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。它其实就是一个计算器,并不能实际执行动画效果。它可以计算出动画要执行的时间,每隔几毫秒刷新一次等等,但具体如何执行动画,它是不管的。你需要在ValueAnimator的ValueAnimator.onUpdateListener监听器中进行设置。
这里给ValueAnimator对象设置了一个值,因为是一个值,所以会默认为是最终的结果值,动画默认从当前的值到你设定的这个值。如果你设定两个值,那么它就意味着是从第一个值到另一个值进行动画。
// set one value,it is final value.
// If you set two values in it.It means animation will start from first value to Second value.
ValueAnimator animator = ValueAnimator.ofFloat(1f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) { }
});
animator.start();
这个AnimationUpdateListener将会在动画执行过程中触发。如果你没在这里做任何处理,那么即使是执行了(start())也不会有任何动画的。
实际运用——*落体 & 抛物线
如果我希望小球抛物线运动【实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s 】,分析一下,貌似只和时间有关系,但是根据时间的变化,横向和纵向的移动速率是不同的,我们该咋实现呢?此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要返回给对象两个值,x当前位置,y当前位置。
/**
* 抛物线
* @param view
*/
public void paowuxian(View view)
{ ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(3000);
valueAnimator.setObjectValues(new PointF(0, 0));
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setEvaluator(new TypeEvaluator<PointF>()
{
// fraction = t / duration
@Override
public PointF evaluate(float fraction, PointF startValue,
PointF endValue)
{
Log.e(TAG, fraction * 3 + "");
// x方向200px/s ,则y方向0.5 * 10 * t
PointF point = new PointF();
point.x = 200 * fraction * 3;
point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
return point;
}
}); valueAnimator.start();
valueAnimator.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
PointF point = (PointF) animation.getAnimatedValue();
mBlueBall.setX(point.x);
mBlueBall.setY(point.y); }
});
}
可以看到,因为ofInt,ofFloat等无法使用,我们自定义了一个TypeValue,每次根据当前时间返回一个PointF对象,(PointF和Point的区别就是x,y的单位一个是float,一个是int;RectF,Rect也是)PointF中包含了x、y的当前位置,然后我们在监听器中获取,动态设置属性。
五、ObjectAnimator
实际应用中一般都会用ObjectAnimator来产生某一对象的动画,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:
- 对象应该有一个setter函数:set<PropertyName>(驼峰命名法)
- 如上面的例子中,像ofFloat之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的getter方法:get<PropertyName>
- 如果有getter方法,其应返回值类型应与相应的setter方法的参数类型一致。
如果上述条件不满足,则不能用ObjectAnimator,应用ValueAnimator代替。
下面是将imageview进行透明度渐变的例子。
ObjectAnimator oa=ObjectAnimator.ofFloat(imageview, "alpha", 0f, 1f);
oa.setDuration(3000);
oa.start();
根据应用动画的对象或属性的不同,可能需要在onAnimationUpdate函数中调用invalidate()函数刷新视图。
下面分析下属性动画的原理:
属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,你对object的属性xxx做动画,如果想让动画生效,要同时满足两个条件:
- object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
- object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)
以上条件缺一不可
那么如果我们的object没办法满足这个条件呢?比如button的setWidth方法仅仅是设置它的最小宽度(minWidth),对于实际宽度不会产生任何影响。如果让它实际改变就需要用
btn.getLayoutParams().width = xxx;
来设置,但这种通过方法来获得public值的方法又不适合ObjecAnimator的应用场景,改怎么办呢?
针对上述问题,Google告诉我们有3中解决方法:
1. 给你的对象加上get和set方法,如果你有权限的话
对于View我们没权限给其添加各种属性,所以这种方法仅仅适合于自己定义的Object对象
2. 用一个类来包装原始对象,间接为其提供get和set方法
这个方法很好,你可以写一个类继承那个Object对象,在子类中添加各种set、get方法,更好的办法是写一个包装类,传入一个object对象,然后给其添加各种方法。
private void performAnimate() {
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
} @Override
public void onClick(View v) {
if (v == mButton) {
performAnimate();
}
} private static class ViewWrapper {
private View mTarget; public ViewWrapper(View target) {
mTarget = target;
} public int getWidth() {
return mTarget.getLayoutParams().width;
} public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
上面类中ViewWrapper这个内部类就是一个包装类,它的构造函数传入一个View对象,然后提供了set和get方法,这样我们就可以对它进行动画的操作了
3. 采用ValueAnimator,监听动画过程,自己实现属性的改变
这个方法就有些别扭了,但扩展性是最强的。ValueAnimator我们在上面已经介绍过了,下面是在动画执行过程中能的得到的东西。
ValueAnimator animator = ValueAnimator.ofFloat(1, 1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer currentValue = (Integer)animation.getAnimatedValue(); // current Value(0~100) float fraction = valueAnimator.getAnimatedFraction(); // progress of animation
}
});
getAnimatedFraction() 这个可以得到当前动画的进度,得到的是浮点型数据很有用。api12中加入的,感觉比getAnimationValue有用
getAnimatedValue(String propertyName) 得到某个特定属性当前的值,这个可用性就很高了。如果有特殊要求可以用它
其余的属性都在下面了,看名字就知道是什么意思啦~
实际运用
这个例子是将一个view进行移动和拉伸的动画。涉及了多个属性,当你要让view选装或者是移动的时候,请务必设定PivotX,PivotY来指定坐标,如果不设定的话,移动和选择默认的中心点都是Object的中心
public void startViewSimpleAnim(final View fromView,Rect finalBounds,int
startOffsetY,int finalOffsetY, float startAlpha, float finalAlpha) {
Rect startBounds = new Rect();
startBounds.set(Position.getGlobalVisibleRect(fromView));
//设置偏移量
startBounds.offset(0, -startOffsetY);
finalBounds.offset(0, -finalOffsetY);
//设定拉伸或者旋转动画的中心位置,这里是相对于自身左上角
fromView.setPivotX(0f);
fromView.setPivotY(0f);
//计算拉伸比例
float scaleX = (float)finalBounds.width() / startBounds.width();
float scaleY = (float)finalBounds.height() / startBounds.height(); AnimatorSet set = new AnimatorSet();
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(fromView, "alpha", startAlpha, finalAlpha);
ObjectAnimator xAnim = ObjectAnimator.ofFloat(fromView, "x", startBounds.left, finalBounds.left);
ObjectAnimator yAnim = ObjectAnimator.ofFloat(fromView, "y", startBounds.top, finalBounds.top);
ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_X, 1f, scaleX);
ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_Y,1f, scaleY); set.play(alphaAnim).with(xAnim).with(yAnim).with(scaleXAnim).with(scaleYAnim); set.setStartDelay(mStartDelay);
set.setDuration(mAnimTime);
set.setInterpolator(mInterpolator);
set.addListener(new AnimListener(fromView,null)); set.start();
}
如果你操作对象的该属性方法里面,比如上例的setRotationX如果内部没有调用view的重绘,则你需要自己按照下面方式手动调用。
anim.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
// view.postInvalidate();
// view.invalidate();
}
});
用xml文件来创建属性动画
大家肯定都清楚,View Animator 、Drawable Animator都可以在anim文件夹下创建动画,然后在程序中使用,甚至在Theme中设置为属性值。当然了,属性动画其实也可以在文件中声明。
首先在res下建立animator文件夹,然后建立res/animator/scalex.xml
<?xml version="1.0" encoding="utf-8"?>
<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>
然后通过动画工具类就可以加载xml中的动画文件了
AnimatorInflater.loadAnimator(this, R.anim.anim);
public void scaleX(View view)
{
// 加载动画
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
anim.setTarget(mMv);
anim.start();
}
纵向与横向同时缩放
<?xml version="1.0" encoding="utf-8"?>
<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>
使用set标签,有一个orderring属性设置为together,【还有另一个值:sequentially(表示一个接一个执行)】
六、通过AnimatorSet来控制多个动画
AnimatorSet和AnimationSet类似,可以操作多个动画属性。AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。
例子01:
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2);
bouncer.play(anim2).with(anim3);
bouncer.play(anim2).with(anim4)
bouncer.play(anim5).after(amin2);
animatorSet.start();
- 播放anim1;
- 同时播放anim2,anim3,anim4;
- 播放anim5。
例子02:
animSet.playTogether(anim1, anim2);
animSet.start();
使用playTogether两个动画同时执行,当然还有playSequentially依次执行
例子03:
/**
* anim1,anim2,anim3同时执行
* anim4接着执行
*/
AnimatorSet animSet = new AnimatorSet();
animSet.play(anim1).with(anim2);
animSet.play(anim2).with(anim3);
animSet.play(anim4).after(anim3);
animSet.setDuration(1000);
animSet.start();
如果我们有一堆动画,如何使用代码控制顺序,比如1,2同时;3在2后面;4在1之前
注意:animSet.play().with();也是支持链式编程的,但是不要想太多。
比如:animSet.play(anim1).with(anim2).before(anim3).before(anim5);
这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行
七、ViewPropertyAnimator
如果需要对一个View的多个属性进行动画可以用ViewPropertyAnimator类,该类对多属性动画进行了优化,会合并一些invalidate()来减少刷新视图,该类在3.1中引入。
例子01:
以下两段代码实现同样的效果
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
myView.animate().x(50f).y(100f);
例子02:
在SDK12的时候,给View添加了animate方法,更加方便的实现动画效果。
btn.animate().x(200).y(200).alpha(0f); 就可以实现动画效果了~
例子03:
简单的使用mBlueBall.animate().alpha(0).y(mScreenHeight / 2).setDuration(1000).start()就能实现动画~~不过需要SDK11,此后在SDK12,SDK16又分别添加了withStartAction和withEndAction用于在动画前,和动画后执行一些操作。当然也可以.setListener(listener)等操作。
package com.example.zhy_property_animation; import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.ImageView; public class ViewAnimateActivity extends Activity
{
protected static final String TAG = "ViewAnimateActivity"; private ImageView mBlueBall;
private float mScreenHeight; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.view_animator); DisplayMetrics outMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
mScreenHeight = outMetrics.heightPixels;
mBlueBall = (ImageView) findViewById(R.id.id_ball); } public void viewAnim(View view)
{
// need API12
mBlueBall.animate()//
.alpha(0)//
.y(mScreenHeight / 2).setDuration(1000)
// need API 12
.withStartAction(new Runnable()
{
@Override
public void run()
{
Log.e(TAG, "START");
}
// need API 16
}).withEndAction(new Runnable()
{ @Override
public void run()
{
Log.e(TAG, "END");
runOnUiThread(new Runnable()
{
@Override
public void run()
{
mBlueBall.setY(0);
mBlueBall.setAlpha(1.0f);
}
});
}
}).start();
} }
八、TypeEvalutors<T>
根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值,Android提供了以下几个evalutor:
- IntEvaluator:属性的值类型为int;
- FloatEvaluator:属性的值类型为float;
- ArgbEvaluator:属性的值类型为十六进制颜色值;
- TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。
自定义TypeEvalutor很简单,只需要实现一个方法,如FloatEvalutor的定义:
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);
}
}
根据动画执行的时间跟应用的Interplator,会计算出一个0~1之间的因子,即evalute函数中的fraction参数,通过上述FloatEvaluator应该很好看出其意思。
九、当Layout改变时应用动画
ViewGroup中的子元素可以通过setVisibility使其Visible、Invisible或Gone,当有子元素可见性改变时,可以向其应用动画。
通过LayoutTransition类的常量(第一个参数)可以区分动画的类型,第二个参数为Animator。
- APPEARING 当一个元素变为Visible时对其应用的动画
- CHANGE_APPEARING 当一个元素变为Visible时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画
- DISAPPEARING 当一个元素变为InVisible时对其应用的动画
- CHANGE_DISAPPEARING 当一个元素变为Gone时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画 disappearing from the container.
mTransition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);
更多的解释
- 过渡的类型一共有四种:
- LayoutTransition.APPEARING 当一个View在ViewGroup中出现时,对此View设置的动画
- LayoutTransition.CHANGE_APPEARING 当一个View在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画
- LayoutTransition.DISAPPEARING 当一个View在ViewGroup中消失时,对此View设置的动画
- LayoutTransition.CHANGE_DISAPPEARING 当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画
- LayoutTransition.CHANGE 不是由于View出现或消失造成对其他View位置造成影响,然后对其他View设置的动画。
- 注意动画到底设置在谁身上,此View还是其他View。
详细的例子,请参考:http://blog.csdn.net/lmj623565791/article/details/38092093
顺便说下:Android 3.0已经原生支持了Animating Layout,当Layout变化的时候,系统会根据Layout变化前后,自动显示动画。
只要给layout添加:android:animateLayoutChanges="true"即可,而且动画还可以通过setLayoutTransition()自己定义。
参考博文如下:
http://www.open-open.com/lib/view/open1329994048671.html
http://www.tuicool.com/articles/yeM3my
http://blog.csdn.net/singwhatiwanna/article/details/17841165