一、基本动画
1.1、帧动画
1.首先需要在res目录下创建drawable目录,再新建xml文件并指定播放形式和资源引用。
2.在布局中创建ImageView,然后写上如下代码
1.2、补间动画
2.缩放动画
5.动画集
动画集是将上述动画进行合并到一起,具体请参看代码:
5.动画的监听和回调
对于动画事件,Android也提供了对应的监听回调,要添加相应的监听方法,代码如下所示:
animation.setAnimationListener(new AnimationListener() { /**动画开始时回调*/ @Override public void onAnimationStart(Animation animation) { } /**动画重播时回调*/ @Override public void onAnimationRepeat(Animation animation) { } /**动画结束时回调*/ @Override public void onAnimationEnd(Animation animation) { } });
二、属性动画
由于基本的动画存在局限性,它只改变显示,并不能响应事件,所以在Android 3.0之后,加入了属性动画。
1.1、ObjectAnimation
创建ObjectAnimation只需要通过静态工厂类直接返回一个ObjectAnimation对象。参数必须包括一个对象和对象的属性名字,
属性必须具备get和set方法。内部会通过反射机制来调用set方法修改对象的属性值。
同样,我们也可以通过setInterpolator设置相应插值器。
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", 300); animator.setDuration(1000); animator.start();
第一个参数是需要操纵的View,第二个参数是要操纵的属性,第三个参数则是一个可变数组参数,需要传递进去该属性变化的取值过程。
在使用ObjectAnimation时,要操纵的属性必须具备get和set方法,不然则会无效。下面是常见的属性值:
如果一个属性没有get和set方法,我们则有两种方案来解决该问题:
- 自定义一个属性类或包装类来间接地给这个属性增加get和set方法。
- 通过ValueAnimation来实现。
public class WrapperView { private View mTarget; public WrapperView(View target){ mTarget = target; } public int getWidth(){ return mTarget.getLayoutParams().width; } public void setWidth(int width){ mTarget.getLayoutParams().width = width; mTarget.requestLayout(); } }
通过以上代码就给属性包装一层,并给它提供get、set方法。使用时只需要操纵包装类就可以间接调用get、set方法了。
WrapperView wrapper = new WrapperView(mButton); ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
1.2、ObjectAnimator的高级用法
ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个color属性,
并提供它的get和set方法。这里我们可以将color属性设置为字符串类型,使用#RRGGBB这种格式来表示颜色值,代码如下所示:
在使用ObjectAnimator之前我们还要完成一个非常重要的工作,就是编写一个用于告知系统如何进行颜色过度的TypeEvaluator。创建ColorEvaluator并实现TypeEvaluator接口,代码如下所示:
复杂工作完成后,我们想实现从蓝色到红色的动画过渡,历时5秒,就可以这样写:
接下来我们需要将上面一段代码移到MyAnimView类当中,让它和刚才的Point移动动画可以结合到一起播放
1.3、PropertyValuesHolder
类似基本动画中的AnimationSet,在属性动画中,如果针对同一个对象的多个属性,要同时作用多种动画,可以使用PropertyValuesHolder来实现。
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", 300f); PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f); PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f); ObjectAnimator.ofPropertyValuesHolder(mButton, pvh1, pvh2, pvh3).setDuration(1000).start();
1.4、ValueAnimator
ValueAnimator在属性动画中是非常重要的,它是整个属性动画的核心所在,ObjectAnimator继承自ValueAnimator。
ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画实现过程。
通常情况下,在ValueAnimator的AnimatorUpdateListener中监听数值的变换,从而完成动画的变换。
ValueAnimator animator = ValueAnimator.ofFloat(0,100); animator.setTarget(mButton); animator.setDuration(1000).start(); animator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (Float) animation.getAnimatedValue(); //TODO use the value } });
1.5、动画事件的监听
一个完整的动画具有Start、Repeat、End、Cancel四个过程,通过Android提供了接口,可以方便地监听到这四个事件:
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "alpha", 0.5f); animator.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } }); animator.start();
当然,大部分时候我们只关心onAnimationEnd事件,所以Android也提供了AnimatorListenerAdapter来让我们选择必要的事件进行监听
animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { } });
1.6、AnimatorSet
对于一个属性同时作用多个属性的动画效果,除了PropertyValuesHolder外,还可以使用AnimatorSet更精准的控制顺序。
ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationX", 300f); ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f, 1f); ObjectAnimator animator3 = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f, 1f); AnimatorSet set = new AnimatorSet(); set.setDuration(1000); set.playTogether(animator1, animator2, animator3);
在属性动画中AnimatorSet正是通过playTogether()、playSwquentinally()、animSet.play.with()、befor()、after、这些方法来控制
多个动画的协同工作并对动画播放顺序进行精准控制。
1.7、XML属性动画
属性动画同视图动画一样,也可以定义在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定义的属性动画也非常的简单:
public void scaleX(View view){ Animator anim = AnimatorInflater.loadAnimator(this, R.anim.objectscalex); anim.setTarget(view); anim.start(); }
1.8、View的animate方法
在Android4.1.2(API 16)时,Google给View增加了animate方法来直接驱动属性动画,它是属性动画一种简写形式:
view.animate().alpha(0).y(300).setDuration(3000) .withStartAction(new Runnable() { @Override public void run() { } }).withEndAction(new Runnable() { @Override public void run() { runOnUiThread(new Runnable() { public void run() { } }); } }).start();
三、布局动画
所谓布局动画是指作用在ViewGroup上,给VewGroup增加View时添加动画过渡效果。
最简单的布局动画是在ViewGroup的XML中,使用以下代码来打开布局动画:
android:animateLayoutChanges="true"
设置该属性后,当ViewGroup添加View时,子View会呈现逐渐显示的过渡效果,这个效果是Android默认的,且无法使用自定义的动画进行替换。
如果想自定义子View的过渡效果,可以使用LayoutAnimationController类来完成相关操作。
LinearLayout ll = (LinearLayout) findViewById(R.id.ll_root); // 设置过渡动画 ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1); sa.setDuration(2000); // 设置布局动画的显示属性 LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F); lac.setOrder(LayoutAnimationController.ORDER_NORMAL); // 为ViewGroup设置布局动画 ll.setLayoutAnimation(lac);
LayoutAnimationController的第一个参数是需要作用的动画,dirge参数是每个子View显示delay时间。
当delay时间不为0时,可以设置子View显示的顺序:
- LayoutAnimationController.ORDER_NORMAL -- 顺序
- LayoutAnimationController.ORDER_RANDOM -- 随机
- LayoutAnimationController.ORDER_REVERSE -- 反序
四、Interpolators(插值器)
插值器是动画中一个非常重要的概念,通过插值器(Interpolators),可以自定义动画变换速率,类似物理中的加速度。
其作用主要是控制目标变量的变化值进行对应的变化。
1.1、Interpolator用法
补间器,它的主要作用是可以控制动画的变化速率,比如去实现一种非线性运动的动画效果。补间动画就支持Interpolator,属性动画新增TimeInterpolator接口。
下面参考TimeInterpolator实现类
使用属性动画时,系统默认的Interpolator其实就是一个先加速后减速的Interpolator,对应的实现类就是AccelerateDecelerateInterpolar。
我们也可以修改这一默认属性,将它替换成任意一个系统内置好的Interpolator。首先是垂直掉落的效果
OK,效果非常明显,说明我们已经成功替换掉了默认的Interpolator,AccelerateInterpolator确实是生效了。但是现在的动画效果看上去仍然是怪怪的,因为一个小球从很高的地方掉落到地面上
直接就静止了,这也是不符合物理规律的,小球撞击到地面之后应该要反弹起来,然后再次落下,接着再反弹起来,又再次落下,以此反复,最后静止。这个功能我们当然可以自己去写,只不
过比较复杂,所幸的是,Android系统中已经提供好了这样一种Interpolator,我们只需要简单地替换一下就可以完成上面的描述的效果,代码如下所示:
系统内置的Interpolator非常多,有时间可以将其他几种Interpolator实现查看效果。
1.2、Interpolator介绍
1.TimeInterpolator
首先看下TimeInterpolator的接口定义:
接口非常简单,只有一个getInterpolation()方法,它接受一个input参数,该参数会随着动画的运行而不断有规律的变化,且变化范围是0到1。
input值是决定fraction的值,因为input的值是由系统经过计算后传入到getInterpolation()方法中,然后可以自己实现该方法中的算法,根据input的值计算出一个返回值就是fraction。
因此,最简单的情况就是input值和fraction值是相同的,这种情况由于input值是匀速增加的,因而fraction的值也是匀速增加的,所以动画的运动情况也是匀速的。
2.LinearInterpolator
系统中内置的LinearInterpolator就是一种匀速运动的Interpolator,那么我们来看一下它的源码是怎么实现的
这里我们只看getInterpolation()方法,这个方法没有任何逻辑,就是把参数中传递的input值直接返回了,因此fraction的值就是等于input的值的,这就是匀速运动的Interpolator的实现方式。
3.AccelerateDecelerateInterpolator
系统默认情况下使用的是AccelerateDecelerateInterpolator,那么我们看下它的源码:
此时getInputerpolator逻辑明显复杂了,不再是直接将参数的input返回,二手进行较为复杂的数学运算。
1.3、自定义Interpolator
通过上上述分析,我们已经了解LinearInterpolator和AccelerateDecelerateInterpolator的实现机制,现在我们自定义Interpolator,由于属性动画默认的Interpolator是先减速后加速的过程,
我们可以修改它为先加速后减速的过程
之后我们将自定义的DecelerateAccelerateInterpolator在代码中进行替换:
五、自定义动画
创建自定义动画只需要继承Animation类,并实现applyTransformation方法即可,
通常情况下还需要覆盖父类的initalize方法以完成一些初始化操作。
public class MyAnimation extends Animation { /**完成一些初始化操作*/ @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); } }
applyTransformation类的interpolatedTime是插值器因子,由动画当前完成的百分比和当前时间所对应的插值所计算得来的,
取值范围是0 - 1.0。
第二个参数Transformation是矩阵的封装类,一般使用这个类获得当前的矩阵对象
Matrix matrix = t.getMatrix();
通过改变matrix对象,可以将动画效果实现出来,基本可以实现任意效果。
public class CustomAnimation extends Animation { private int mCenterWidth; private int mCenterHeight; private float mRotateY = 0.0f; private Camera mCamera = new Camera(); /**完成一些初始化操作*/ @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); // 设置默认时长 setDuration(2000); // 动画结束后保留状态 setFillAfter(true); // 设置默认插值器 setInterpolator(new BounceInterpolator()); mCenterWidth = width / 2; mCenterHeight = height / 2; } // 暴漏接口 -设置旋转角度 public void setRotateY(float rotateY){ mRotateY = rotateY; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { Matrix matrix = t.getMatrix(); mCamera.save(); // 使用Camera设置旋转的角度 mCamera.rotateY(mRotateY * interpolatedTime); // 将旋转变换作用到matrix上 mCamera.getMatrix(matrix); mCamera.restore(); // 通过pre方法设置矩阵作用前的偏移量来改变旋转中心 matrix.preTranslate(mCenterWidth, mCenterHeight); matrix.preTranslate(-mCenterWidth, -mCenterHeight); } }
调用时需要通过对外暴漏的方法来进行设置:
CustomAnimation customAnimation = new CustomAnimation(); customAnimation.setRotateY(30); view.startAnimation(customAnimation);
六、SVG矢量动画(5.X)
七、Android动画特效
1.1、灵动菜单
参看附件中的灵动菜单demo。
1.2、计时器动画
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView tView = (TextView) findViewById(R.id.tv_view); tView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 计时器动画 TimerAnim(tView); } }); } public void TimerAnim(final View view){ ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100); valueAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { TextView tView = (TextView) view; tView.setText("$" + animation.getAnimatedValue()); } }); valueAnimator.setDuration(3000); valueAnimator.start(); } }
当用户点击文本后,数字会不断的增加,以此完成过渡效果。
1.3、下拉展开动画
想实现点击一个View的时候,显示下面因此的一个View,实现该功能只需要将View的visibility属性由gone设置为visible即可,
但是这个过程是瞬间完成的,我们期望的是隐藏的View的高度不断发生变化,则可以通过ValueAnimator来模拟。
public class MainActivity extends Activity { private LinearLayout mHiddenView; private float mDensity; private int mHiddenViewMeasuredHeight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.drop); mHiddenView = (LinearLayout) findViewById(R.id.hidden_view); // 获取像素密度 mDensity = getResources().getDisplayMetrics().density; // 获取布局的高度 mHiddenViewMeasuredHeight = (int) (mDensity * 200 + 0.5); } public void llClick(View view) { if (mHiddenView.getVisibility() == View.GONE) { animateOpen(mHiddenView); } else { animateClose(mHiddenView); } } private void animateOpen(final View view) { view.setVisibility(View.VISIBLE); ValueAnimator animator = createDropAnimator(view, 0, mHiddenViewMeasuredHeight); animator.start(); } private void animateClose(final View view) { int origHeight = view.getHeight(); ValueAnimator animator = createDropAnimator(view, origHeight, 0); animator.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { view.setVisibility(View.GONE); } }); animator.start(); } private ValueAnimator createDropAnimator(final View view, int start, int end) { ValueAnimator animator = ValueAnimator.ofInt(start, end); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { int value = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.height = value; view.setLayoutParams(layoutParams); } }); return animator; } }
布局文件参看附件中的展开动画.rar
目前已经封装为一个非常方便的控件,参看附件中的任务模块
源码下载:
链接:http://pan.baidu.com/s/1jHMgxUm 密码:7023