11.1 动画资源

Android SDK支持3种动画:属性动画、帧动画和补间动画,而补间动画又分为移动补间动画、透明度补间动画、缩放补间动画和旋转补间动画,这4种动画都可以使用一种叫渲染器的技术。本节将介绍这些动画的定义和使用方法,以及如何自定义渲染器。

11.1.1 属性(Property)动画

源代码目录:src/ch11/PropertyAnim

属性动画从API Level = 11(Android 3.0)才开始支持。

属性动画可以使对象的属性值在一定时间间隔内变化到某一个值。例如,在1000毫秒内移动控件的位置(改变x和y的值),在500毫秒内改变alpha属性的值以便改变图像的透明度。属性动画资源文件位于res/animator目录中。下面的代码定义了一个属性动画文件。

源代码文件:src/ch11/PropertyAnim/res/animator/property_anim.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"

  android:ordering="sequentially" >

  <objectAnimator

    android:duration="2000"

    android:propertyName="y"

    android:valueTo="300"

    android:valueType="intType" />

  <objectAnimator

    android:duration="3000"

    android:propertyName="x"

    android:valueTo="220"

    android:valueType="intType" />

</set>

其中android:ordering属性指定<set>标签中动画的执行顺序,本例是按顺序执行。默认是同时执行(两个<objectAnimator>标签指定的动画同时改变对象属性的值)。因此在本例中property_anim.xml动画文件的功能就是先将对象的y属性值在2000毫秒内变为300,然后再将对象的x属性值在3000毫秒内变为220。

由于Java没有对象的概念,所以这里的属性就是getter和setter方法。例如,x属性会对应读属性值的getX方法和写属性值的setX方法,所以为了使对象可以使用property_anim.xml资源文件控制属性值,在对象中必须要有x和y属性,而且必须是int类型。

在本例中提供了一个Move类,通过该类创建的对象可以通过property_anim.xml动画文件改变x和y属性的值。运行本例后,单击“move”按钮,“移动我”按钮会通过改变Move对象的x和y属性值垂直和水平移动,效果如图11-1所示。

 

▲图11-1 属性动画

本例的完整实现代码如下:

源代码文件:src/ch11/PropertyAnim/src/mobile/android/property/anim/Main.java

public class Main extends Activity

{  

  private Button button;

  private Move move;

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    button = (Button) findViewById(R.id.button);

    move = new Move(); 

  }

  // 对Move对象使用属性动画

  class Move

  {

    private int y;

    private int x;

 

    public int getY()

    { 

      return y;

    }

 

    public void setY(int y)

    {

      this.y = y;

      // 垂直移动按钮

      button.layout(button.getLeft(), y, button.getRight(),

          y + button.getMeasuredHeight());

    }

 

    public int getX()

    {

      return x;

    }

 

    public void setX(int x)

    {

      this.x = x;

      // 水平移动按钮

      button.layout(x, button.getTop(), x + button.getMeasuredWidth(),

          button.getBottom());

    }

  }

  // “move”按钮的单击事件方法

  public void onClick_Move(View view)

  {

    // 装载属性动画资源

    AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,

        R.animator.property_anim);

    // 设置要控制的对象

    set.setTarget(move);

    // 开始动画

    set.start();

  }

}

11.1.2 帧(Frame)动画

源代码目录:src/ch11/FrameAnim

帧动画类似于电影的播放过程。电影一般每秒会至少播放25幅静态的图像(25帧)。由于人类的视觉暂留,会将一定时间(很短的时间)之前的图像暂存于大脑中,这样在高频率地连续播放静态图像时就会产生动画的效果。

根据帧动画的原理,需要在动画资源中定义若干个静态的图像,然后在res/anim目录中建立一个帧动画资源文件。

定义帧动画资源文件时应了解如下几点。

帧动画必须用<animation-list>标签作为根节点。

如果<animation-list>标签的android:oneshot属性值为true,表示动画只播放一次。如果该属性值为false,则表示动画会无限次循环播放。

每一个<item>表示一个静态图像。通过android:drawable属性指定图像资源。

<animation-list>标签的android:duration属性指定了当前图像停留的时间,也就是两幅静态画面之间切换的时间间隔。

使用帧动画资源需要先装载资源文件,并转换成AnimationDrawable对象,然后调用AnimationDrawable.start方法开始播放帧动画。

下面看一个帧动画的完整示例。在这个例子中单击“开始动画”按钮可以连续不断播放动画,效果如图11-2所示。单击“添加动画”按钮,会动态将另一个帧动画添加到当前帧动画中,在播放完第1个帧动画后,会立刻播放第2个帧动画,然后会重复这一过程,效果如图11-3所示。

 

▲图11-2 循环播放帧动画

 

▲图11-3 循环播放两个帧动画

实现本例首先需要准备若干图像文件,然后在res/anim目录建立两个帧动画资源文件:frame_animation.xml和frame_animation1.xml。这两个资源文件的内容相似,只是定义的静态图像文件不同。这两个帧动画资源文件中都设置了每个静态图像停留时间为50毫秒,也就是切换两幅图的时间间隔是50毫秒。

源代码文件:src/ch11/FrameAnim/res/anim/frame_animation.xml

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

  android:oneshot="false">

  <item android:drawable="@drawable/anim1" android:duration="50" />

  <item android:drawable="@drawable/anim2" android:duration="50" />

  <item android:drawable="@drawable/anim3" android:duration="50" />

  ……<!-- 省略了其他item标签 -->

</animation-list>

接下来要实现本例的主程序,程序的核心代码在onClick方法中,该方法处理了图11-2所示界面中4个按钮的单击动作。本例完整的实现代码如下:

源代码文件:src/ch11/FrameAnim/src/mobile/android/frame/anim/Main.java

public class Main extends Activity implements OnClickListener

{  

  private ImageView ivAnimView;

  private AnimationDrawable animationDrawable;

  private AnimationDrawable animationDrawable1;

  private Button btnAddFrame;

  @Override

  public void onClick(View view)

  {

    switch (view.getId())

    {

      // 只播放一次动画

      case R.id.btnOneShot:

        // 动态修改android:oneshot属性值

        animationDrawable.setOneShot(true);

        // 需要先停止动画,然后再开始动画设置才能生效

        animationDrawable.stop();

        // 开始播放动画

        animationDrawable.start();

        break;

      // 循环播放动画

      case R.id.btnStartAnim:

        animationDrawable.setOneShot(false);

        animationDrawable.stop();

        animationDrawable.start();

        break;

      // 停止播放动画

      case R.id.btnStopAnim:

        // 停止播放第1个帧动画

        animationDrawable.stop();

        if (animationDrawable1 != null)

        {

          // 如果添加了第2个帧动画,则同时停止第2个帧动画

          animationDrawable1.stop();

        }

        break;

      // 在第1个帧动画后添加第2个帧动画

      case R.id.btnAddFrame:

        // 装载第2个帧动画

        animationDrawable1 = (AnimationDrawable) getResources()

            .getDrawable(R.anim.frame_animation1);

        // 设置第2个帧动画停留的时间(2000毫秒),不管在这段时间内帧动画

        // 是否播放完,都将切换到第1个帧动画

        animationDrawable.addFrame(animationDrawable1, 2000);

        btnAddFrame.setEnabled(false);     

        break;

    }

  }

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    Button btnStartAnim = (Button) findViewById(R.id.btnStartAnim);

    Button btnStopAnim = (Button) findViewById(R.id.btnStopAnim);

    Button btnOneShot = (Button) findViewById(R.id.btnOneShot);

    btnAddFrame = (Button) findViewById(R.id.btnAddFrame);

    btnStartAnim.setOnClickListener(this);

    btnStopAnim.setOnClickListener(this);

    btnOneShot.setOnClickListener(this);

    btnAddFrame.setOnClickListener(this);

    ivAnimView = (ImageView) findViewById(R.id.ivAnimView);

    // 将第1个帧动画设为ImageView控件的背景

    ivAnimView.setBackgroundResource(R.anim.frame_animation);

    Object backgroundObject = ivAnimView.getBackground();

    animationDrawable = (AnimationDrawable) backgroundObject;

  }

}

帧动画还可以通过AnimationDrawable.setAlpha方法设置透明度,setAlpha方法的参数值范围是0至255,0表示不透明,255表示完全透明(动画看不见了)。例如,下面的代码设置了帧动画的透明度为100。

AnimationDrawable animationDrawable = (AnimationDrawable) getResources()

.getDrawable(R.anim.frame_animation);

// 设置帧动画的透明度为100

animationDrawable.setAlpha(100);

11.1.3 移动补间(TranslateTween)动画

移动是最常见的动画效果。可以通过配置动画文件(xml文件)或Java代码来实现补间动画的移动效果。补间动画文件需要放在res/anim目录中,在动画文件中通过<translate>标签设置移动效果,假设在res/anim目录下有一个动画文件test.xml,该文件的内容如下:

<translate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/accelerate_interpolator"

  android:fromXDelta="0" android:toXDelta="320" android:fromYDelta="0"

  android:toYDelta="0" android:duration="2000" />

从上面的配置代码可以看出,<translate>标签中设置了6个属性,这6个属性的含义如下。

android:interpolator:表示动画渲染器。通过android:interpolator属性可以设置不同的动画渲染器,例如,accelerate_interpolator(动画加速器)、decelerate_interpolator(动画减速器)、accelerate_decelerate_interpolator(动画加速减速器)等。动画加速器使动画在开始时速度最慢,然后逐渐加速。动画减速器使动画在开始时速度最快,然后逐渐减速。动画加速减速器使动画在开始和结束时速度最慢,但在前半部分(根据时间分成前半部分和后半部分)时开始加速,在后半部分时开始减速。

android:fromXDelta:相对于图像当前位置的水平开始偏移距离。

android:toXDelta:相对于图像当前位置的水平结束偏移距离。

android:fromYDelta:相对于图像当前位置的垂直开始偏移距离。

android:toYDelta:相对于图像当前位置的垂直介绍偏移距离。

android:duration:动画的持续时间,单位是毫秒。也就是说,动画要在android:duration属性指定的时间内从起始点移动到结束点。

装载补间动画文件需要使用android.view.animation.AnimationUtils. loadAnimation方法,该方法的原型如下:

public static Animation loadAnimation(Context context, int id);

其中id参数表示动画文件的资源ID。装载test.xml文件的代码如下:

Animation animation = AnimationUtils.loadAnimation(this, R.anim.test);

假设有一个EditText控件(editText),将test.xml文件中设置的补间动画应用到EditText控件上的方式有如下两种。

(1)使用EditText.startAnimation方法,代码如下:

editText.startAnimation(animation);

(2)使用Animation.start方法,代码如下:

// 绑定补间动画

editText.setAnimation(animation);

// 开始动画

animation.start();

使用上面两种方式开始的补间动画都只显示一次。如果想循环显示动画,需要使用如下代码将动画设置成循环状态:

animation.setRepeatCount(Animation.INFINITE);

上面的代码在开始动画之前和之后执行都没有问题。

如果想通过Java代码实现移动补间动画,可以创建android.view.animation.TranslateAnimation对象。TranslateAnimation类构造方法的原型如下:

public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) ;

TranslateAnimation类构造方法的4个参数等同于<translate>标签同名的属性。

在创建TranslateAnimation对象后,可以通过TranslateAnimation类的如下方法设置移动补间动画的其他属性。

setInterpolator:设置动画渲染器。该方法的参数类型是Interpolator,在Android SDK中提供了一些内置的动画渲染器,例如LinearInterpolator、AccelerateInterpolator等。这些渲染器同样可以通过<translate>标签的android:interpolator属性设置。

setDuration:设置动画的持续时间。该方法相当于设置了<translate>标签的android:duration属性。

补间动画有3个状态:动画开始、动画结束、动画循环。要想监听这3个状态,需要实现android.view.animation.Animation.AnimationListener接口。该接口定义了3个方法:onAnimationStart、onAnimationEnd和onAnimationRepeat,这3个方法分别在动画开始、动画结束和动画循环时调用。关于AnimationListener接口的用法将在下一节详细介绍。

11.1.4 循环向右移动的EditText与上下弹跳的球

源代码目录:src/ch11/TranslateAnim

本节将演示如何使用移动补间动画资源实现动画效果。本例的动画效果是在屏幕上方的EditText控件从左到右循环匀速水平移动。EditText下方的小球上下移动,从上到下移动时加速,从下到上移动时减速。

在本例中定义了3个动画文件,其中translate_right.xml应用于EditText控件,translate_bottom.xml(从上到下移动、加速)和translate_top.xml(从下到上移动、减速)应用于小球(ImageView控件)。这3个动画资源文件的代码如下:

源代码文件:src/ch11/TranslateAnim/res/anim/translate_right.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/linear_interpolator"

  android:fromXDelta="-320" android:toXDelta="320" android:fromYDelta="0"

  android:toYDelta="0" android:duration="5000" />

源代码文件:src/ch11/TranslateAnim/res/anim/translate_bottom.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/accelerate_interpolator"

  android:fromXDelta="0" android:toXDelta="0" android:fromYDelta="0"

  android:toYDelta="260" android:duration="2000" />

源代码文件:src/ch11/TranslateAnim/res/anim/translate_top.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/decelerate_interpolator"

  android:fromXDelta="0" android:toXDelta="0" android:fromYDelta="260"

  android:toYDelta="0" android:duration="2000" />

EditText控件的循环水平移动可以直接使用setRepeatMode和setRepeatCount方法进行设置,而小球的移动则需要应用两个动画文件。本例采用的方法是在一个动画播放完后,再将另一个动画文件应用到显示小球的ImageView控件中。这个操作需要在AnimationListener.onAnimationEnd方法中完成。

运行本例后,单击“开始动画”按钮,EditText控件从屏幕的左侧出来,循环水平向右移动,当EditText控件完全移进屏幕右侧时,会再次从屏幕左侧出来,同时小球会上下移动。效果如图11-4所示。

 

▲图11-4 移动补间动画

本例的完整代码如下:

源代码文件:src/ch11/TranslateAnim/src/mobile/android/translate/anim/Main.java

public class Main extends Activity implements OnClickListener, AnimationListener

{

  private EditText editText;

  private ImageView imageView;

  private Animation animationRight;

  private Animation animationBottom;

  private Animation animationTop;

 

  // animation参数表示当前应用到控件上的Animation对象

  @Override

  public void onAnimationEnd(Animation animation)

  {

    // 根据当前显示的动画决定下次显示哪一个动画

    if (animation.hashCode() == animationBottom.hashCode())

      imageView.startAnimation(animationTop);

    else if (animation.hashCode() == animationTop.hashCode())

      imageView.startAnimation(animationBottom);

  }

  @Override

  public void onAnimationRepeat(Animation animation)

  {

  }

  @Override

  public void onAnimationStart(Animation animation)

  {

  }

  // “开始动画”按钮的单击事件方法

  @Override

  public void onClick(View view)

  {

    // 开始EditText的动画

    editText.setAnimation(animationRight);

    animationRight.start();

    // 设置EditText动画无限循环状态

    animationRight.setRepeatCount(Animation.INFINITE);

    editText.setVisibility(EditText.VISIBLE);

    // 开始小球的动画

    imageView.startAnimation(animationBottom);

  }

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    editText = (EditText) findViewById(R.id.edittext);

    editText.setVisibility(EditText.INVISIBLE);

    Button button = (Button) findViewById(R.id.button);

    button.setOnClickListener(this);

    imageView = (ImageView) findViewById(R.id.imageview);

    animationRight = AnimationUtils.loadAnimation(this,R.anim.translate_right);

    animationBottom = AnimationUtils.loadAnimation(this,R.anim.translate_bottom);

    animationTop = AnimationUtils.loadAnimation(this, R.anim.translate_top);

    animationBottom.setAnimationListener(this);

    animationTop.setAnimationListener(this);

  }

}

11.1.5 缩放补间(Scale Tween)动画

缩放补间动画就是使动画资源所控制的View在一定时间内从大到小或从小到大的变化过程。通过<scale>标签可以定义缩放补间动画。下面的代码定义了一个标准的缩放补间动画。

<scale xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/decelerate_interpolator"

 android:fromXScale="0.2" android:toXScale="1.0" android:fromYScale="0.2"

  android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%"

  android:duration="2000" />

<scale>标签和<translate>标签中有些属性是相同的,而有些属性是<scale>标签特有的,这些属性的含义如下。

android:fromXScale:表示沿x轴缩放的起始比例。

android:toXScale:表示沿x轴缩放的结束比例。

android:fromYScale:表示沿y轴缩放的起始比例。

android:toYScale:表示沿y轴缩放的结束比例。

android:pivotX:表示沿x轴方向缩放的支点位置。如果该属性值为50%,则支点在沿x轴的中心位置。

android:pivotY:表示沿y轴方向缩放的支点位置。如果该属性值为50%,则支点在沿y轴的中心位置。

其中前4个属性的取值规则如下。

0.0:表示收缩到没有。

1.0:表示正常不收缩。

大于1.0:表示将View放大到相应的比例。例如值为1.5,表示放大到原View的1.5倍。

小于1.0:表示将View缩小到相应的比例。例如值为0.5,表示缩小到原View的50%。

如果想通过Java代码实现缩放补间动画,可以创建android.view.animation.ScaleAnimation对象。ScaleAnimation类构造方法的原型如下:

public ScaleAnimation(float fromX, float toX, float fromY, float toY,float pivotX, float pivotY)

通过ScaleAnimation类的构造方法可以设置上述6个属性值。设置其他属性的方法与移动补间动画相同。

11.1.6 跳动的心

源代码目录:src/ch11/ScaleAnim

本节将演示如何使用缩放补间动画资源实现动画效果,本例将实现一个可以跳动的心,跳动实际上就是将“心”图像不断地放大和缩小。因此,需要两个动画文件来控制图像的放大和缩小。这两个动画文件的内容如下:

源代码文件:src/ch11/ScaleAnim/res/anim/to_small.xml

< !-- 控制图像的缩小 -->

<scale xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/accelerate_interpolator"

  android:fromXScale="1.0" android:toXScale="0.2" android:fromYScale="1.0"

  android:toYScale="0.2" android:pivotX="50%" android:pivotY="50%"

  android:duration="500" />

源代码文件:src/ch11/ScaleAnim/res/anim/to_large.xml

< !-- 控制图像的放大 -->

<scale xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/decelerate_interpolator"

  android:fromXScale="0.2" android:toXScale="1.0" android:fromYScale="0.2"

  android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%"

  android:duration="500" />

对ImageView控件不断应用上面两个动画文件后,就会显示如图11-5所示的动画效果。

 

▲图11-5 跳动的心

本例的完整代码如下:

源代码文件:src/ch11/ScaleAnim/src/mobile/android/scale/anim/Main.java

public class Main extends Activity implements AnimationListener

{

  private Animation toLargeAnimation;

  private Animation toSmallAnimation;

  private ImageView imageView;

  @Override

  public void onAnimationEnd(Animation animation)

  {

    // 交替应用两个动画文件

    if(animation.hashCode() == toLargeAnimation.hashCode())

      imageView.startAnimation(toSmallAnimation);

    else

      imageView.startAnimation(toLargeAnimation);

  }

  @Override

  public void onAnimationRepeat(Animation animation)

  {

  }

  @Override

  public void onAnimationStart(Animation animation)

  {

  }

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    imageView = (ImageView) findViewById(R.id.imageview);

    toLargeAnimation = AnimationUtils.loadAnimation(this, R.anim.to_large);

    toSmallAnimation = AnimationUtils.loadAnimation(this, R.anim.to_small);

    toLargeAnimation.setAnimationListener(this);

    toSmallAnimation.setAnimationListener(this);

    imageView.startAnimation(toSmallAnimation);

  }

}

11.1.7 旋转补间(Rotate Tween)动画

旋转补间动画可以使View围绕某一点旋转,如果这个旋转点在View的中心,那么View就是自转。通过<rotate>标签可以定义旋转补间动画。下面的代码定义了一个标准的旋转补间动画。

<rotate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@anim/linear_interpolator" android:fromDegrees="0"

  android:toDegrees="360" android:pivotX="50%" android:pivotY="50%"

  android:duration="10000"

  android:repeatMode="restart" android:repeatCount="infinite"/>

其中<rotate>标签有两个特殊的属性。它们的含义如下。

android:fromDegrees:表示旋转的起始角度。

android:toDegrees:表示旋转的结束角度。

在<rotate>标签中还使用如下两个属性设置旋转的次数和模式。

android:repeatCount:设置旋转的次数。该属性需要设置一个整数值,如果该值为0,表示不重复显示动画。也就是说,对于上面的旋转补间动画,只从0度旋转到360度,动画就会停止。如果属性值大于0,动画会再次显示该属性指定的次数。例如,如果android:repeatCount属性值为1,动画除了正常显示一次外,还会再显示一次。也就是说,前面的旋转补间动画会顺时针旋转两周。如果想让补间动画永不停止,可以将android:repeatCount属性值设为infinite或-1。该属性的默认值是0。

android:repeatMode:设置重复的模式,默认值是restart。该属性只有当android:repeatCount属性值大于0或是infinite时才起作用。android:repeatMode属性值除了可以设为restart外,还可以设为reverse,表示偶数次显示动画时会做与动画文件定义的方向相反的动作。例如,前面定义的旋转补间动画如果android:repeatMode属性的值设为reverse。会在第1、3、5、……2n-1圈顺时针旋转,而在2、4、6、……2n圈逆时针旋转。如果想使用Java代码来设置该属性,可以使用Animation.setRepeatMode方法,该方法只接受一个int类型的参数值。可取的值是Animation.RESTART和Animation.REVERSE。

如果想通过Java代码实现旋转补间动画,可以创建android.view.animation.RotateAnimation对象。RotateAnimation类构造方法的原型如下:

public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY);

通过RotateAnimation类的构造方法可以设置旋转开始角度(fromDegrees)、旋转结束角度(toDegrees)、旋转支点横坐标(pivotX)和旋转支点纵坐标(pivotY)。

11.1.8 旋转的星系

源代码目录:src/ch11/RotateAnim

本节将演示如何使用旋转补间动画资源实现动画效果。本例实现了两颗行星绕着一颗恒星旋转的效果,其中恒星会顺时针和逆时针交替旋转(android:repeatMode属性值为reverse),效果如图11-6所示。

 

▲图11-6 旋转的星系

使两颗行星和一颗恒星旋转需要3个动画资源文件。行星对应的两个动画文件的代码如下:

源代码文件:src/ch11/RotateAnim/res/anim/hesper.xml

<!-- 应用于金星(hesper) -->

<rotate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@anim/linear_interpolator" android:fromDegrees="0"

  android:toDegrees="360" android:pivotX="200%" android:pivotY="300%"

  android:duration="5000" android:repeatMode="restart" android:repeatCount="infinite"/>

源代码文件:src/ch11/RotateAnim/res/anim/earth.xml

<!-- 应用于地球 -->

<rotate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@anim/linear_interpolator" android:fromDegrees="0"

  android:toDegrees="360" android:pivotX="200%" android:pivotY="300%"

  android:duration="10000" android:repeatMode="restart" android:repeatCount="infinite"/>

恒星对应的动画文件的内容如下:

源代码文件:src/ch11/RotateAnim/res/anim/sun.xml

<!-- 应用于太阳(sun) -->

<rotate xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@anim/linear_interpolator" android:fromDegrees="0"

  android:toDegrees="360" android:pivotX="50%" android:pivotY="50%"

  android:duration="20000"

  android:repeatMode="reverse" android:repeatCount="infinite"/>

本例的主程序相对简单,只需要装载这3个动画文件,并开始动画即可,代码如下:

源代码文件:src/ch11/RotateAnim/src/mobile/android/rotate/anim/Main.java

public class Main extends Activity

{

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    ImageView ivEarth = (ImageView) findViewById(R.id.ivEarth);

    ImageView ivHesper = (ImageView) findViewById(R.id.ivHesper);

    ImageView ivSun = (ImageView) findViewById(R.id.ivSun);

    Animation earthAnimation = AnimationUtils.loadAnimation(this,R.anim.earth);

    Animation hesperAnimation = AnimationUtils.loadAnimation(this,R.anim.hesper);

    Animation sunAnimation = AnimationUtils.loadAnimation(this, R.anim.sun);

    ivEarth.startAnimation(earthAnimation);

    ivHesper.startAnimation(hesperAnimation);

    ivSun.startAnimation(sunAnimation);

  }

}

11.1.9 透明度补间(Alpha Tween)动画

透明度补间动画可以控制View在透明和不透明之间变化。通过<alpha>标签可以定义透明度补间动画。下面的代码定义了一个标准的透明度补间动画。

<alpha xmlns:android="http://schemas.android.com/apk/res/android"

  android:interpolator="@android:anim/accelerate_interpolator"

    android:fromAlpha="1.0" android:toAlpha="0.1" android:duration="2000" />

其中android:fromAlpha和android:toAlpha属性分别表示起始透明度和结束透明度,这两个属性的值都在0.0~1.0之间。属性值为0.0表示完全透明,属性值为1.0表示完全不透明。

如果想通过Java代码实现透明度补间动画,可以创建android.view.animation.AlphaAnimation对象。AlphaAnimation类构造方法的原型如下:

public AlphaAnimation(float fromAlpha, float toAlpha);

通过AlphaAnimation类的构造方法可以设置起始透明度(fromAlpha)和结束透明度(toAlpha)。

在下一节中会将多种补间动画和帧动画相结合,实现投掷炸弹并爆炸的效果。

11.1.10 投掷炸弹

源代码目录:src/ch11/AlphaAnim

本节将前面介绍的多种动画效果进行结合,实现投掷炸弹并爆炸的特效。在本例中采用的动画类型有帧动画、移动补间动画、缩放补间动画和透明度补间动画。

其中使用帧动画播放一个爆炸的GIF动画;使用移动补间动画实现炸弹被投下仍然会向前移动的偏移效果;缩放补间动画实现当炸弹被投下时逐渐缩小的效果;透明度补间动画实现炸弹被投下时逐渐模糊的效果。当运行本例后,会在屏幕下方正中间显示一个炸弹,如图11-7所示。然后触摸这个炸弹,炸弹开始投掷,逐渐变小和模糊,如图11-8所示。当炸弹变得很小、很模糊时,会播放GIF动画来显示爆炸效果,并播放爆炸的声音,如图11-9所示。

 

▲图11-7 初始状态的炸弹

 

▲图11-8 炸弹逐渐变小和模糊

 

▲图11-9 炸弹爆炸的效果

除了爆炸效果外,其他的效果都必须同时进行,因此,需要将这些效果放在同一个动画文件中。在res/anim目录中建立一个missile.xml文件,并输入如下内容:

源代码文件:src/ch11/AlphaAnim/res/anim/missile.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">

  <alpha android:interpolator="@android:anim/accelerate_interpolator"

    android:fromAlpha="1.0" android:toAlpha="0.1" android:duration="2000" />

  <translate android:interpolator="@android:anim/accelerate_interpolator"

    android:fromXDelta="0" android:toXDelta="0" android:fromYDelta="0"

    android:toYDelta="-380" android:duration="2000" />

  <scale android:interpolator="@android:anim/accelerate_interpolator"

    android:fromXScale="1.0" android:toXScale="0.2" android:fromYScale="1.0"

    android:toYScale="0.2" android:pivotX="50%" android:pivotY="50%"

    android:duration="2000" />

</set>

上面的动画文件与前面介绍的动画文件不同之处在于这个动画文件使用了<set>标签作为XML根节点。所有在<set>标签中定义的动画会在同一时间开始,在混合动画效果的情况下,往往会使用<set>标签来组合动画效果。

本例还有一个帧动画文件(blast.xml),在该动画文件中定义了15个静态的GIF文件。blast.xml文件的内容请读者参阅随书光盘中的源代码。

由于在播放完爆炸GIF动画后,需要隐藏显示动画的ImageView控件,所以在本例中实现了一个MyImageView类,并将所有的逻辑封装在该类中。MyImageView类的代码如下:

源代码文件:src/ch11/AlphaAnim/src/mobile/android/alpha/anim/MyImageView.java

public class MyImageView extends ImageView

{

  public AnimationDrawable animationDrawable;

  public ImageView ivMissile;

  public Field field;

  @Override

  protected void onDraw(Canvas canvas)

  {

    try

    {

      field = AnimationDrawable.class.getDeclaredField("mCurFrame");

      field.setAccessible(true);

      int curFrame = field.getInt(animationDrawable);

      // 当显示完最后一幅图像后,将MyImageView控件隐藏,并显示炸弹的原始图像

      if (curFrame == animationDrawable.getNumberOfFrames() - 1)

      {

        setVisibility(View.INVISIBLE);

        ivMissile.setVisibility(View.VISIBLE);

      }

    }

    catch (Exception e)

    {  

    }

    super.onDraw(canvas);

  }

  public MyImageView(Context context, AttributeSet attrs)

  {

    super(context, attrs);

  }

}

本例主程序的完整代码如下:

源代码文件:src/ch11/AlphaAnim/src/mobile/android/alpha/anim/Main.java

public class Main extends Activity implements OnTouchListener,AnimationListener

{

  private ImageView ivMissile;

  private MyImageView ivBlast;

  private AnimationDrawable animationDrawable;

  private Animation missileAnimation;

  @Override

  public boolean onTouch(View view, MotionEvent event)

  {

    // 触摸炸弹后,开始播放动画

    ivMissile.startAnimation(missileAnimation);

    return false;

  }

  @Override

  public void onAnimationEnd(Animation animation)

  {

    // 在播放投掷炸弹动画结束后,显示MyImageView控件,并将显示炸弹的ImageView控件隐藏

    ivBlast.setVisibility(View.VISIBLE);

    ivMissile.setVisibility(View.INVISIBLE);

    try

    {

      // 开始播放爆炸的声音

      MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.bomb);

      mediaPlayer.stop();

      mediaPlayer.prepare();

      mediaPlayer.start();

    }

    catch (Exception e)

    {    

    }

    animationDrawable.stop();

    // 播放爆炸效果动画

    animationDrawable.start();

  }

  @Override

  public void onAnimationRepeat(Animation animation)

  {

  }

  @Override

  public void onAnimationStart(Animation animation)

  {

  }

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    ivMissile = (ImageView) findViewById(R.id.ivMissile);

    ivMissile.setOnTouchListener(this);

    ivBlast = (MyImageView) findViewById(R.id.ivBlast);

    ivBlast.setBackgroundResource(R.anim.blast);

    Object backgroundObject = ivBlast.getBackground();

    animationDrawable = (AnimationDrawable) backgroundObject;

    ivBlast.animationDrawable = animationDrawable;

    missileAnimation = AnimationUtils.loadAnimation(this, R.anim.missile);

    missileAnimation.setAnimationListener(this);

    // 在程序启动后,将显示爆炸效果的MyImageView控件隐藏

    ivBlast.setVisibility(View.INVISIBLE);

    ivBlast.ivMissile = ivMissile;

  }

}

注意

如果要想让<set>标签中的动画循环显示,需要将<set>标签中的每一个动画标签(<translate>、<scale>、<rotate>和<alpha>)的android:repeatCount属性值设为infinite。当然,也可以将部分动画标签的android:repeatCount属性值设为infinite,这样那些未设置android:repeatCount属性的动画就不会循环显示。

11.1.11 震动渲染器(Shake Interpolator)

源代码目录:src/ch11/Shake

在前面曾介绍过4个动画渲染器(linear_interpolator、accelerate_interpolator、decelerate_ interpolator和accelerate_decelerate_interpolator)。实际上,在Android SDK中还支持更多的动画渲染器,例如,震动渲染器cycle_interpolator就是其中之一。

下面来建立一个动画文件shake.xml,并输入如下内容:

源代码文件:src/ch11/Shake/res/anim/shake.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"

  android:fromXDelta="0" android:toXDelta="10" android:duration="1000"

  android:interpolator="@android:anim/cycle_interpolator" />

本节的例子会使一个EditText控件发生剧烈的振动,开始振动效果的代码如下:

源代码文件:src/ch11/Shake/src/mobile/android/shake/Main.java

Animation animation = AnimationUtils.loadAnimation(this, R.anim.shake);

EditText editText = (EditText)findViewById(R.id.edittext);

editText.startAnimation(animation);

如果想在Java代码中实现振动效果,需要创建CycleInterpolator对象,CycleInterpolator类构造方法的原型如下:

public CycleInterpolator(float cycles);

通过CycleInterpolator类的构造方法可以设置振动因子。

运行本节的例子,单击“振动”按钮后,上方的EditText控件会左右剧烈振动,如图11-10所示。

 

▲图11-10 振动效果

注意

由于Android模拟器自身的问题,本例在Android模拟器上的运行效果并不理想,所以尽量在真机上测试本例。

11.1.12 自定义渲染器(Interpolator)

源代码目录:src/ch11/Interceptor

前面介绍了很多动画渲染器。实际上,我们也可以实现自己的动画渲染器。要实现动画渲染器,需要实现android.view.animation.Interpolator接口。本节的例子中要实现一个可以来回弹跳的动画渲染器。渲染器的实现代码如下:

源代码文件:src/ch11/Interceptor/src/mobile/android/interceptor/MyInterceptor.java

public class MyInterceptor implements Interpolator

{

  @Override

  public float getInterpolation(float input)

  {

    // 动画前一半时间不断接近目标点

    // 由于随着input的不断增大,input*input也会越来越大,而且增加的幅度会增大

    // 所以动画的前一半时间内是做加速运动的,而且由于input*input不可能超过0.25,

    // 所以使用该渲染器的View只能完成总路程的1/4 ,例如,对于移动补间动画,只能移动

    // 1/4的路程

    if (input <= 0.5)

      return input * input;

    // 动画后一半时间不断远离目标点(减速)

    else

      return (1 - input) * (1 - input) ;

  }

}

在Interpolator接口中只有一个getInterpolation方法。该方法有一个float类型的参数,取值范围在0.0~1.0之间,表示动画的进度。如果参数值为0.0,表示动画刚开始。如果参数值为1.0,表示动画已结束。如果getInterpolation方法的返回值小于1.0,表示动画对象还没有到达目标点,越接近1.0,动画对象离目标点越近,当返回值为1.0时,正好到达目标点。如果getInterpolation方法的返回值大于1.0,表示动画对象超过了目标点。例如在移动补间动画中,getInterpolation方法的返回值是2.0,表示动画对象超过了目标点,并且距目标点的距离等于目标点到起点的距离。

下面来编写动画文件(translate.xml),代码如下:

源代码文件:src/ch11/Interceptor/res/anim/translate.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android"

  android:fromXDelta="0" android:toXDelta="0" android:fromYDelta="0"

  android:toYDelta="1550" android:duration="5000" />

装载和开始动画的代码如下:

源代码文件:src/ch11/Interceptor/src/mobile/android/interceptor/Main.java

ImageView imageView = (ImageView)findViewById(R.id.imageview);

Animation animation = AnimationUtils.loadAnimation(this, R.anim.translate);

// 设置自定义动画渲染器(自定义的动画渲染器不能使用动画文件中的android:interpolator属性设置)

animation.setInterpolator(new MyInterceptor());

animation.setRepeatCount(Animation.INFINITE);

imageView.startAnimation(animation);

运行本节的例子,会看到如图11-11所示的小球,小球到达屏幕底端会向上弹起。

 

▲图11-11 上下弹跳的小球

源码分析:自定义动画渲染器为什么不能使用android:interpolator属性设置

自定义的动画渲染器是不能使用android:interpolator属性设置的,那么这是为什么呢?

要回答这个问题,首先要知道系统内置的动画渲染器为什么可以使用android:interpolator属性设置。系统内置的每一个动画渲染器都是一个XML文件,这些XML文件可以在如下的路径找到根据版本不同,android-17也可以是其他版本的目录,如android-16,但其他目录中的动画渲染器文件个数可能不同。

<Android SDK根目录>/platforms/android-17/data/res/anim

进入anim目录后,凡是文件名以“_interpolator”结尾的XML文件都是动画渲染器文件,例如,前面使用过的加速渲染器对应的文件名是accelerate_interpolator.xml,加速减速渲染器对应的文件名是accelerate_decelerate_interpolator.xml。读者可以打开任何一个渲染器文件,看到所有的文件中除了注释外,就只有一行代码。例如,accelerate_interpolator.xml文件中的代码如下:

<accelerateInterpolator />

经过测试,accelerateInterpolator并不代表任何的渲染器类,例如,如果自定义的渲染器类是MyInterpolator,是不能在渲染器文件中使用如下内容的。

<myInterpolator>

那么<accelerateInterpolator />到底代表什么呢?这就需要从Android SDK源代码中获得答案了。

由于装载动画资源文件是通过AnimationUtils.loadAnimation方法完成的,因此处理XML形式的渲染器最有可能在AnimationUtils类中完成。在Eclipse中跟踪进AnimationUtils类,寻找与Interpolator相关的方法。通过方法列表很容易找到一个loadInterpolator方法,定位到该方法的实现代码,发现该方法中调用了一个私有的createInterpolatorFromXml方法,该方法实际用于从XML文件中装载渲染器,也就是前面所说的accelerate_interpolator.xml、accelerate_decelerate_interpolator.xml等文件。现在定位到createInterpolatorFromXml方法,很容易就看到该方法中有如下的代码片段。其中name就是XML形式的渲染器文件中的唯一的标签的名称,例如,accelerateInterpolator。

if (name.equals("linearInterpolator")) {

  interpolator = new LinearInterpolator(c, attrs);

} else if (name.equals("accelerateInterpolator")) {

  interpolator = new AccelerateInterpolator(c, attrs);

} else if (name.equals("decelerateInterpolator")) {

  interpolator = new DecelerateInterpolator(c, attrs);

} else if (name.equals("accelerateDecelerateInterpolator")) {

  interpolator = new AccelerateDecelerateInterpolator(c, attrs);

} else if (name.equals("cycleInterpolator")) {

  interpolator = new CycleInterpolator(c, attrs);

} else if (name.equals("anticipateInterpolator")) {

  interpolator = new AnticipateInterpolator(c, attrs);

} else if (name.equals("overshootInterpolator")) {

  interpolator = new OvershootInterpolator(c, attrs);

} else if (name.equals("anticipateOvershootInterpolator")) {

  interpolator = new AnticipateOvershootInterpolator(c, attrs);

} else if (name.equals("bounceInterpolator")) {

  interpolator = new BounceInterpolator(c, attrs);

} else {

  throw new RuntimeException("Unknown interpolator name: " + parser.getName());

}

从上面的代码很容易看出,系统已经将内置的渲染器硬编码在Android SDK中,而且是根据渲染器文件中的标签名决定使用哪个渲染器类的,例如,<linearInterpolator/>标签对应了LinearInterpolator类。如果是自定义的渲染器类,自然无法直接定义一个XML形式的渲染器文件,除非修改Android SDK代码。