Android Animation完全总结(一) Drawable动画

本文对Android中的常用动画技术进行了较为全面的总结,并给出了代码示例(Java代码实现和XML中实现)。由于内容较多,所以尽可能简洁表述,并重点指出其中不易理解、容易出错的内容。

本文提到的Android动画主要有三类:

  • Drawable动画
  • Animation与AnimationSet
  • Animator与AnimatorSet

示例代码、思维导图可在此下载
https://github.com/jzj1993/AndroidAnimation

Animatable Drawable 动画

用Drawable实现动画,适用于不需要变换View,只需要对View中所展示的Drawable图形产生动画的情况。

由于不像Animation需要对View进行矩阵变换,更不需要像Animator使用反射机制,实现同样的效果,Drawable动画通常性能较好,推荐使用。

原理简介

当给一个View的背景、ImageView的src设置了Drawable后,View会将自身设置为Drawable的Drawable.Callback(View实现了Drawable.Callback接口)。于是在Drawable需要刷新时,可通过这个接口调用View的invalidate,从而触发View.onDraw方法进行重绘。具体可参考Android源码。

支持动画的Drawable,应实现Animatable接口。有些View属性会对Drawable进行判断(例如ImageView的src属性),如果实现了Animatable接口,就会自动调用其start方法启动动画。

  1. public class ImageView extends View implements Drawable.Callback {

  2. public void setImageDrawable(Drawable drawable) {

  3. if (mDrawable != drawable) {

  4. // ...

  5. updateDrawable(drawable);

  6. // ...

  7. }

  8. }

  9. private void updateDrawable(Drawable d) {

  10. if (mDrawable != null) {

  11. mDrawable.setCallback(null);

  12. unscheduleDrawable(mDrawable);

  13. }

  14. mDrawable = d;

  15. if (d != null) {

  16. d.setCallback(this);

  17. if (d.isStateful()) {

  18. d.setState(getDrawableState());

  19. }

  20. d.setLevel(mLevel);

  21. d.setLayoutDirection(getLayoutDirection());

  22. d.setVisible(getVisibility() == VISIBLE, true);

  23. mDrawableWidth = d.getIntrinsicWidth();

  24. mDrawableHeight = d.getIntrinsicHeight();

  25. applyColorMod();

  26. configureBounds();

  27. } else {

  28. mDrawableWidth = mDrawableHeight = -1;

  29. }

  30. }

  31. }

FrameAnimation 逐帧动画(AnimationDrawable)

逐帧动画实际上就是一种支持动画效果的Drawable

  1. public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable { }

从XML创建,使用Java代码加载:

  • 将每一帧的图片放在资源文件夹res/drawable

  • 在XML中定义动画每一帧及其持续时间

  • 在Java代码中加载动画并设置给View,然后启动动画

  • OneShot属性为true则只播放一次,否则不断循环播放

  1. <?xml version="1.0" encoding="utf-8"?>

  2. <animation-list xmlns:android="http://schemas.android.com/apk/res/android"

  3. android:oneshot="false">

  4. <item

  5. android:drawable="@drawable/icon1"

  6. android:duration="200" />

  7. <item

  8. android:drawable="@drawable/icon2"

  9. android:duration="200" />

  10. <item

  11. android:drawable="@drawable/icon3"

  12. android:duration="200" />

  13. <item

  14. android:drawable="@drawable/icon4"

  15. android:duration="200" />

  16. </animation-list>

  17. mFrameAnimation = (AnimationDrawable) getResources().getDrawable(R.drawable.frame_anim); // 从XML加载动画

  18. mTextView.setBackground(mFrameAnimation);

  19. mFrameAnimation.start();

  • 也可以在Java代码中实例化AnimationDrawable对象,并添加帧和持续时间
  1. mFrameAnimation = new AnimationDrawable();
  2. mFrameAnimation.addFrame(getResources().getDrawable(R.drawable.icon1), 200);
  3. mFrameAnimation.addFrame(getResources().getDrawable(R.drawable.icon2), 200);

注意:

Android系统提供的这种逐帧动画,通常每一帧是一个BitmapDrawable,会在内存中一直保存每一帧的Bitmap,如果帧数较多、每一帧图片较大,消耗的内存会很大。

如果对内存有要求,可以自行实现逐帧动画,每切换一帧的时候临时加载该帧的BitmapDrawable,这样虽然增加了一些CPU资源消耗,但减少了内存占用。

AnimatedRotateDrawable旋转动画

和逐帧动画类似,Android系统还提供了AnimatedRotateDrawable,可以实现图片旋转的效果,常用于展示进度条动画。

由于构造函数是私有的,AnimatedRotateDrawable不支持在Java代码中实例化,只能从XML加载。

res/drawable/loading.png是一个PNG文件

res/drawable/animated_rotate.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:drawable="@drawable/loading"
  4. android:fromDegrees="0.0"
  5. android:pivotX="50.0%"
  6. android:pivotY="50.0%"
  7. android:toDegrees="360.0" />

注:因为是Drawable,animated_rotate的XML文件应该放在drawable文件夹下。

在XML中引用Drawable即可

  1. <ProgressBar

  2. android:id="@+id/progress"

  3. android:layout_width="50dp"

  4. android:layout_height="50dp"

  5. android:indeterminate="true"

  6. android:indeterminateDrawable="@drawable/animated_rotate" />

  7. <ImageView

  8. android:id="@+id/image_view"

  9. android:layout_width="50dp"

  10. android:layout_height="50dp"

  11. android:layout_marginTop="60dp"

  12. android:src="@drawable/animated_rotate" />

需要注意的是,不同的View和属性,是否会自动启动Drawable动画的行为不同。例如ProgressBar从XML加载indeterminateDrawable是可以启动动画的,但从Java加载则需要显示调用start方法。而ImageView的src属性,则可以自动启动动画。

  • 如果用Java代码给ProgressBar的indeterminateDrawable设置Drawable,一般需要用setBounds指定drawable的宽高,并显示调用Animatable.start()方法启动动画。

  • 如果用Java代码给ImageView的src设置Drawable,ImageView会自动处理Drawable的尺寸,并判断其是否实现了Animatable接口,从而启动动画。

  1. ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress);

  2. final Drawable drawable = getResources().getDrawable(R.drawable.animated_rotate);

  3. if (drawable != null) {

  4. drawable.setBounds(0, 0, progressBar.getWidth(), progressBar.getHeight());

  5. progressBar.setIndeterminateDrawable(drawable);

  6. if (drawable instanceof Animatable) {

  7. ((Animatable) drawable).start();

  8. }

  9. }

  10. ImageView imageView = (ImageView) findViewById(R.id.image_view);

  11. imageView.setImageDrawable(getResources().getDrawable(R.drawable.animated_rotate));

自定义DrawableAnimation

通过实现Animatable接口,可以自行定义支持动画效果的Drawable。

示例代码是一个每隔0.5s切换一种随机颜色值的Drawable,具体效果可参考工程源码。

  1. public class MyAnimDrawable extends Drawable implements Animatable, Runnable {

  2. private boolean mRunning = false;

  3. @Override

  4. public void draw(Canvas canvas) {

  5. canvas.drawARGB(128, (int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255));

  6. }

  7. @Override

  8. public void setAlpha(int alpha) {

  9. }

  10. @Override

  11. public void setColorFilter(ColorFilter cf) {

  12. }

  13. @Override

  14. public int getOpacity() {

  15. return 0;

  16. }

  17. @Override

  18. public boolean setVisible(boolean visible, boolean restart) {

  19. boolean changed = super.setVisible(visible, restart);

  20. if (visible) {

  21. if (changed restart) {

  22. nextFrame();

  23. }

  24. } else {

  25. unscheduleSelf(this);

  26. }

  27. return changed;

  28. }

  29. @Override

  30. public void start() {

  31. if (!mRunning) {

  32. mRunning = true;

  33. nextFrame();

  34. }

  35. }

  36. @Override

  37. public void stop() {

  38. unscheduleSelf(this);

  39. mRunning = false;

  40. }

  41. @Override

  42. public boolean isRunning() {

  43. return mRunning;

  44. }

  45. @Override

  46. public void run() {

  47. invalidateSelf();

  48. nextFrame();

  49. }

  50. private void nextFrame() {

  51. unscheduleSelf(this);

  52. scheduleSelf(this, SystemClock.uptimeMillis() + 500);

  53. }

  54. }