第16回 哼,我四岁就看动画!

张角:“看不出来,你们还有些斤两!”

张飞:“哼,比体重,我燕人张飞怕过谁!”

张角:“嗯,Good Point!我大黄巾教的未来可就全靠这款APP了!但是我觉得光有一些丰富的界面还是不够的啊,你们说再加点什么内容好呢?”

刘备心想:“靠,这我哪知道啊。”赶忙向孔明望去,哪知孔明好似没听到一般,装作什么事情都没有发生。刘备说:“这个嘛,我觉得为了达到更好的宣传效果,更生动的表达我们黄天已死,苍天当立这一理念……”

张角:“是苍天已死,黄天当立!”

刘备:“嗯?你确定?我怎么觉得是黄天已死,苍天当立读取来顺畅一些?”

张角:“我说是就是!”

刘备:“好吧,不管怎样,为了更好的达到宣传贵教教义的效果,我觉得应该在应用中加入一些动……动画?”说完,刘备又瞟了一眼孔明,但孔明仍是全无反应。

张角一拍手:“对啊!动画!我怎么没想到呢,动画的效果好啊,简单易懂。你们会做动画不?”
刘备:“哼,我四岁就看动画!”

张角:“啊哈哈哈,那我就放心了,你们赶紧搞起,这里是项目定金,事成之后的赏钱少不了你们的!”

刘关张三人齐道:“多谢大爷!”

张角走后,关羽说道:“大哥啊,动画这个东西咱没接触过啊。”

刘备:“嗨,什么项目都先接下再说,实在搞不好咱就向官府举报他们是邪教,也就没人来追究我们责任了,嘿嘿!”

张飞:“大哥,你好腹黑!”

1.1. 动画介绍

制作精美的应用程序,动画是必不可少的元素。Android操作系统提供多种动画效果,例如图片的放大缩小、按钮的弹出效果,Activity的切换动画,文本图片的旋转效果等。Android平台提供了一套完整的动画框架,使得应用开发者可以用它实现各种动画效果。本章通过对Android Animation、Activity跳转动画、Gif动画、SurfaceView绘画动画和ListView动画实例详解Android 动画开发。

1.2. Android Animation介绍

Android 3.0 以前提供了两种Animation动画。一种是Tween动画,即通过对场景里的对象不断进行图像变换(平移、缩放、旋转)来产生动画效果,也叫做补间动画。该动画的原理是给出两个关键帧,通过一些算法将指定的属性值在给定的时间内在这两个关键帧间渐变;第二种就是 Frame 动画,即顺序播放事先做好的图像,和电影类似,又称帧动画。在Android 3.0中又引入了一个新的动画:Property动画,通过改变对象属性实现动画,如Button的缩放,改变Button的位置与大小属性值。Property动画可以应用于任何对象。Property动画只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是由开发者决定的。

1.2.1.Tween动画

Android的Tween动画有四种动画类型,分别是渐变透明度动画效果alpha、渐变尺寸伸缩动画效果scale、画面转换位置移动动画效果translate和画面转移旋转动画效果rotate。在Android应用程序中可以通过xml文件来定义这些动画,也可以调用对应的渐变透明度动画效果AlphaAnimation类、渐变尺寸伸缩动画效果ScaleAnimation类、画面转换位置移动动画效果TranslateAnimation类和画面转移旋转动画效果RotateAnimation类来控制动画的播放。四种动画效果可以相互叠加,形成个性化的动画效果。本节就通过实例,详细介绍这四种Tween动画。运行程序,结果如图16-1所示:

图16-1 Tween动画效果

最近军师喜欢上了一个姑娘,叫月英,这也不咋教我们Android了。幸亏我老张的智商到达了222,这才能够最近学习动画开发,一起来看看我写的TweenActivity类吧!

TweenActivity.java代码清单16-2-1:

/**

*@author张飞:军师,二哥说女人爱说的5个谎话,你求我我就告诉你,往下看噢~

*/

public class TweenActivity extendsActivity {

    // 渐变透明度动画效果的按钮

private Button alphaButton;

    // 渐变透明度动画效果的按钮的监听器

private OnClickListener alphaButtonListener = null;

    // 渐变尺寸伸缩动画效果的按钮

private Button scaleButton;

    // 渐变尺寸伸缩动画效果的按钮的监听器

private OnClickListener scaleButtonListener = null;

    // 画面转换位置移动动画效果的按钮

private Button translateButton;

    // 画面转换位置移动动画效果的监听器

private OnClickListener translateButtonListener = null;

    // 画面转移旋转动画效果的按钮

private Button rotateButton;

    // 画面转移旋转动画效果的监听器

private OnClickListener rotateButtonListener = null;

    // 叠加动画效果的按钮

private Button mixButton;

    // 叠加动画效果的按钮的监听器

private OnClickListener mixButtonListener = null;

    // 旋转图像

private ImageView imageView = null;

    // 渐变透明度动画动画控制类

private Animation myAnimation_Alpha;

    // 渐变尺寸伸缩动画控制类

private Animation myAnimation_Scale;

    // 画面转换位置移动动画控制类

private Animation myAnimation_Translate;

    // 画面转移旋转动画控制类

private Animation myAnimation_Rotate;

    // 叠加动画控制类

private Animation myAnimation_Mix;

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

       setContentView(R.layout.tween);

       setListener();

        findView();

    }

    /**

     * @author张飞:1.给我讲讲你和她的故事,我不会生气的(讲完必须生气)

     */

private void findView() {

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

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

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

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

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

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

       alphaButton.setOnClickListener(alphaButtonListener);

       scaleButton.setOnClickListener(scaleButtonListener);

       translateButton.setOnClickListener(translateButtonListener);

       rotateButton.setOnClickListener(rotateButtonListener);

       mixButton.setOnClickListener(mixButtonListener);

    }

private void setListener() {

       alphaButtonListener = newOnClickListener() {

           @Override

public void onClick(View v) {

                //AnimationUtils动画工具类,loadAnimation()返回动画类Animation

                myAnimation_Alpha =AnimationUtils.loadAnimation(TweenActivity.this, R.anim.alpha);

                //view.startAnimation开始动画

               imageView.startAnimation(myAnimation_Alpha);

                // 通过AlphaAnimation类实现淡入淡出

                // alpha(imageView);

            }

        };

       scaleButtonListener = newOnClickListener() {

           @Override

public void onClick(View v) {

                //AnimationUtils动画工具类,loadAnimation()返回动画类Animation

                myAnimation_Scale =AnimationUtils.loadAnimation(TweenActivity.this, R.anim.scale);

               imageView.startAnimation(myAnimation_Scale);

                // 通过ScaleAnimation实现伸缩

                //sclae(imageView);

            }

        };

       translateButtonListener = newOnClickListener() {

           @Override

public void onClick(View v) {

               myAnimation_Translate = AnimationUtils.loadAnimation(TweenActivity.this,

                       R.anim.translate);

               imageView.startAnimation(myAnimation_Translate);

                // 通过ScaleAnimation实现位置移动

                //translate(imageView);

            }

        };

       rotateButtonListener = newOnClickListener() {

           @Override

publicvoid onClick(View v) {

               myAnimation_Rotate = AnimationUtils

                       .loadAnimation(TweenActivity.this,R.anim.rotate);

               imageView.startAnimation(myAnimation_Rotate);

                // 通过RotateAnimation实现旋转

                // positive(imageView)顺时针旋转

                //negative(imageView)逆时针旋转

            }

        };

       mixButtonListener = newOnClickListener() {

           @Override

publicvoid onClick(View v) {

                myAnimation_Mix =AnimationUtils.loadAnimation(TweenActivity.this, R.anim.mix);

               imageView.startAnimation(myAnimation_Mix);

            }

        };

    }

    /**

     *透明度渐变动画

* @author张飞:2.老夫老妻了,不要什么情人节礼物了(不买你就完蛋了)

     */

publicvoid alpha(View view) {

        // 设置开始完全不透明1.0f,后完全透明0.0f

        Animationanim = new AlphaAnimation(1.0f,0.0f);

        // 设置持续时间

       anim.setDuration(3000);

        // 停留在最后一帧

       anim.setFillAfter(true);

       view.startAnimation(anim);

    }

    /**

     *画面转换位置移动。

     *@author张飞:3.我想我真的不适合你(我根本就不喜欢你)

     */

publicvoid translate(View view) {

        // 第一个参数fromXDelta 属性为动画起始时 X坐标上的位置 200

        // 第二个参数toXDelta 属性为动画结束时 X坐标上的位置 0

        // 第三个参数fromYDelta 属性为动画起始时 Y坐标上的位置 300

        // 第四个参数toYDelta 属性为动画结束时 Y坐标上的位置 0

        Animationanim = newTranslateAnimation(200, 0, 300, 0);

       anim.setDuration(2000);

       anim.setFillAfter(true);

        /** 快速到达终点并超出一小步最后回到终点OvershootInterpolator */

       OvershootInterpolator overshoot = new OvershootInterpolator();

       anim.setInterpolator(overshoot);

       view.startAnimation(anim);

    }

    /**

     *渐变尺寸伸缩动画

     *@author张飞:我暂时不想交男朋友(闪边啦!你还不到我择偶标准的一半)

     */

publicvoid sclae(View view) {

        // 第一个参数fromX为动画起始时 X坐标上的伸缩尺寸

        // 第二个参数toX为动画结束时 X坐标上的伸缩尺寸

        // 第三个参数fromY为动画起始时Y坐标上的伸缩尺寸

        // 第四个参数toY为动画结束时Y坐标上的伸缩尺寸

        // 说明: 以上四种属性值

        /** 0.0表示收缩到没有 // 1.0表示正常无伸缩 // 值小于1.0表示收缩 // 值大于1.0表示放大 */

        // 第五个参数pivotXType为动画在X轴相对于物件位置类型

        // 第六个参数pivotXValue为动画相对于物件的X坐标的开始位置

        // 第七个参数pivotXType为动画在Y轴相对于物件位置类型

        // 第八个参数pivotYValue为动画相对于物件的Y坐标的开始位置

        Animationanim = new ScaleAnimation(2.0f,1.0f, 2.0f, 1.0f, Animation.RELATIVE_TO_SELF,

               0.5f, Animation.RELATIVE_TO_SELF,0.5f);

       anim.setDuration(2000);

       anim.setFillAfter(true);

        /** 最后阶段弹球效果 */

       BounceInterpolator bounce = newBounceInterpolator();

       anim.setInterpolator(bounce);

       view.startAnimation(anim);

    }

privateint currAngle;

    /**

     *画面顺时针转移旋转动画

            *@author张飞:我心中牵挂着一个人(那个人是我专门为你这种人虚构的)

     */

publicvoid positive(View view) {

        // 第一个参数fromDegrees为动画起始时的旋转角度

        // 第二个参数toDegrees为动画旋转到的角度

        // 第三个参数pivotXType为动画在X轴相对于物件位置类型

        // 第四个参数pivotXValue为动画相对于物件的X坐标的开始位置

        // 第五个参数pivotXType为动画在Y轴相对于物件位置类型

        // 第六个参数pivotYValue为动画相对于物件的Y坐标的开始位置

        Animationanim = newRotateAnimation(currAngle, currAngle + 180,

               Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f);

        /** 匀速插值器 */

       LinearInterpolator lir = newLinearInterpolator();

       anim.setInterpolator(lir);

       anim.setDuration(1000);

        /** 动画完成后不恢复原状 */

       anim.setFillAfter(true);

        currAngle+= 180;

if (currAngle > 360) {

           currAngle = currAngle - 360;

        }

       view.startAnimation(anim);

    }

    /**

     *画面逆时针转移旋转动画

            *@author孔明:飞飞?如果五句话她都对我说过是什么情况?

     */

public void negative(View view) {

        Animationanim = newRotateAnimation(currAngle, currAngle - 180,

               Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f);

        /** 匀速插值器 */

       LinearInterpolator lir = newLinearInterpolator();

        anim.setInterpolator(lir);

       anim.setDuration(1000);

        /** 动画完成后不恢复原状 */

       anim.setFillAfter(true);

        currAngle-= 180;

if (currAngle < -360) {

           currAngle = currAngle + 360;

        }

       view.startAnimation(anim);

    }

}

从上述代码可以看出Tween动画不仅可以通过硬编码的方式来实现,而且可以在res/anim目录下通过设置xml来实现。该xml文件必须包含一个根元素,可以使<alpha><scale><translate><rotate>插值元素或者是把上面的元素都放入<set>元素组中。默认情况下,所有的动画指令都是同时发生的,为了让它们按序列发生,需要设置一个特殊的属性startOffset。动画的指令定义了动画发生什么样的转换,执行多长的时间。

转换可以是连续的也可以是同时的。例如,让文本内容从左边移动到右边,然后旋转180度,或在移动的过程中同时旋转。如果让几个转换同时发生,可以给它们设置相同的开始时间,如果按序列发生,设置时间时需要根据开始时间加上其周期。 每个转换需要设置一些特殊的参数:开始和结束时的大小尺寸、旋转角度等,也可以设置基本的参数例如,开始时间与周期。下面就详细介绍如何通过xml配置实现动画。

 

渐变透明度Alpha

通过AlphaAnimation类可以设定渐变透明度动画效果,需要设置渐变的开始时透明度,结束时透明度和持续时间,也可以通过定义个xml文件实现渐变透明度动画效果,如下所示:

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

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

<alpha

android:fromAlpha="0.1"

android:toAlpha="1.0"

android:duration="3000"/>

</set>

<!-- 透明度控制动画效果 alpha

fromAlpha :动画起始时透明度

toAlpha:动画结束时透明度

说明:0.0表示完全透明,1.0表示完全不透明。值一般取0.0-1.0之间的float数据类型的数字长整型值。

duration:动画的持续时间。

说明:时间以毫秒为单位-->

 

渐变尺寸伸缩Scale

渐变尺寸伸缩动画需要设置伸缩前的坐标位置、伸缩后的坐标位置和动画相对于物件开始坐标位置的变化,如下所示:

<?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:指定一个动画的插入器

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时,该动画的转化将在动画结束后被应用-->

 

位置转移Translate

位置转移动画效果需要设置位置移动前后坐标的变化,如下所示:

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

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

<translate

android:fromXDelta="30"

android:toXDelta="-80"

android:fromYDelta="30"

android:toYDelta="300"

android:duration="2000"/>

</set>

<!-- translate 位置转移动画效果

fromXDelta:动画起始时 X坐标上的位置

toXDelta:动画结束时 X坐标上的位置

fromYDelta:动画起始时 Y坐标上的位置

toYDelta:动画结束时 Y坐标上的位置

说明:没有指定fromXType和toXType时,默认以自己为相对参照物

duration:动画的持续时间

说明:时间以毫秒为单位-->

 

旋转Rotate

旋转动画效果分为顺时针旋转和逆时针旋转,需要设置旋转前的坐标位置、旋转后的坐标位置、动画的插入器和动画相对于物件开始坐标位置的变化,如下所示:

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

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

<rotate

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

android:fromDegrees="0"

android:toDegrees="+350"

android:pivotX="50%"

android:pivotY="50%"

android:duration="3000" />

</set>

<!-- rotate 旋转动画效果

interpolator:指定一个动画的插入器

fromDegrees:动画起始时物件的角度

toDegrees:动画结束时物件旋转的角度,可以大于360度

说明:当角度为负数表示逆时针旋转、当角度为正数表示顺时针旋转

                      (负数from——to正数:顺时针旋转)  

              (负数from——to负数:逆时针旋转)

              (正数from——to正数:顺时针旋转)

               (正数from——to负数:逆时针旋转)      

pivotX:动画相对于物件的X坐标的开始位置

pivotY:动画相对于物件的Y坐标的开始位置

说明:以上两个属性值从0%-100%中取值、50%为物件X或Y方向的坐标上的中点位置

duration:动画的持续时间

说明:时间以毫秒为单位-->

 

叠加动画

通过xml文件不仅可以定义单个的动画,也可以将这些动画相互结合起来,如下所示:

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

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

<!-- 定义渐变动画 -->

<alpha

android:fromAlpha="0.1"

android:toAlpha="1.0"

android:duration="3000"/>

<!-- 定义渐变尺寸伸缩动画 -->

<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" />

<!-- 定义位置转移动画效果 -->

<translate

android:fromXDelta="30"

android:toXDelta="-80"

android:fromYDelta="30"

android:toYDelta="300"

android:duration="2000"/>

<!-- 定义旋转动画 -->

<rotate

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

android:fromDegrees="0"

android:toDegrees="+350"

android:pivotX="50%"

android:pivotY="50%"

android:duration="3000" />

</set>

 

Interpolator插值器的概念

Interpolator定义一个动画的变化率。这使得基本的动画效果(Alpha、Scale、Translate、 Rotate)得以加速、减速、重复等。Interpolator定义了动画的变化速度,可以实现匀速、正加速、负加速、无规则变加速等。Interpolator是基类,Android提供了几个Interpolator子类,用于实现不同的速度曲线,如下所示:

l  AccelerateDecelerateInterpolator        :变化频率在开始和结尾处慢,而在中间部分加速。

l  AccelerateInterpolator:变化频率在开始慢,然后加速。

l  AnticipateInterpolator:先向后,然后向前抛出(抛物运动)。

l  AnticipateOvershootInterpolator:先向后,向前抛出并超过目标值,最终返回到目标值。

l  BounceInterpolator    :在结束时反弹。

l  CycleInterpolator:用指定的循环数,重复播放动画。

l  DecelerateInterpolator:变化频率是快出,然后减速。

l  LinearInterpolator:固定的变化频率。

l  OvershootInterpolator:向前抛出,并超过目标值,然后再返回。

l  TimeInterpolator:实现自定义插值的一个接口。

 

AnimationUtils动画类介绍

AnimationUtils类是Android系统中的动画工具类,提供了控制View对象的一些工具。该类中最常用方法是public static Animation loadAnimation (Context context, int id)。通过loadAnimation()方法加载动画配置文件,并设置了动画的一些特征,然后开始执行动画。

 

AnimationListener动画的事件监听

Tween动画提供AnimationListener接口用于监听动画的播放事件。AnimationListener主要有三个接口:onAnimationStart()方法监听动画开始事件、onAnimationEnd()方法监听动画结束事件、onAnimationRepeat()方法监听动画重复事件。

1.2.2.Frame动画

Frame动画是帧动画,就是顺序播放事先做好的图像,类似于放电影。Android SDK提供了一个AnimationDrawable类来定义使用Frame动画。Frame动画可以在xml文件中定义,也可以使用AnimationDrawable类的API定义。由于Tween动画与Frame动画有着很大的不同,因此xml定义的格式也完全不一样。Frame Animation的xml文件格式为:首先是animation-list根节点,animation-list根节点中包含多个item子节点,每个item节点定义了一帧动画、当前帧的drawable资源和当前帧的持续时间。下面对各个节点的元素加以说明:

l  drawable:当前帧引用的drawable资源。

l  duration:当前帧的持续时间(毫秒)。

l  oneshot:果为true,表示动画只播放一次停止在最后一帧上,如果设置为false表示动画循环播放。

l  visible:定义drawable的初始可见性,默认为flase。

下面通过一个实例来讲解Frame动画如何使用,运行程序,结果如图16-2所示:

图16-2Frame动画效果

最近我小飞飞手气很好,玩色子那赢钱花花的,连军师要娶媳妇的钱都被我赢来了。现在草庐不叫卧龙岗了,改叫黑风岗了,哈哈。下面我偷偷告诉你们玩色子动画FrameActivity是怎么写的。

FrameActivity.java代码清单16-2-2:

/**

*@author张飞:“大哥,你怎么每次赌都带我去,是不是想用我的美色勾引他们?”
       刘备:“你想哪儿去了,我带你去是吓他们的!”

*/

public class FrameActivityextends Activity {

    // 开始动画按钮

private Button startButton;

    // 开始动画按钮的监听器

private OnClickListener startButtonListener = null;

    // 终止动画按钮

private Button stopButton;

    // 终止动画按钮的监听器

private OnClickListener stopButtonListener = null;

    // 旋转图像

private ImageView imageView = null;

    // 动画播放类

private AnimationDrawable anim;

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

       setContentView(R.layout.frame);

       setListener();

        findView();

    }

private void findView() {

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

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

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

       startButton.setOnClickListener(startButtonListener);

       stopButton.setOnClickListener(stopButtonListener);

    }

private void setListener() {

       startButtonListener = newOnClickListener() {

           @Override

public void onClick(View v) {

                // TODO Auto-generated method stub

               imageView.setBackgroundResource(R.anim.frame);

                // 获得初始化动画,开始动画

               anim = (AnimationDrawable)imageView.getBackground();

               anim.start();

            }

        };

       stopButtonListener = newOnClickListener() {

           @Override

public void onClick(View v) {

                // 停止动画

               anim.stop();

            }

        };

    }

}

该实例在frame .xml文件中定义了Frame动画,如下所示:

frame.xml代码清单16-2-2:

<!—张飞:想开心看二人转,想闹心就看一下足球,想往死了闹心就看一下中国足球。-->

<?xml version="1.0"encoding="UTF-8"?>

<animation-list android:oneshot="false"

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

<item android:duration="40" android:drawable="@drawable/dice_roll_00000" />

<item android:duration="60" android:drawable="@drawable/dice_roll_00001" />

<item android:duration="80" android:drawable="@drawable/dice_roll_00002"/>

<item android:duration="100" android:drawable="@drawable/dice_roll_00003"/>

<item android:duration="120" android:drawable="@drawable/dice_roll_00004"/>

<item android:duration="100" android:drawable="@drawable/dice_roll_00005"/>

<item android:duration="80" android:drawable="@drawable/dice_roll_00006"/>

<item android:duration="60" android:drawable="@drawable/dice_roll_00007"/>

<item android:duration="40" android:drawable="@drawable/dice_roll_00008"/>

<item android:duration="40" android:drawable="@drawable/dice_roll_00009"/>

<item android:duration="40" android:drawable="@drawable/dice_roll_00010"/>

<item android:duration="40" android:drawable="@drawable/dice_roll_00011"/>

<item android:duration="60" android:drawable="@drawable/dice_roll_00012"/>

<item android:duration="80" android:drawable="@drawable/dice_roll_00013"/>

<item android:duration="100" android:drawable="@drawable/dice_roll_00014"/>

<item android:duration="40" android:drawable="@drawable/dice_roll_00015"/>

<item android:duration="40" android:drawable="@drawable/dice_roll_00016"/>

</animation-list>

张飞:主公,启动Frame动画的start()方法不能在onCreate()方法中执行,因为onCreate()方法中的AnimationDrawable还没有完全与ImageView绑定。如果这时在onCreate()方法中调用start()方法启动动画,只能看到第一张图片。

 

 

 

 


AnimationDrawable是Frame动画的主要控制类,常用于获取、设置动画的属性增加、获取帧动画和控制动画显示。AnimationDrawable类的常用方法如下所示:

l  int getDuration():获取动画的时长。

l  int getNumberOfFrames():获取动画的帧数。

l  boolean isOneShot():是否仅播放一次。

l  Void setOneShot(boolean oneshot):设置oneshot属性。

l  void inflate(Resurce r,XmlPullParserp,AttributeSet attrs):增加、获取帧动画。

l  Drawable getFrame(int index):获取某帧的Drawable资源。

l  void addFrame(Drawable frame,intduration):为当前动画增加帧。

l  void start():开始动画。

l  boolean isRunning():当前动画是否在运行。

l  void stop():停止当前动画。

Frame动画没有提供监听动画播放事件的接口。我们可以开启一个新的线程监控动画的播放,不断的判断当前播放的帧是否是最后一帧,如下代码所示:

       private Handler mHandle = new Handler(){  

                     public voidhandleMessage(Message msg){

                         //获取当前帧的哈希值

                                int current= anim.getCurrent().hashCode();

                                if(anim.isRunning()&&(current==last)){ 

                             //停止动画播放

                                           stop();

                         }

                     }  

};

1.2.3.Property动画

Property动画系统是一个健壮的框架,它几乎可以把任何对象变成动画。Property动画就是在指定的时间长度上改变一个属性(对象中的一个成员字段)的值,实现动画。要让某些对象变成动画,就要不断改变对象的动画属性,如对象在屏幕上的位置、动画的停留时间以及动画之间的值等。比如在Property动画中,Button的缩放,其实是Button的位置与大小属性改变了。

Property动画定义的动画特性如下所示:

l  持续时间:指定动画的持续时间,默认长度是300毫秒。

l  时间插值:决定动画的变化频率。

l  重复次数和行为:指定在动画结束时是否重新播放动画,以及重复播放的次数。

l  动画集合:把动画组织到一个逻辑集合中,然后或者同时、或者顺序地、或者延迟地播放它们。

l  帧刷新延迟:能够指定动画帧的刷新频率,默认是每10秒刷新一次。应用程序刷新帧的速度依赖于系统的繁忙程度和系统提供的底层定时器的反应速度。

视图动画(Tween动画和Frame动画)只提供了让View对象具有动画效果的能力,因此想要非View对象具有动画效果,就得自己实现动画效果的代码。事实上,视图动画系统也受到了限制,它只会把View对象的部分特征暴露出来。例如,当View对象的缩放和旋转,无法同时更改View的背景色。视图动画的另一个缺点是,它仅能够在绘制View对象时被修改,没有修改实际的View对象本身。例如,如果要让一个按钮,以动画的形式穿越屏幕,按钮正确的绘制了,但是点击按钮的实际位置却不会改变,因此必须自己来实现这种逻辑。

Property动画系统能够让任何对象的任何属性具有动画效果(View对象和非View对象),并且能够修改对象自身属性。Property动画可以给想要动画效果的属性分配动画执行器,如颜色、位置、尺寸以及能够定义的动画特性(插值和多个动画的同步等)。但是,Property动画系统需要较多的创建时间和编写较多的代码。如果视图动画能够满足需求,或者既存的代码已经做了想要完成的动画效果,就不需要使用Property动画了。大家可以针对不同的情况来选择使用这两种不同的动画系统。

下面通过一个实例进一步讲解Property动画,实例实现了点击按钮播放小球掉下的动画,效果如图16-3所说:

图16-3 Property动画效果

动画真是个神奇的事情,这三个大铁球,一个是砸给大哥的,一个砸给二哥,还有一个砸给军师,他们都砸晕了,我好独自分享泡面!哈哈,看我实现的PropertyActivity代码:

PropertyActivity.java代码清单16-2-3:

/**

*@author张飞:军师找的月英真是纯爷们,可纯了!

*/

public class PropertyActivity extendsActivity {

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

       setContentView(R.layout.property);

        // 获取当前水平布局

       LinearLayout container = (LinearLayout)findViewById(R.id.container);

final MyAnimationView animView = new MyAnimationView(this);

        // 增加新View

       container.addView(animView);

        Buttonstarter = (Button)findViewById(R.id.startButton);

       starter.setOnClickListener(newView.OnClickListener() {

public void onClick(View v) {

                // 动画view开始动画

               animView.startAnimation();

            }

        });

    }

 /* ValueAnimator包含Property动画的所有核心功能,ValueAnimator类通过设定动画过程中的int、float或颜色值来指定动画播放期间的某些类型的动画值,在属性值更新时执行相应的操作。在ObjectAnimator(继承自ValueAnimator类)中会自动更新属性。在方法中会传递一个ValueAnimator参数,并通过ValueAnimator类的getAnimatedValue()取得当前动画属性值*/

public class MyAnimationView extendsView implementsValueAnimator.AnimatorUpdateListener {

        // 定义图形数组

public final ArrayList<ShapeHolder> balls = new ArrayList<ShapeHolder>();

        // 编排多个Animator时,某些动画的开始需要依赖于其他动画的开始或结束,这

// 时候就可以使用AnimatorSet来绑定这些Animator了

        AnimatorSetanimation = null;

private float mDensity;

public MyAnimationView(Context context) {

super(context);

            // 获取屏幕分辨率

           mDensity = getContext().getResources().getDisplayMetrics().density;

            // 在屏幕上初始化四个不同位置的球

           ShapeHolder ball0 = addBall(50f, 25f);

           ShapeHolder ball1 = addBall(150f, 25f);

           ShapeHolder ball2 = addBall(250f, 25f);

           ShapeHolder ball3 = addBall(350f, 25f);

        }

        // 创建动画

private void createAnimation() {

if (animation == null){

                //ObjectAnimator对象动画,设置x,y坐标和宽高

               ObjectAnimator anim1 = ObjectAnimator.ofFloat(balls.get(0),"y", 0f,

                       getHeight() - balls.get(0).getHeight()).setDuration(500);

                // 克隆一个相同的anim1

               ObjectAnimator anim2 = anim1.clone();

                // 设定的目标对象,其属性和目标图形数组ball1一样

               anim2.setTarget(balls.get(1));

               anim1.addUpdateListener(this);

                // 从图形数组中取出ball2

               ShapeHolder ball2 = balls.get(2);

               ObjectAnimator animDown = ObjectAnimator.ofFloat(ball2, "y",0f,

                       getHeight() - ball2.getHeight()).setDuration(500);

                //ObjectAnimator对象动画设置加速插值器

                animDown.setInterpolator(new AccelerateInterpolator());

               ObjectAnimator animUp = ObjectAnimator.ofFloat(ball2, "y",

                       getHeight() - ball2.getHeight(), 0f).setDuration(500);

                //ObjectAnimator对象动画设置减速插值器

               animUp.setInterpolator(newDecelerateInterpolator());

               AnimatorSet s1 = newAnimatorSet();

                //s1依次播放动画

               s1.playSequentially(animDown, animUp);

               animDown.addUpdateListener(this);

               animUp.addUpdateListener(this);

               AnimatorSet s2 = (AnimatorSet)s1.clone();

               s2.setTarget(balls.get(3));

               animation = newAnimatorSet();

                // 同时播放动画

               animation.playTogether(anim1, anim2, s1);

               animation.playSequentially(s1, s2);

            }

        }

private ShapeHolder addBall(float x, float y) {

            // 椭圆形

           OvalShape circle = newOvalShape();

            // 高度和宽度

           circle.resize(50f * mDensity, 50f * mDensity);

            // 初始化图形类

           ShapeDrawable drawable = newShapeDrawable(circle);

            // 初始自定义图形类

           ShapeHolder shapeHolder = newShapeHolder(drawable);

            // 设置x,y坐标

           shapeHolder.setX(x - 25f);

           shapeHolder.setY(y - 25f);

int red = (int)(100+ Math.random() * 155);

int green = (int)(100+ Math.random() * 155);

int blue = (int)(100+ Math.random() * 155);

            // 随机设置颜色

int color = 0xff000000 | red << 16 | green << 8| blue;

            // 设置画笔

            Paintpaint = drawable.getPaint(); // new

            //Paint(Paint.ANTI_ALIAS_FLAG);

int darkColor = 0xff000000 | red / 4 << 16 | green / 4<< 8 | blue / 4;

            // 设置渲染效果

           RadialGradient gradient = newRadialGradient(37.5f, 12.5f, 50f, color, darkColor,

                   Shader.TileMode.CLAMP);

           paint.setShader(gradient);

           shapeHolder.setPaint(paint);

           balls.add(shapeHolder);

return shapeHolder;

        }

        @Override

protected void onDraw(Canvas canvas) {

for (int i =0; i < balls.size(); ++i) {

                // 将图形添加到画笔上保存

               ShapeHolder shapeHolder = balls.get(i);

                canvas.save();

               canvas.translate(shapeHolder.getX(), shapeHolder.getY());

               shapeHolder.getShape().draw(canvas);

               canvas.restore();

            }

        }

publicvoid startAnimation() {

            // 创建动画

           createAnimation();

            // 动画开始

           animation.start();

        }

publicvoid onAnimationUpdate(ValueAnimator animation) {

            // 不断重绘

           invalidate();

        }

    }

}

我小飞飞将图形设置都封装在ShapeHolder这个类中,实现了代码的重复利用!哈哈,我太牛了。看我实现的ShapeHolder代码:

ShapeHolder.java代码清单16-2-3:

/**设置一个图形数据类型,包含图形的属性和画图形的一些设置*/

public class ShapeHolder {

//图形所在view的x,y坐标

private float x = 0, y = 0;

           //可画的对象类

private ShapeDrawable shape;

           //颜色属性

private int color;

           //环形渲染效果

private RadialGradient gradient;

           //透明度属性

private float alpha = 1f;

           //画笔

private Paint paint;

           //设置画笔

public void setPaint(Paint value) {

        paint =value;

    }

public Paint getPaint() {

return paint;

    }

           //设置X坐标

public void setX(floatvalue) {

        x = value;

    }

public float getX() {

return x;

    }

           //设置Y坐标

public void setY(floatvalue) {

        y = value;

    }

public float getY() {

return y;

    }

           //设置形状,例如圆形,长方形

public void setShape(ShapeDrawable value) {

        shape =value;

    }

public ShapeDrawable getShape() {

return shape;

    }

public int getColor() {

return color;

    }

           //设置画笔颜色

public void setColor(intvalue) {

       shape.getPaint().setColor(value);

        color =value;

    }

           //设置渲染效果

public void setGradient(RadialGradient value) {

        gradient =value;

    }

public RadialGradient getGradient() {

return gradient;

    }

           //设置透明度

public void setAlpha(floatalpha) {

this.alpha = alpha;

       shape.setAlpha((int)((alpha* 255f) + .5f));

    }

public float getWidth() {

return shape.getShape().getWidth();

    }

           //设置宽度

public void setWidth(floatwidth) {

        Shape s =shape.getShape();

       s.resize(width, s.getHeight());

    }

public float getHeight() {

return shape.getShape().getHeight();

    }

           //设置高度

public void setHeight(floatheight) {

        Shape s =shape.getShape();

       s.resize(s.getWidth(), height);

    }

public ShapeHolder(ShapeDrawable s) {

        shape = s;

    }

}

ValueAnimator对象保持着动画的时间轨迹,如动画的运行时间。ValueAnimator类封装了一个TimeInterpolator类,该类定义了动画的差值和一个TypeEvaluator类。TypeEvaluator类定义动画属性值的计算方式。要启动一个动画,就要创建一个ValueAnimator对象,并且要给该对象设置想要的动画属性的开始和结束值,以及动画的持续时间。在调用start()方法播放动画时,整个动画期间ValueAnimator对象都会根据动画的持续时间和已经执行的时间,在0和1之间,计算一个过去系数。该系数代表了动画已经完成的百分比,0意味着0%,1意味着100%。当ValueAnimator对象完成过去系数的计算时,它会调用当前设置的TimeInterpolator对象计算一个差值系数。差值系数(interpolated fraction)把过去系数映射到一个新的设置时间差值的系数。在计算差值系数时,ValueAnimator对象会调用相应的TypeEvaluator对象,基于差值系数、动画的开始值、结束值来计算动画的属性值。

 

Animator

在android.animation包中能够找到大多数Property动画系统的API。Property动画系统也能够使用视图动画系统在android.view.animation包中定义的插值。Animator类提供了创建动画的基本架构,它只提供了基本功能,因此要完全的支持动画值就必须扩展这个类,下表16-1列出了Animator的子类:

表16-1 Animators的子类

说明

ValueAnimator

用于计算处理动画属性值的主要属性动画时序引擎。它有所有的计算动画值的核心功能,并包含了每个动画的时序细节、动画是否重复的信息、监听接收更新事件和设置评估定制类型的能力。有两类动画属性:1.计算动画处理的值;2.把这些值设置到要进行动画处理的对象和属性上。ValueAnimator类不执行第二类属性,因此必须通过ValueAnimator对象来监听被计算值的变化,并且要自己修改动画的逻辑。

ObjectAnimator

ValueAnimator类的一个子类,它允许给目标对象和对象属性设置动画。这个类在计算新的动画值时,会更新属性的坐标。ObjectAnimator类更常用,因为它使得动画值的处理更加简便。但有时也会直接使用ValueAnimator类,因为ObjectAnimator类有更多的限制,如在目标对象上需要指定用于呈现的acessor方法。

AnimatorSet

提供了一种把动画组织到一起的机制,以便它们能够彼此相互关联的运行。AnimatorSet能够设置动画同时播放、顺序播放、或者在指定的延时之后播放。

 

Evaluators评价器

Evaluators评价器会告诉Property动画系统如何计算给定属性的值。Evaluators评价器利用Animator类提供时序数据:动画的开始和结束值,以及基于这些数据计算得来的Property动画值。Property动画系统提供的评价器如表16-2所示:

表16-2 Evaluators评价器

Class/Interface

说明

IntEvaluator

默认的用于评价int类型属性计算值的评价器。

FloatEvaluator

默认的用于评价float类型属性计算值的评价器。

ArgbEvaluator

默认的用于评价颜色属性计算值的评价器,颜色属性值用十六进制表示。

TypeEvaluator

允许创建自定义评价器的接口。如果要让一个非int、float、颜色类型的属性具有动画效果,就必须实现这个TypeEvaluator接口,用它来指定如何计算对象属性动画值。

 

Interpolator时间差值器

Interpolator时间差值器给动画中的时间方法定义了一个用于计算的具体值。例如,一个线性过渡的动画可以通过时间差值器实现动画播放时均匀的移动,或者加速开始,减速结束。本章在Tween动画中已经介绍了插值器的概念。

 

动画监听器

Property动画同样有监听事件,在动画播放期间监听器能够监听的重要事件如下所示:

l  Animator.AnimatorListener

n  onAnimationStart():动画开始的时候被调用。

n  onAnimationEnd():动画结束的时候被调用,它不管动画是如何结束的。

n  onAnimationRepeate():动画重复播放的时候被调用。

n  onAnimationCancel():动画被取消播放的时候被调用。

如果不实现Animator.AnimatorListener接口的所有方法,需要继承AnimatorListenerAdapter类来代替对Animator.AnimatorListener接口的实现。AnimatorListenerAdapter类对这些方法提供了空的实现,可以选择性的重写这些方法。

l  ValueAnimator.AnimatorUpdateListener

onAnimationUpdate()方法是ValueAnimator.AnimatorUpdateListener接口的回调方法,如果使用ValueAnimator类,那么必须实现这个监听器。通过监听属性更新事件可以获得当前的动画值。

1.3. Activity切换动画

Activity的切换动画指的是从一个Activity跳转到另外一个Activity时的动画。它包括两个部分:一部分是第一个Activity退出时的动画; 另外一部分是第二个Activity进入时的动画。在Android 2.0版本之后,通过 overridePendingTransition()方法可以实现切换动画。该方法有两个参数,第一个参数是第二个Activity进入时的动画,第二个参数是第一个Activity退出时的动画。 有两点需要注意: overridePendingTransition()方法必需紧挨着startActivity()或者finish()方法后调用;它只适用于Android 2.0及以上版本。下面通过一个实例来讲解Activity切换动画,运行程序,结果如图16-4所示:

图16-4 Activity切换动画效果

实例的效果是从Activity_1退出时播放伸缩淡出效果动画,启动Activity_2时播放伸缩效果动画。而从Activity_2跳转到Activity_1时,播放Android SDK自带的淡入淡出动画。

新建一个Activity,命名为ActActivity_1,用于实现Activity退出时伸缩淡出的动画效果,具体代码如下所示:

ActActivity_1.java代码清单16-3-0:

/**

*@author张飞:军师!爱情,是我们都相信的谎言!           

*/

public class ActActivity_1 extendsActivity {

private Button button;

private OnClickListener buttonListener = null;

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.act_1);

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

        buttonListener = new OnClickListener() {

            @Override

public void onClick(View v) {

                Intent i = new Intent(ActActivity_1.this, ActActivity_2.class);

                startActivity(i);

                /**

                 * 在startActivity(Intent)或finish()方法调用后,会立即用一个指定的描述动画的xml文件来执行

* overridePendingTransition(R.anim.zoom_enter, 0)参数讲解

* 第一个参数:一个动画资源,用于目标Activity 进入屏幕时的动画,此处写0代表无动画

* 第二个参数:一个动画资源,用于当前Activity 退出屏幕时的动画,此处写0代表无动画

*/

               overridePendingTransition(R.anim.zoom_enter,R.anim.zoom_exit);

                /**

                 * 列出了几个Android自带的动画效果,可以通过替换overridePendingTransition方法的参数实现这些动画效果

                 * 实现淡入淡出的效果

                 *overridePendingTransition(android.R.anim.fade_in,android

                 * .R.anim.fade_out);

                 *由左向右滑入的效果

                 * overridePendingTransition

                 *(android.R.anim.slide_in_left,android

                 * .R.anim.slide_out_right);

                 */

            }

        };

       button.setOnClickListener(buttonListener);

    }

}

ActActivity_1启动时对应的布局文件zoom_enter.xml如下所示:

zoom_enter.xml代码清单16-4:

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

<!-- 实现zoom_enter和zoom_exit,即类似iphone的进入和退出时的效果 -->

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

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

<scale

android:fromXScale="2.0"

android:toXScale="1.0"

      android:fromYScale="2.0"

android:toYScale="1.0"

      android:pivotX="50%p"

android:pivotY="50%p"

      android:duration="1000" />

</set>

ActActivity_1退出时对应的布局文件zoom_exit.xml如下所示:

zoom_exit.xml代码清单16-3-0:

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

<!-- 实现zoom_enter和zoom_exit,即类似iphone的进入和退出时的效果 -->

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

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

        android:zAdjustment="top">

<scale

android:fromXScale="1.0"

       android:toXScale=".5"

android:fromYScale="1.0"

android:toYScale=".5"

android:pivotX="50%p"

android:pivotY="50%p"

android:duration="3000"/>

<alpha

android:fromAlpha="1.0"

       android:toAlpha="0"

android:duration="1000"/>

</set>

新建另一个Activity,命名为ActActivity_2,用于实现Activity启动时伸缩的动画效果,具体代码如下所示:

ActActivity_2.java代码清单16-3-0:

/**

*@author孔明:令人不能自拔的,除了牙齿还有爱情。

*/

public class ActActivity_2 extendsActivity {

private Button button;

private OnClickListener buttonListener = null;

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.act_2);

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

        buttonListener = new OnClickListener() {

            @Override

public void onClick(View v) {

                Intent i = new Intent(ActActivity_2.this, ActActivity_1.class);

                startActivity(i);

               overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);

            }

        };

       button.setOnClickListener(buttonListener);

    }

}

在Android 2.0版本以下没有overridePendingTransition方法,可以通过Tween动画类实现Activity切换动画的效果。在第一个Activity退出时,启动顶层容器的动画效果,在动画效果结束时,关闭第一个Activity,启动第二个Activity,在第二个Activity启动时,加载顶层容器的动画效果,这样间接的实现了Activity切换动画的效果。关键代码如下所示:

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

   findViewById(R.id.body).startAnimation(anim);

    对应的xml文件的部分代码如下所示:

<LinearLayout android:id="@+id/body"

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

    android:layout_width="fill_parent"

 android:layout_height="fill_parent"

    android:orientation="vertical">

通过设置应用程序的Style可以使得所有的Activity切换时都播放相同的动画效果。首先定义动画效果xml,然后设置Activity的Style为将播放的动画样式,最后将样式添加到AndroidManifest.xml中即可。Style的xml文件style.xml代码如下:

<stylename="Animation.Activity">

<item name="activityOpenEnterAnimation">@anim/activity_open_enter</item>

<item name="activityOpenExitAnimation">@anim/activity_open_exit</item>

<item name="activityCloseEnterAnimation">@anim/activity_close_enter</item>

<item name="activityCloseExitAnimation">@anim/activity_close_exit</item>

</style>

在AndroidManifest.xml中添加Activity的样式为MyTheme,代码如下所示:

<style name="MyTheme">

<item name="android:windowAnimationStyle">@style/Animation.Activity</item>

</style>

Activity的Style,不仅可以设置Activity的切换动画,还可以设置很多其他属性,下面列出最常用的Style属性:

l  android:theme="@android:style/Theme.Dialog":将一个Activity显示为能话框模式。

l  android:theme="@android:style/Theme.NoTitleBar":不显示应用程序标题栏。

l  android:theme="@android:style/Theme.NoTitleBar.Fullscreen":不显示应用程序标题栏,并全屏。

l  android:theme="Theme.Light":背景为白色。

l  android:theme="Theme.Light.NoTitleBar":白色背景并无标题栏。

l  android:theme="Theme.Light.NoTitleBar.Fullscreen":白色背景,无标题栏,全屏。

l  android:theme="Theme.Black":黑色背景。

l  android:theme="Theme.Black.NoTitleBar":黑色背景并无标题栏。

l  android:theme="Theme.Black.NoTitleBar.Fullscreen":黑色背景,无标题栏,全屏。

l  android:theme="Theme.Wallpaper":将系统桌面设置为应用程序背景。

l  android:theme="Theme.Wallpaper.NoTitleBar":将系统桌面设置为应用程序背景,且无标题栏。

l  android:theme="Theme.Wallpaper.NoTitleBar.Fullscreen":将系统桌面设置为应用程序背景,无标题栏,全屏。

l  android:theme="Translucent":半透明效果。

l  android:theme="Theme.Translucent.NoTitleBar":半透明,无标题栏。 android:theme="Theme.Translucent.NoTitleBar.Fullscreen":半透明,无标题栏,全屏。

l  android:theme="Theme.Panel":默认黑暗的主题面板窗口。

l  android:theme="Theme.Light.Panel":默认光亮的主题面板窗口。

1.4. Gif动画

有时,我们需要利用Gif图片展现一些简单的动画,但Android SDK不支持直接使用Gif 图片。下面介绍三种播放Gif图片的方法。第一种方法,使用Movie类播放Gif文件;第二种方法,使用第三方开源GifView包播放Gif文件;第三种方法,通过利用其他软件,将Gif 图片分解为一帧一帧的图片,再通过AnimationDrawable或View播放动画。下面通过一个实例讲解上述三种方法,运行程序,结果如图16-5所示:

图16-5 Gif动画效果

一口气实现了三个动画,真刺激!我小飞飞第一张Gif图使用Movie类实现,第二张Gif图使用第三方开发包实现,第三张Gif图,我先给它分解再实现动画。

新建一个Activity,命名为GifActivity,代码如下所示:

GifActivity.java代码清单16-4-0:

/**

 *@author张飞:我的妈啊,月英和军师成了?太刺激了!

*/

public class GifActivity extendsActivity {

    // 利用movie类播放Gif按钮

private Button movieButton;

    // 利用movie类播放Gif按钮的监听器

private OnClickListener movieButtonListener = null;

    // 利用GifView第三方类按钮

private Button gifviewButton;

    // 利用GifView第三方类按钮的监听器

private OnClickListener gifviewButtonListener = null;

    // 利用Gif图片散开结合AnimationDrawable播放Gif按钮

private Button resolveButton;

    // 利用Gif图片散开结合AnimationDrawable播放Gif按钮的监听器

private OnClickListener resolveButtonListener = null;

    // 第三方GifView类

private GifView gifview;

private MovieGifView movieGifView = null;

private ImageView imageview = null;

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.gif);

        setListener();

        findView();

    }

private void findView() {

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

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

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

       movieButton.setOnClickListener(movieButtonListener);

       gifviewButton.setOnClickListener(gifviewButtonListener);

       resolveButton.setOnClickListener(resolveButtonListener);

        gifview = (GifView)findViewById(R.id.gifview);

        // 设置Gif的类型

        gifview.setGifImageType(GifImageType.COVER);

        // 设置Gif动画显示的高度和宽度

        gifview.setShowDimension(300, 300);

        // 设置Gif图片

        gifview.setGifImage(R.drawable.lubu);

        // gifview.setOnClickListener(this);

    }

private boolean isShow = false;

private void setListener() {

        /**

         * movie类播放Gif动画

         */

        movieButtonListener = newOnClickListener() {

            @Override

public void onClick(View v) {

                movieGifView = new MovieGifView(GifActivity.this);

               movieGifView.setBackgroundGifView(R.drawable.yazi);

                movieGifView.setGifStart();

                //以下注释代码可以实现监听Gif图片播放结束事件

                //movieGifView.setOnGifAnimationEnd(new

                //OnGifOnAnimationEndListener() {

                // @Override

                // public voidonGifAnimationEnd() {

                // // TODO Auto-generated method stub

                //Toast.makeText(GifActivity.this, "动画结束监听", 100).show();

                //movieGifView.setAlive(false);

                // }

                // });

                setContentView(movieGifView);

            }

        };

        /**

         * 第三方包播放Gif动画

         */

        gifviewButtonListener = newOnClickListener() {

            @Override

public void onClick(View v) {

if (isShow) {

                // 显示Gif动画的第一帧的图像,也就是不让Gif动起来

                gifview.showCover();

                isShow = false;

            } else {

                 // 开始动画

                 gifview.showAnimation();

                 isShow = true;

            }

        }

    };

    /**

     *AnimationDrawable播放Gif动画

     */

    resolveButtonListener = newOnClickListener() {

        @Override

public void onClick(View v) {

            imageview = new ImageView(GifActivity.this);

            AnimationDrawable anim;

           imageview.setBackgroundResource(R.anim.frame_gif);

            anim =(AnimationDrawable)imageview.getBackground();

            setContentView(imageview);

            anim.start();

        }

    };

}

1.4.1.Movie播放Gif动画

Movie类可以实现对Gif动画中的多个帧的管理,该类通过对Gif文件输入流的读取和解码,在onDraw()方法中不断地绘制每一帧图片,利用setTime()根据时间顺序播放帧图片,最后实现动起来的效果。但是使用这个方式播放Gif 格式的动画时偶尔会出现花屏,这是因为 Android 中使用的 libgif 库版本较低,不支持新的Gif特性。

上一节实例为了方便Movie类实现动画,将Movie封装为一个View类,命名为MovieGifView,可以直接在xml文件里直接调用。MovieGifView的代码如下所示:

/**

 *@author关羽:啊!成了?太刺激了!

*/

public class MovieGifView extends View {

    // Movie类,用于播放动画

private Movie mMovie;

private Context mContext;

    // 动画当前播放时间

private int currentime = 0;

    // 动画每次变化的时间

private int increTime = 10;

private int dur = 0;

    // 是否在播放动画

private boolean isGifRun = false;

private int x;

private int y;

private boolean isAlive = true;

    /**初始化Gif类*/

public MovieGifView(Context context) {

super(context);

        setFocusable(true);

        mContext = context;

    }

    /**获取到Gif动画当前播放时间*/

public int getCurrentime() {

return currentime;

    }

    /**GifView的横坐标*/

public int getX() {

return x;

    }

public boolean isAlive() {

return isAlive;

    }

public void setAlive(boolean isAlive){

this.isAlive =isAlive;

    }

    /**设置GifView的横坐标*/

public void setX(int x) {

this.x = x;

    }

    /**获取GifView的纵坐标*/

public int getY() {

return y;

    }

    /**设置GifView的纵坐标*/

public void setY(int y) {

this.y = y;

    }

    /**设置GifView播放的速度*/

public void setGifspeed(int increTime){

this.increTime =increTime;

    }

    /**设置GifView动画启动*/

public void setGifStart() {

        isGifRun = true;

    }

    /**设置GifView动画停止*/

public void setGifStop() {

        isGifRun = false;

    }

    /**判断动画是否还在播放*/

public boolean isGifRun() {

return isGifRun;

    }

    /**设置GifView背景为Gif动画*/

public void setBackgroundGifView(intresid) {

        InputStream is;

        is =mContext.getResources().openRawResource(resid);

        mMovie = Movie.decodeStream(is);

    }

    /**获取到Gif动画的周期时间*/

public int getDuration() {

if (mMovie != null) {

            dur = mMovie.duration();

if (dur == 0){

                dur = 0;

            }

        }

return dur;

    }

    /**Gif动画结束判断*/

private void setGifAnimationEnd() {

if (currentime>= this.getDuration()) {

           mOnGifOnAnimationEndListener.onGifAnimationEnd();

            currentime = dur;

            isGifRun = false;

        }

    }

    /**设置Gif结束监听接口*/

public interface OnGifOnAnimationEndListener {

publicvoid onGifAnimationEnd();

}

private OnGifOnAnimationEndListener mOnGifOnAnimationEndListener = null;

    /**设置Gif结束监听*/

public voidsetOnGifAnimationEnd(OnGifOnAnimationEndListener mOnGifOnAnimationEndListener){

this.mOnGifOnAnimationEndListener= mOnGifOnAnimationEndListener;

    }

    @Override

protected void onDraw(Canvas canvas) {

        // 设置gif播放

if (isGifRun){

if (mMovie != null) {

                // 获得动画持续时间

                dur = mMovie.duration();

                // 如果动画不存在,设置一个初始值

if (dur == 0){

                    dur = 1000;

                }

                // 设置当前播放的帧位置

                mMovie.setTime(currentime);

                // 在view的x,y位置画上画布内容

                mMovie.draw(canvas, x, y);

                // 设置新的帧位置

                currentime = currentime +increTime;

                // 设置动画结束监听类

if(mOnGifOnAnimationEndListener != null)

                    setGifAnimationEnd();

            }

        } else {

            mMovie.setTime(currentime);

            mMovie.draw(canvas, x, y);

        }

        // 不断重绘view

if (isAlive)

        invalidate();

               }

}

}

1.4.2.第三方包GifView

GifView是一个为了解决Android中不能直接显示Gif图片而开发的组件,其用法和ImageView相似。GifView.jar包共有四个类:GifAction.java 观察者类,用于监视Gif是否加载成功;GifFrame.java帧类,包含三个成员:当前图片、延时、下张Frame的链接;GifDecoder.java解码线程类;GifView.java主类,包括常用方法,如GifView的构造方法、设置图片源、延迟、绘制等。

使用GifView包,首先需要将GifView.jar添加至项目,同时可以通过xml文件和Java代码设置GifView属性。在xml中配置GifView的基本属性,GifView继承自View类,和Button、ImageView一样是一个UI控件。常见的GifView xml设置如下所示:

<com.ant.liao.GifView

           android:id="@+id/gifview"

           android:layout_height="300dip"

android:layout_width="fill_parent"/>

 

也可以在代码中配置GifView的常用属性,如下所示:

           // 从xml中得到GifView的句柄

    gifview = (GifView)findViewById(R.id.gifview);

    // 设置Gif图片源

    gifview.setGifImage(R.drawable.lubu);

    // 添加监听器

    gifview.setOnClickListener(this);

    // 设置显示的大小、拉伸或者压缩

    gifview.setShowDimension(300, 300);

    // 设置加载方式:先加载后显示、边加载边显示、只显示第一帧再显示

    gifview.setGifImageType(GifImageType.COVER);

1.4.3.分解Gif

由16.2.2节可以知道,AnimationDrawable类支持逐帧播放,Gif图片其实也是在一帧一帧的播放动画。可以利用一些软件将Gif图片转为多个帧图片,再利用AnimationDrawable类实现动画播放。图16-6显示了Gif图片的帧分解。

图16-6 Gif分解软件示意图

1.5. SurfaceView绘画动画

View 类是Android视图组件的一个超类,主要用于显示视图、内置画布、提供图形绘制方法。View类必须在UI主线程内更新画面,速度较慢。当需要绘制复杂的图像或者对程序的执行效率要求比较高时,View类一般不能满足需求,这时就需要用到SurfaceView类。

1.5.1.SurfaceView

SurfaceView类继承View类,内嵌了一个专门用于绘制的Surface。SurfaceView使用了双缓冲机制,在主线程之外的线程中向屏幕绘图,避免了画图任务繁重时造成主线程阻塞,从而提高了程序的反应速度,更适合2D游戏的开发。例如,在Android游戏开发中的背景、人物、动画等多用到SurfaceView。

上面一段提到Android系统中一个重要的概念Surface。简单地说Surface对应了一块屏幕缓冲区,每个窗口对应一个Surface,任何View都是画在Surface上的,传统的View共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行。

张飞:Surface是纵深排序的,这说明它总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个可见区域内的Surface的部分内容才可见,可见区域外部部分不可见。Surface的排版显示受到视图层级关系的影响,它的兄弟节点会在顶端显示。这意味者Surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物,例如:文本、按钮等控件。但是,当Surface上有透明控件时,每次变化都会引起框架重新计算Surface和顶层控件的透明效果,这会影响性能。

 

 

 

 

 

 

 


1.5.2.SurfaceView和View的区别

SurfaceView和View的最本质的区别在于,SurfaceView在一个单独线程中重新绘制画面,而View必须在UI的主线程中更新画面。在UI的主线程中更新画面,可能会引发问题,比如更新画面的时间过长,主UI线程会被阻塞,无法响应按键、触摸等消息。当使用SurfaceView时,由于是在新的线程中更新画面所以不会阻塞UI主线程,但这会产生事件同步的问题。例如,当发生了触屏事件时,就需要SurfaceView中线程处理该事件,一般来说,会设计一个队列来保存这些触屏事件。

在开发应用程序中,画面一般分为两类:被动更新画面和主动更新动画。被动更新画面:画面的更新依赖于onTouch()方法,可以直接使用invalidate()方法进行刷新。在这种情况下,这一次Touch和下一次Touch需要的时间比较长,不会产生影响,例如棋类游戏,使用View较好;主动更新动画:需要一个单独的线程不停地重绘画面,避免阻塞主UI线程,例如一个人在一直跑动,此时SurfaceView较好。

 

 

 

张飞:SurfaceView提供了两个线程:UI线程和渲染线程。应该注意的是:所有的SurfaceView和SurfaceHolder.Callback的方法都应该在应用程序的UI主线程里调用;渲染线程所要访问的各种变量应该进行同步处理;由于Surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和SurfaceHoledr.Callback.surfaceDestroyed()方法之间有效,所以要确保渲染线程访问的是合法有效地Surface。

 

 

 

 

 

 


1.5.3. SurfaceView实例

下面通过一个实例说明如何使用SurfaceView编写动画。实例通过SurfaceView在屏幕上不断地改变圆的位置,形成圆运动的动画。运行程序,结果如图16-8所示:

图16-8 SurfaceView动画示意图

大哥,二哥,军师发现泡面被我吃光了,他们罚我自己用大铁球砸自己,好惨呀!但我没告诉他们我用SurfaceView做的铁球是轻的,嘿嘿。

新建一个Activity,命名为SurfaceViewActivity,代码如下所示:

SurfaceViewActivity.java代码清单16-5-3:

/**

*@author张飞:有人模仿我的脸,有人模仿我的面!

*/

public class SurfaceViewActivity extends Activity {

    @Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// 指定界面

        setContentView(new MySurfaceView(this));

    }

}

新建一个SurfaceView类,命名为MySurfaceView,该类实现了画圆的动画,代码如下所示:

MySurfaceView.java代码清单16-5-3:

/**

*继承SurfaceView类,实现画图

* @author张飞:军师,你们看不起我的人格,还看不起我的智商?

 */

public class MySurfaceView extendsSurfaceView implements Runnable,Callback {

    // 获得操作句柄

private SurfaceHolder surfaceHolder;

    // 设置宽度和高度

private int width, height;

    // 是否退出

private boolean bExit;

    // 画笔

private Paint paint;

    // 圆形类

private RectF rect;

public MySurfaceView(Context context) {

super(context);

        // 初始化句柄,矩形类,画笔

        surfaceHolder = this.getHolder();

        surfaceHolder.addCallback(this);

        rect = new RectF(150, 0, 200, 200);

        paint = new Paint();

        // 抗锯齿方法

        paint.setAntiAlias(true);

        paint.setColor(Color.WHITE);

        // 请将屏幕一直保持为开启状态,以便校准

this.setKeepScreenOn(true);

        bExit = false;

    }

private void draw() {

        // 获得画笔和设置画笔

        Canvas canvas =surfaceHolder.lockCanvas();

        // 设置画笔背景颜色

        canvas.drawColor(Color.BLACK);

        // 设置矩形的高度不断变化

        rect.top += 30;

if (rect.top> height)

            rect.top = 0;

        rect.bottom = rect.top + 50;

        canvas.drawOval(rect, paint);

        // 解锁画布,提交画好的图像

       surfaceHolder.unlockCanvasAndPost(canvas);

    }

    @Override

public void surfaceChanged(SurfaceHolder holder, int format, intwidth, int height) {

    }

    @Override

public void surfaceCreated(SurfaceHolder holder) {

        // 获得view的宽度和高度

        width = this.getWidth();

        height = this.getHeight();

new Thread(this).start();

    }

    @Override

public void surfaceDestroyed(SurfaceHolder holder) {

    }

    @Override

public void run() {

while (!bExit) {

            // 不断改变矩形的高度位置

            draw();

try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

从上述代码中可以看出使用SurfaceView画图时,首先需要继承SurfaceView类,实现SurfaceHolder.Callback接口,添加回调函数SurfaceHolder.addCallback(callback),然后获得Canvas对象并调用SurfaceHolder.lockCanvas()方法锁定画布,在Canvas上进行绘画,最后调用SurfaceHolder.unlockCanvasAndPost(Canvascanvas)方法解锁画布,提交画布内容。在使用SurfaceView时,所有的绘图工作必须得在Surface被创建之后才能开始,在Surface被销毁之前必须结束,所以CallBack中的surfaceCreated()和surfaceDestroyed()方法就成了绘图代码的边界。

SurfaceHolder.Callback接口的详细说明:

l  public voidsufaceChanged(SurfaceHolder holder,int format,int width,int height):Surface的大小发生改变时调用。

l  public voidsurfaceCreated(SurfaceHolder holder):Surface创建时调用,一般在这里调用画面的线程。

l  public voidsurfaceDestroyed(SurfaceHolder holder):Surface销毁时调用,一般在这里将画面的线程停止、释放。

l  public void addCallback:给SurfaceView添加一个回调函数。

l  public void lockCanvas:锁定Canvas,绘图之前必须锁定Canvas才能够获得Canvas对象。

l  public void unlockCanvasAndPost:开始绘制时锁定Canvas,绘制完成后解锁Canvas。

l  public void removeCallback:从SurfaceView中移除回调函数。

上面的SurfaceHolder是Surface的控制器,用来操纵Surface,处理Canvas上绘画的效果,控制表面、大小、像素等。访问SurfaceView的底层图形是通过SurfaceHolder接口来实现的,可以通过getHolder()方法获得 SurfaceHolder对象。

SurfaceView类用于显示,SurfaceHolder类用于管理SurfaceView类。SurfaceHolder通过SurfceHolder.addCallback()方法添加一个SurfaceHolder接口,其内部接口的三个抽象方法用于管理和监听SurfaceView,达到了管理SurfaceView的目的。SurfaceHolder的三个抽象方法如下所示:

l  abstract voidaddCallback(SurfaceHolder.Callbask callback):给SurfaceView当前的持有者返回一个回调函数。

l  abstract Canvas lockCanvas():在锁定Canvas后就可以通过其返回的画布对象操纵Canvas。

l  abstract Canvas lockCanvas(Rect dirty):锁定画布的某个区域进行画图,画完图后,会调用unlockCanvasAndPost()方法改变显示的内容。

l  abstract voidunlockCanvasAndPost(Canvas canvas):结束锁定画图,并提交改变。

1.6. 玄德有话说

张飞:我想程序启动就执行动画效果,怎么用AnimationDrawable写在onCreate里面不行呢? 

刘备:傻飞飞,因为控件及其附属动画要先初始化完后才能播放。Activity在执行onCreate函数时候,自己还没有初始化,怎么能初始化动画呢,如果想程序启动就执行动画,可以使用定时器或者新线程,在完成onCreate函数后500ms后执行。

 

张飞:使用Frame动画,动画帧太多,时不时会出现内存堆栈溢出的问题,怎么办?

刘备:可以用借鉴双缓冲的思想,一个线程不断读取图片缓存添加到存储空间队尾,一个线程不断为帧动画提供帧,读一帧显示完就remove当前帧,这样防止了一次性读取所以图片导致的溢出情况。

 

张飞:我想在程序中实现白天和夜晚不同风格的效果,怎么办?

刘备:可以style定义两种或者多种界面风格,触发事件后,切换Activity显示风格,即可。

 

张飞:为什么我的动画没有效果,代码很正确呀!

刘备:你个呆子,是不是把手机设置为不准播放动画了。

 

张飞:什么是双缓冲?

刘备:双缓冲是为了防止动画闪烁而实现的一种多线程应用,基于SurfaceView的双缓冲实现很简单,就是通过一个holder控制Activity,Activity一边与用户交互,一边用holder画画。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值