【Android开发日记】之基础篇(二)——Android的动画效果
什么是动画,动画的本质是通过连续不断地显示若干图像来产生“动”起来的效果。比如说一个移动的动画,就是在一定的时间段内,以恰当的速率(起码要12帧/秒以上,才会让人产生动起来的错觉)每隔若干时间在屏幕上更新一次位置。游戏中的动画效果也是由此而来。同样还有其他属性变更所引起的动画效果,从数学的角度来看,包括:(1)平移(2)旋转(3)缩放(4)透明度。当然这些属性可以组合起来使用,来达到更绚丽的画面。但是不论什么样的组合方式,我们都可以统一用Matirx运算来实现,从技术实现的角度来讲,Matrix是动画的核心,2D不用说,3D更是依赖矩阵(Matrix)的运算。当然,今天只是讲讲怎么实现动画效果,不会涉及这些基础知识~~
一、Tween Animation(补间动画)
补间动画可以实现View组件的移动、放大、缩小以及渐变等效果。也是我们使用动画效果时最常用的动画了。
一共有四种动画,分别为
AlphaAnimation |
渐变透明度动画效果 |
ScaleAnimation |
渐变尺寸伸缩动画效果 |
TranslateAnimation |
画面转换位置移动动画效果 |
RotateAnimation |
画面转移旋转动画效果 |
而设置动画也有两种方式,一种是在代码中设置,另一种就是在xml文件中配置。
- 设置alpha的动画
public Animation AlphaAnimation(){ Animation myAnimation_Alpha; //透明度从0到1 myAnimation_Alpha = new AlphaAnimation(0,1); //设置时间持续时间为 2000毫秒 myAnimation_Alpha.setDuration(2000); return myAnimation_Alpha; }
很简单,新建一个AlphaAnimation的类,里面放着要变化的起始参数和结束参数,然后程序就会自动演化出过场的动画。最后在要动画的view上设置
myAnimation = AlphaAnimation(); av.startAnimation(myAnimation);//av是一个imageview
- 设置rotate的动画
public Animation RotateAnimation(){ Animation myAnimation_Rotate; //第一个参数fromDegrees为动画起始时的旋转角度 //第二个参数toDegrees为动画旋转到的角度 //第三个参数pivotXType为动画在X轴相对于物件位置类型 //第四个参数pivotXValue为动画相对于物件的X坐标的开始位置 //第五个参数pivotXType为动画在Y轴相对于物件位置类型 //第六个参数pivotYValue为动画相对于物件的Y坐标的开始位置 myAnimation_Rotate = new RotateAnimation(0.0f, +350.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f); //动画插入器,可以自己定义动画的速度 //加速-减速 动画插入器 myAnimation_Rotate.setInterpolator(this,android.R.anim.accelerate_decelerate_interpolator); myAnimation_Rotate.setDuration(2000); return myAnimation_Rotate; }
里面有一个动画插入器的概念,这是控制动画过程速度的一个方法,比如上面代码中的accelerate_decelerate_interpolator就是要求动画先加速运行,在减速运行
- 设置Translate的动画
public Animation TranslateAnimation(){ //TranslateAnimation(float fromXDelta, float toXDelta, //float fromYDelta, float toYDelta) //第一个参数fromXDelta为动画起始时 X坐标上的移动位置 //第二个参数toXDelta为动画结束时 X坐标上的移动位置 //第三个参数fromYDelta为动画起始时Y坐标上的移动位置 //第四个参数toYDelta为动画结束时Y坐标上的移动位置 Animation myAnimation_Translate; myAnimation_Translate=new TranslateAnimation(0.0f, -80.0f, 0.0f, 300.0f); myAnimation_Translate.setDuration(2000); myAnimation_Translate.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { av.setTranslationX(av.getTranslationX()-80f); av.setTranslationY(av.getTranslationY()+300f); } @Override public void onAnimationRepeat(Animation animation) { } }); return myAnimation_Translate; }
这里提到了一个监听器的概念,如上面的方法所示,可以对动画的开始时刻,结束时刻以及重复时刻进行监听,然后调用自己写入的方法。 - 设置Scale动画
public Animation ScaleAnimation(){ Animation myAnimation_Scale; //第一个参数fromX为动画起始时 X坐标上的伸缩尺寸 //第二个参数toX为动画结束时 X坐标上的伸缩尺寸 //第三个参数fromY为动画起始时Y坐标上的伸缩尺寸 //第四个参数toY为动画结束时Y坐标上的伸缩尺寸 /*说明: 以上四种属性值 0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大 */ //第五个参数pivotXType为动画在X轴相对于物件位置类型 //第六个参数pivotXValue为动画相对于物件的X坐标的开始位置 //第七个参数pivotXType为动画在Y轴相对于物件位置类型 //第八个参数pivotYValue为动画相对于物件的Y坐标的开始位置 /*myAnimation_Scale = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f); myAnimation_Scale.setDuration(2000);*/ //使用xml读取动画 myAnimation_Scale = AnimationUtils.loadAnimation(this,R.anim.scale); return myAnimation_Scale; }
这里我们使用读取anim文件夹下的xml文件来设置动画(若res文件夹下没有anim文件夹要自己创建)
xml内部内容如下:<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:interpolator= "@android:anim/accelerate_decelerate_interpolator" android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50%" android:pivotY="50%" android:fillAfter="false" android:duration="700" /> </set> <!-- 尺寸伸缩动画效果 scale 属性:interpolator 指定一个动画的插入器 在我试验过程中,使用android.res.anim中的资源时候发现 有三种动画插入器: accelerate_decelerate_interpolator 加速-减速 动画插入器 accelerate_interpolator 加速-动画插入器 decelerate_interpolator 减速- 动画插入器 其他的属于特定的动画效果 浮点型值: fromXScale 属性为动画起始时 X坐标上的伸缩尺寸 toXScale 属性为动画结束时 X坐标上的伸缩尺寸 fromYScale 属性为动画起始时Y坐标上的伸缩尺寸 toYScale 属性为动画结束时Y坐标上的伸缩尺寸 说明: 以上四种属性值 0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大 pivotX 属性为动画相对于物件的X坐标的开始位置 pivotY 属性为动画相对于物件的Y坐标的开始位置 说明: 以上两个属性值 从0%-100%中取值 50%为物件的X或Y方向坐标上的中点位置 长整型值: duration 属性为动画持续时间 说明: 时间以毫秒为单位 布尔型值: fillAfter 属性 当设置为true ,该动画转化在动画结束后被应用 -->
scale的参数可能有点多,其实也就是比Translate的动画多了设置中心点的参数,其他跟Translate类似,只不过scale是对x,y进行缩放而不是位移
- AnimationsSet,动画集合
private AnimationSet mySet = new AnimationSet(true); public void AllAnimation(){ mySet.addAnimation(AlphaAnimation()); mySet.addAnimation(RotateAnimation()); mySet.addAnimation(TranslateAnimation()); mySet.addAnimation(ScaleAnimation()); mySet.setDuration(3000); av.startAnimation(mySet); }
动画集合可以将多个动画放到一个集合中,然后一起进行播放。(AnimationSet也是继承自Animation类,所以其它的属性跟单个动画差不多)
- 完整代码
public class MyActivity extends Activity implements View.OnClickListener { private ImageView av; private Button alpha,rotate,translate,scale; private AnimationSet mySet; private Button allAnimation,back; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); mySet = new AnimationSet(true); av = (ImageView)findViewById(R.id.imageView2); alpha = (Button)findViewById(R.id.alpha); alpha.setOnClickListener(this); rotate = (Button)findViewById(R.id.rotate); rotate.setOnClickListener(this); translate = (Button)findViewById(R.id.translate); translate.setOnClickListener(this); scale = (Button)findViewById(R.id.scale); scale.setOnClickListener(this); allAnimation = (Button)findViewById(R.id.all); allAnimation.setOnClickListener(this); back = (Button)findViewById(R.id.back); back.setOnClickListener(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.my, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @Override public void onClick(View v) { Animation myAnimation = null; switch(v.getId()){ case R.id.alpha: myAnimation = AlphaAnimation(); av.startAnimation(myAnimation);//av是一个imageview break; case R.id.rotate: myAnimation = RotateAnimation(); av.startAnimation(myAnimation); break; case R.id.translate: myAnimation = TranslateAnimation(); av.startAnimation(myAnimation); break; case R.id.scale: myAnimation = ScaleAnimation(); av.startAnimation(myAnimation); break; case R.id.all: AllAnimation(); break; case R.id.back: this.finish(); break; } } public Animation AlphaAnimation(){ Animation myAnimation_Alpha; //透明度从0到1 myAnimation_Alpha = new AlphaAnimation(0,1); //设置时间持续时间为 2000毫秒 myAnimation_Alpha.setDuration(2000); return myAnimation_Alpha; } public Animation RotateAnimation(){ Animation myAnimation_Rotate; //第一个参数fromDegrees为动画起始时的旋转角度 //第二个参数toDegrees为动画旋转到的角度 //第三个参数pivotXType为动画在X轴相对于物件位置类型 //第四个参数pivotXValue为动画相对于物件的X坐标的开始位置 //第五个参数pivotXType为动画在Y轴相对于物件位置类型 //第六个参数pivotYValue为动画相对于物件的Y坐标的开始位置 myAnimation_Rotate = new RotateAnimation(0.0f, +350.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f); //动画插入器,可以自己定义动画的速度 //加速-减速 动画插入器 myAnimation_Rotate.setInterpolator(this,android.R.anim.accelerate_decelerate_interpolator); myAnimation_Rotate.setDuration(2000); return myAnimation_Rotate; } public Animation TranslateAnimation(){ //TranslateAnimation(float fromXDelta, float toXDelta, //float fromYDelta, float toYDelta) //第一个参数fromXDelta为动画起始时 X坐标上的移动位置 //第二个参数toXDelta为动画结束时 X坐标上的移动位置 //第三个参数fromYDelta为动画起始时Y坐标上的移动位置 //第四个参数toYDelta为动画结束时Y坐标上的移动位置 Animation myAnimation_Translate; myAnimation_Translate=new TranslateAnimation(0.0f, -80.0f, 0.0f, 300.0f); myAnimation_Translate.setDuration(2000); myAnimation_Translate.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { av.setTranslationX(av.getTranslationX()-80f); av.setTranslationY(av.getTranslationY()+300f); } @Override public void onAnimationRepeat(Animation animation) { } }); return myAnimation_Translate; } public Animation ScaleAnimation(){ Animation myAnimation_Scale; //第一个参数fromX为动画起始时 X坐标上的伸缩尺寸 //第二个参数toX为动画结束时 X坐标上的伸缩尺寸 //第三个参数fromY为动画起始时Y坐标上的伸缩尺寸 //第四个参数toY为动画结束时Y坐标上的伸缩尺寸 /*说明: 以上四种属性值 0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大 */ //第五个参数pivotXType为动画在X轴相对于物件位置类型 //第六个参数pivotXValue为动画相对于物件的X坐标的开始位置 //第七个参数pivotXType为动画在Y轴相对于物件位置类型 //第八个参数pivotYValue为动画相对于物件的Y坐标的开始位置 /*myAnimation_Scale = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f); myAnimation_Scale.setDuration(2000);*/ //使用xml读取动画 myAnimation_Scale = AnimationUtils.loadAnimation(this,R.anim.scale); return myAnimation_Scale; } public void AllAnimation(){ mySet.addAnimation(AlphaAnimation()); mySet.addAnimation(RotateAnimation()); mySet.addAnimation(TranslateAnimation()); mySet.addAnimation(ScaleAnimation()); mySet.setDuration(3000); av.startAnimation(mySet); } }
二、Frame Animation(逐帧动画)
所谓的逐帧动画就是平常我们观看的视频一样,都是一帧一帧的快速播放图片来欺骗观众的眼球以达到动起来的错觉。
- 逐帧动画可以使用xml文件来进行设置
<?xml version="1.0" encoding="utf-8"?> <!-- animation-list是一组图片的数组,用来逐帧显示 --> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" ><!-- oneshot表示是否只循环一遍 --> <item android:drawable="@drawable/p0" android:duration="500" /> <item android:drawable="@drawable/p1" android:duration="500" /> <item android:drawable="@drawable/p2" android:duration="500" /> <item android:drawable="@drawable/p3" android:duration="500" /> <item android:drawable="@drawable/p4" android:duration="500" /> <item android:drawable="@drawable/p5" android:duration="500" /> </animation-list>
上面每个item就是一张图片,这个xml文件要放在drawable文件夹中。
在代码中调用iv.setBackgroundResource(R.drawable.frame_animation); AnimationDrawable anim = (AnimationDrawable) iv.getBackground(); anim.start();
iv是一个ImageView控件,设置背景图像后,就可以开始动画。
- 直接用代码生成逐帧动画
//完全编码实现的动画效果 AnimationDrawable anim = new AnimationDrawable(); for (int i = 1; i <= 4; i++) { //根据资源名称和目录获取R.java中对应的资源ID int id = getResources().getIdentifier("p" + i, "drawable", getPackageName()); //根据资源ID获取到Drawable对象 Drawable drawable = getResources().getDrawable(id); //将此帧添加到AnimationDrawable中 anim.addFrame(drawable, 500); } anim.setOneShot(false); //设置为loop //api15以前 iv.setBackgroundDrawable(anim); //将动画设置为ImageView背景 //api16以后 //iv.setBackground(anim); anim.start(); //开始动画
- 全部代码
package com.cpacm.demo.testrep; import android.app.Activity; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.ImageView; import com.cpacm.demo.testrep.R; public class FrameAnimationActivity extends Activity implements View.OnClickListener{ private ImageView iv; private Button b1,b2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_frame_animation); iv = (ImageView)findViewById(R.id.imageView); b1 = (Button)findViewById(R.id.start); b1.setOnClickListener(this); b2 = (Button)findViewById(R.id.end); b2.setOnClickListener(this); } @Override public void onClick(View v) { switch(v.getId()){ case R.id.start: iv.setBackgroundResource(R.drawable.frame_animation); AnimationDrawable anim = (AnimationDrawable) iv.getBackground(); anim.start(); //runFrame(); break; case R.id.end: AnimationDrawable anim2 = (AnimationDrawable) iv.getBackground(); if (anim2.isRunning()) { //如果正在运行,就停止 anim2.stop(); } break; } } public void runFrame() { //完全编码实现的动画效果 AnimationDrawable anim = new AnimationDrawable(); for (int i = 1; i <= 4; i++) { //根据资源名称和目录获取R.java中对应的资源ID int id = getResources().getIdentifier("p" + i, "drawable", getPackageName()); //根据资源ID获取到Drawable对象 Drawable drawable = getResources().getDrawable(id); //将此帧添加到AnimationDrawable中 anim.addFrame(drawable, 500); } anim.setOneShot(false); //设置为loop //api15以前 iv.setBackgroundDrawable(anim); //将动画设置为ImageView背景 //api16以后 //iv.setBackground(anim); anim.start(); //开始动画 } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.frame_animation, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
- 效果截图
三、Activity Animation(切换动画)
Activity之间切换也是有动画的,我们可以直接拿系统默认的动画,也可以使用自定义的动画
Intent intent = new Intent(); intent.setClass(mainActivity.this,TweenAnimationActivity.class); startActivity(intent); overridePendingTransition(android.R.anim.fade_in,android.R.anim.fade_out);//系统自带的切换动画
放在intent跳转antivity的后面。
自带的动画这样写
//如果这个地方想用自己的,可以调用anim文件夹下的xml文件,如下: overridePendingTransition(R.anim.push_left_in, R.anim.push_left_out);
xml文件
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="300" android:fromYDelta="100%p" android:toYDelta="0" /> <alpha android:duration="300" android:fromAlpha="0.0" android:toAlpha="1.0" /> </set>
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <!-- push_up_out 上下滑入式 --> <translate android:duration="300" android:fromYDelta="0" android:toYDelta="-100%p" /> <alpha android:duration="300" android:fromAlpha="1.0" android:toAlpha="0.0" /> </set>
overridePendingTransition(arg1,arg2);
里面的两个参数分别是进入的activity动画和退出的activity动画。
四、ObjectAnimator(属性动画)
Animator框架是Android 4.0中新添加的一个动画框架,和之前的Animation框架相比,Animator可以进行更多和更精细化的动画控制,而且比之前更简单和更高效。在4.0源码中随处都可以看到Animator的使用。
Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是一个整个View动画,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源。
在Animator框架中使用最多的是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,多个ObjectAnimator组合到AnimatorSet形成一个动画。而且ObjectAnimator能够自动驱动,可以调用setFrameDelay(long frameDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少CPU资源消耗。
//第一个参数是属性,即要变化的属性,下面是变化的值,可以放置多个数值 PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X,100); PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 1f); PropertyValuesHolder pvhR = PropertyValuesHolder.ofFloat(View.ROTATION, 730); PropertyValuesHolder pvhsX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1,2,1); PropertyValuesHolder pvhsY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1,2,1); PropertyValuesHolder pvhA = PropertyValuesHolder.ofFloat(View.ALPHA, 1); final ObjectAnimator animation = ObjectAnimator.ofPropertyValuesHolder(iv, pvhX, pvhY, pvhR, pvhsX, pvhsY, pvhA); animation.setDuration(2000); animation.setInterpolator(new OvershootInterpolator(0.9f)); animation.start();
主要是ObjectAnimator,其ObjectAnimator.ofPropertyValuesHolder(view,PropertyValuesHolder...)的方法是给view设置一系列的动画,与AnimationSet类似。
详细的用法可以 参考文章(5)ApiDemos解析
完整代码:
package com.cpacm.demo.testrep; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.OvershootInterpolator; import android.widget.Button; import android.widget.ImageView; public class ObjectAnimatorActivity extends Activity implements View.OnClickListener{ private ImageView iv; private Button b1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_object_animator); iv = (ImageView)findViewById(R.id.imageView2); b1 = (Button)findViewById(R.id.button); b1.setOnClickListener(this); } @Override public void onClick(View v) { switch(v.getId()){ case R.id.button: //第一个参数是属性,即要变化的属性,下面是变化的值,可以防止多个数值 PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X,100); PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 1f); PropertyValuesHolder pvhR = PropertyValuesHolder.ofFloat(View.ROTATION, 730); PropertyValuesHolder pvhsX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1,2,1); PropertyValuesHolder pvhsY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1,2,1); PropertyValuesHolder pvhA = PropertyValuesHolder.ofFloat(View.ALPHA, 1); final ObjectAnimator animation = ObjectAnimator.ofPropertyValuesHolder(iv, pvhX, pvhY, pvhR, pvhsX, pvhsY, pvhA); animation.setDuration(2000); animation.setInterpolator(new OvershootInterpolator(0.9f)); animation.start(); break; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.object_animator, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
效果:
五、结束语
动画在android的UI设计中总是占着一席之地,动画往往会给用户带来一种生动的感觉,所以学习好动画的设计是很重要的。这篇文章列举的动画种类可能不全,我也是根据自己用过的动画做了个总结而已,如果还有其他的动画API欢迎大家补充。那么大家下次再见~
参考文章:(1) Android动画效果 第九章 http://book.51cto.com/art/201204/328247.htm
(2)详解Android动画之Frame Animation http://blog.csdn.net/liuhe688/article/details/6657776
(3)Android Activity之间动画完整版详解 http://mzh3344258.blog.51cto.com/1823534/807337
(5)ApiDemos解析:多属性动画 http://www.cnblogs.com/mengdd/archive/2013/09/06/3305698.html
Demo地址:https://github.com/cpacm/android_learn/tree/master/AnimationDemo
========================================
作者:cpacm