8、逐帧(Frame)动画
要求开发者把动画过程的没涨静态图片都收集起来,然后由Android开控制依次显示这些静态图片,与放电影的原理一样。
9、AnimationDrawable与逐帧动画
只要在<animation-list.../>元素中使用<item.../>子元素定义动画的全部帧,并指定各帧的持续时间即可。语法格式如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot=["true" | "false"] >
<item android:drawable="@[package:]drawable/drawable_resource_name"
android:duration="integer" />
</animation-list>
android:oneshot控制该动画是否循环播放(true:不会循环播放)。每个<item/>子元素添加一帧。
【提示】Android完全支持在Java代码中创建逐帧动画,如果开发者喜欢的话,完全可以先创建AnimationDrawable对象,
然后调用addFrame(Drawable frame, int duration)向该动画中添加帧,每调用一次addFrame方法,就像<animation-list.../>
元素中添加一个<item.../>子元素。
一旦程序获取了AnimationDrawable对象之后,接下来就可用ImageView把AnimationDrawable显示出来---习惯上把AnimationDrawable
设置成ImageView的背景即可。
范例:功夫熊猫动画。
1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- 指定动画循环播放 fat_po.xml --> 3 <animation-list xmlns:android="http://schemas.android.com/apk/res/android" 4 android:oneshot="false"> 5 <!-- 添加多个帧 --> 6 <item android:drawable="@drawable/fat_po_f01" android:duration="60" /> 7 <item android:drawable="@drawable/fat_po_f02" android:duration="60" /> 8 <item android:drawable="@drawable/fat_po_f03" android:duration="60" /> 9 <item android:drawable="@drawable/fat_po_f26" android:duration="60" /> 10 <item android:drawable="@drawable/fat_po_f27" android:duration="60" /> 11 </animation-list>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#fff" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <Button android:id="@+id/play" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/play" /> <Button android:id="@+id/stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/stop" /> </LinearLayout> <ImageView android:id="@+id/anim" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@anim/fat_po" android:scaleType="center" /> </LinearLayout>
1 // 获取AnimationDrawable动画对象,并设置到ImageView背景上。 2 final AnimationDrawable anim = (AnimationDrawable) imageView 3 .getBackground(); 4 play.setOnClickListener(new OnClickListener() { 5 @Override 6 public void onClick(View v) { 7 // 开始播放动画 8 anim.start(); 9 } 10 }); 11 stop.setOnClickListener(new OnClickListener() { 12 @Override 13 public void onClick(View v) { 14 // 停止播放动画 15 anim.stop(); 16 } 17 });
【注意】AnimationDrawable代表的动画默认是不播放的,必须在程序中启动动画播放才可以。
AnimationDrawable提供了如下两个方法来开始、停止动画。
start():开始播放动画。
stop():停止播放动画。
范例:在指定点爆炸
爆炸效果实际上是一个逐帧动画,开发者需要收集从开始爆炸到爆炸结束的所有静态图片,再
将这些图片定义成一个逐帧动画,接着在碰撞点播放该逐帧动画即可。
<!-- res\anim\blast.xml --> <?xml version="1.0" encoding="utf-8"?> <!-- 定义动画只播放一次,不循环 --> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true" > <item android:drawable="@drawable/bom_f01" android:duration="80" /> <item android:drawable="@drawable/bom_f02" android:duration="80" /> <item android:drawable="@drawable/bom_f03" android:duration="80" /> <item android:drawable="@drawable/bom_f26" android:duration="80" /> <item android:drawable="@drawable/bom_f27" android:duration="80" /> </animation-list>
public class Blast extends Activity { private MyView myView; private AnimationDrawable anim; private MediaPlayer bomb; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 使用FrameLayout布局管理器,它允许组件自己控制位置 FrameLayout frame = new FrameLayout(this); setContentView(frame); // 设置使用背景 frame.setBackgroundResource(R.drawable.back); // 加载音效 bomb = MediaPlayer.create(this, R.raw.bomb); myView = new MyView(this); // 设置myView用于显示blast动画 myView.setBackgroundResource(R.anim.blast); // 设置myView默认为隐藏 myView.setVisibility(View.INVISIBLE); // 获取动画对象 anim = (AnimationDrawable) myView.getBackground(); frame.addView(myView); frame.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View source, MotionEvent event) { // 只处理按下事件(避免每次产生两个动画效果) if (event.getAction() == MotionEvent.ACTION_DOWN) { // 先停止动画播放 anim.stop(); float x = event.getX(); float y = event.getY(); // 控制myView的显示位置 myView.setLocation((int) y - 40, (int) x - 20); myView.setVisibility(View.VISIBLE); // 启动动画 anim.start(); // 播放音效 bomb.start(); } return false; } }); } // 定义一个自定义View,该自定义View用于播放“爆炸”效果 class MyView extends ImageView { public MyView(Context context) { super(context); } // 定义一个方法,该方法用于控制MyView的显示位置 public void setLocation(int top, int left) { this.setFrame(left, top, left + 40, top + 40); } // 重写该方法,控制如果动画播放到最后一帧时,隐藏该View @Override protected void onDraw(Canvas canvas) { try { Field field = AnimationDrawable.class .getDeclaredField("mCurFrame"); field.setAccessible(true); // 获取anim动画的当前帧 int curFrame = field.getInt(anim); // 如果已经到了最后一帧 if (curFrame == anim.getNumberOfFrames() - 1) { // 让该View隐藏 setVisibility(View.INVISIBLE); } } catch (Exception e) { } super.onDraw(canvas); } } }
10、补间(Tween)动画
补间动画就是指开发者只需指定动画开始、动画结束“关键帧”,而动画变化的“中间帧”由系统计算并补齐,
这就是把Tween动画翻译为“补间动画”的原因。
11、Tween动画与Interpolator
对于补间动画而言,开发者无需"逐一"定义动画过程中的每一帧。只要定义动画开始、结束的关键帧,并指定动画的持续时间即可。
Interpolator根据特定算法计算出整个动画所需要动态插入帧的密度和位置,简单说,
Interpolator负责控制动画的变化速度,这就使得基本的动画效果(Alpha、Scale、Translate、Rotate)
能以匀速变化、加速、减速、抛物线速度等各种速度变化。
Interpolator是一个接口,它定义了所有Interpolator都需要实现的方法:float getInterpolation(float input),
开发者完全可以通过实现Interpolator来控制动画的变化速度。
Android为Interpolator提供了如下几个实现类:分别用于实现不同动画变化速度。
● LinearInterpolator:动画以均匀的速度改变。
● AccelerateInterpolator:在动画开始的地方改变速度较慢,然后开始加速。
● AccelerateDecelerateInterpolator:在动画开始、结束的地方改变速度较慢,在中间的时候加速。
● CycleInterpolator:运动循环播放特定的次数,变化速度按正玄曲线改变。
● DecelerateInterpolator:在动画开始的地方改变速度较快,然后开始减慢。
为了实现动画资源文件中指定补间动画所使用的Interpolator,定义补间动画的<set.../>元素支持一个
android:interpolator属性,该属性的属性值可以指定为Android默认支持的Interpolator。
● @android:anim/linear_interpolator
● @android:anim/accelerate_interpolator
● @android:anim/accelerate_decelerate_interpolator
......
其实上面的写法很有规律,它们就是把系统提供的Interpolator实现类的类名的驼峰写法改为
中划线写法即可。
一旦在程序中通过AnimationUtils得到代表补间动画的Animation之后,接下来就可调用View
的startAnimation(Animation anim)方法开始对该View执行动画了。
12、位置、大小、旋转度、透明度改变的补间动画
虽然Android允许在程序中创建Animation对象,但实际上一般都会采用动画资源文件来定义补间动画。
实例:介绍补间动画(包括两个动画资源文件)
第一个动画资源文件控制图片以旋转的方式缩小,该动画资源文件如下:
<!-- res\anim\anim.xml --> <?xml version="1.0" encoding="UTF-8"?> <!-- 指定动画匀速改变 --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/linear_interpolator"> <!-- 定义缩放变换 --> <scale android:fromXScale="1.0" android:toXScale="0.01" android:fromYScale="1.0" android:toYScale="0.01" android:pivotX="50%" android:pivotY="50%" android:fillAfter="true" android:duration="3000"/> <!-- 定义透明度的变换 --> <alpha android:fromAlpha="1" android:toAlpha="0.05" android:duration="3000"/> <!-- 定义旋转变换 --> <rotate android:fromDegrees="0" android:toDegrees="1800" android:pivotX="50%" android:pivotY="50%" android:duration="3000"/> </set>
上面的动画资源指定动画匀速变化,同时进行缩放、透明度改变、旋转三种改变,动画持续时间为三秒。
第二个动画资源则控制图片以动画的方式恢复回来,对应的动画资源如下:\res\anim\reverse.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- 指定动画匀速改变 --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/linear_interpolator" android:startOffset="3000"> <!-- 定义缩放变换 --> <scale android:fromXScale="0.01" android:toXScale="1" android:fromYScale="0.01" android:toYScale="1" android:pivotX="50%" android:pivotY="50%" android:fillAfter="true" android:duration="3000"/> <!-- 定义透明度的变换 --> <alpha android:fromAlpha="0.05" android:toAlpha="1" android:duration="3000"/> <!-- 定义旋转变换 --> <rotate android:fromDegrees="1800" android:toDegrees="0" android:pivotX="50%" android:pivotY="50%" android:duration="3000"/> </set>
定义动画资源之后,接下来就可以利用AnimationUtils工具类来加载指定的动画资源,加载成功后会返回一个Animation,
该对象即可控制图片或视图播放动画。
public class TweenAnim extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final ImageView flower = (ImageView) findViewById(R.id.flower); // 加载第一份动画资源 final Animation anim = AnimationUtils.loadAnimation(this, R.anim.anim); // 设置动画结束后保留结束状态 anim.setFillAfter(true); // 加载第二份动画资源 final Animation reverse = AnimationUtils.loadAnimation(this, R.anim.reverse); // 设置动画结束后保留结束状态 reverse.setFillAfter(true); Button bn = (Button) findViewById(R.id.bn); final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0x123) { flower.startAnimation(reverse); } } }; bn.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { flower.startAnimation(anim); // 设置3.5秒后启动第二个动画 new Timer().schedule(new TimerTask() { @Override public void run() { handler.sendEmptyMessage(0x123); } }, 3500); } }); } }
范例:蝴蝶飞舞
蝴蝶飞行时的振翅效果是逐帧动画;蝴蝶飞行时的位置改变是补间动画。
<!-- \res\anim\butterfly.xml --> <?xml version="1.0" encoding="utf-8"?> <!-- 定义动画循环播放 --> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/butterfly_f01" android:duration="120" /> <item android:drawable="@drawable/butterfly_f02" android:duration="120" /> <item android:drawable="@drawable/butterfly_f03" android:duration="120" /> <item android:drawable="@drawable/butterfly_f04" android:duration="120" /> <item android:drawable="@drawable/butterfly_f05" android:duration="120" /> <item android:drawable="@drawable/butterfly_f06" android:duration="120" /> </animation-list>
定义了上面逐帧动画的动画资源后,接下来在程序中使用一个ImageView显示该动画资源即可。
这就可以看到蝴蝶“振翅”效果了。由于蝴蝶飞舞主要是位移改变,接下来可以在程序中通过
TranslateAnimation以动画的方式改变ImageView的位置,这样就达到蝴蝶飞舞的效果了。
<ImageView android:id="@+id/butterfly" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@anim/butterfly" />
public class Butterfly extends Activity { // 记录蝴蝶ImageView当前的位置 private float curX = 0; private float curY = 30; // 记录蝴蝶ImageView下一个位置的座标 float nextX = 0; float nextY = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 获取显示蝴蝶的ImageView组件 final ImageView imageView = (ImageView) findViewById(R.id.butterfly); final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0x123) { // 横向上一直向右飞 if (nextX > 320) { curX = nextX = 0; } else { nextX += 8; } // 纵向上可以随机上下 nextY = curY + (float) (Math.random() * 10 - 5); // 设置显示蝴蝶的ImageView发生位移改变 TranslateAnimation anim = new TranslateAnimation(curX, nextX, curY, nextY); curX = nextX; curY = nextY; anim.setDuration(200); // 开始位移动画 imageView.startAnimation(anim); // ① } } }; final AnimationDrawable butterfly = (AnimationDrawable) imageView .getBackground(); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 开始播放蝴蝶振翅的逐帧动画 butterfly.start();// ② // 通过定制器控制每0.2秒运行一次TranslateAnimation动画 new Timer().schedule(new TimerTask() { @Override public void run() { handler.sendEmptyMessage(0x123); } }, 0, 200); } }); } }
上面程序①位于Handler的消息处理方法内,这样程序每隔0.2秒即对该ImageView执行一次位移动画;
程序②,用于播放butterfly动画(蝴蝶振翅效果)。运行上面程序,单击蝴蝶,即可看到屏幕有只蝴蝶从左向右飞舞。
13、自定义补间动画
Android提供了Animation作为补间动画抽象基类。而且为该抽象基类提供了AlphaAnimation、RotateAnimation、
ScaleAnimation、TranslateAnimation四个实现类。这四个实现类只是补间动画的四种基本形式:透明度改变、旋转、
缩放、位移,在实际项目中可能还需要一些更复杂的动画,比如让图片在"三维"空间内进行旋转动画等。这就需要开发
着自己开发补间动画了。
import android.graphics.Camera; import android.graphics.Matrix; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; // 自定义动画类 public class MyAnimation extends Animation { private float centerX; private float centerY; // 定义动画的持续事件 private int duration; private Camera camera = new Camera(); public MyAnimation(float x, float y, int duration) { this.centerX = x; this.centerY = y; this.duration = duration; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); // 设置动画的持续时间 setDuration(duration); // 设置动画结束后效果保留 setFillAfter(true); setInterpolator(new LinearInterpolator()); } /* * 该方法的interpolatedTime代表了抽象的动画持续时间,不管动画实际持续时间多长, * interpolatedTime参数总是从0(动画开始时)~1(动画结束时) Transformation参数代表了对目标组件所做的变. */ @Override protected void applyTransformation(float interpolatedTime, Transformation t) { camera.save(); // 根据interpolatedTime时间来控制X、Y、Z上的偏移 camera.translate(100.0f - 100.0f * interpolatedTime, 150.0f * interpolatedTime - 150, 80.0f - 80.0f * interpolatedTime); // 设置根据interpolatedTime时间在Y柚上旋转不同角度。 camera.rotateY(360 * (interpolatedTime)); // 设置根据interpolatedTime时间在X柚上旋转不同角度 camera.rotateX((360 * interpolatedTime)); // 获取Transformation参数的Matrix对象 Matrix matrix = t.getMatrix(); camera.getMatrix(matrix); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); camera.restore(); } }
1 import android.app.Activity; 2 import android.os.Bundle; 3 import android.util.DisplayMetrics; 4 import android.view.Display; 5 import android.view.WindowManager; 6 import android.widget.ListView; 7 8 public class ListViewTween extends Activity { 9 @Override 10 public void onCreate(Bundle savedInstanceState) { 11 super.onCreate(savedInstanceState); 12 setContentView(R.layout.main); 13 // 获取ListView组件 14 ListView list = (ListView) findViewById(R.id.list); 15 WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); 16 Display display = windowManager.getDefaultDisplay(); 17 DisplayMetrics metrice = new DisplayMetrics(); 18 // 获取屏幕的宽和高 19 display.getMetrics(metrice); 20 // 设置对ListView组件应用动画 21 list.setAnimation(new MyAnimation(metrice.xdpi / 2, metrice.ydpi / 2, 22 3500)); 23 } 24 }
<!-- main.xml --> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ListView android:id="@+id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:entries="@array/bookArray" /> </LinearLayout>
<!-- res/values/arrays.xml --> <?xml version="1.0" encoding="UTF-8"?> <resources> <string-array name="bookArray"> <item>疯狂Java讲义</item> <item>轻量级Java EE企业应用实战</item> <item>经典Java EE企业应用实战</item> <item>疯狂Ajax讲义</item> <item>疯狂Android讲义</item> </string-array> </resources>