Android Animation★★★★

1.Android动画

Android系统的动画主要分为三大类:

①View Animation 视图动画:在古老的Android版本系统中就已经提供了,只能被用来设置View的动画。

②Drawable Animation (也叫Frame动画、帧动画):专门用来一个一个的显示Drawable的resources,就像放幻灯片一样。

③Property Animation 属性动画:只对Android 3.0(API 11)以上版本的Android系统才有效,这种动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。这种动画是可扩展的,可以自定义任何类型和属性的动画。

2.View Animation(视图动画)

视图动画,也叫Tween(补间)动画,它的作用对象只能是View,在同一个视图容器内执行一系列简单变换(位置、大小、旋转、透明度)。比如,一个TextView对象,您可以移动、旋转、缩放、透明度设置其文本,当然,如果它有一个背景图像,背景图像会随着文本变化。

视图动画可以通过XML定义,也可以通过Java代码动态设置。建议使用XML文件定义,因为它可读性好、而且可重用。

视图动画支持四种动画效果:

①AlphaAnimation

透明度动画,改变视图整体透明度,透明度值由1~0,从可见到不可见的变化。

1)在XML实现:

在res/anim/目录下新建alpha_demo.xml文件。

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

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

    android:fromAlpha="0.0"

    android:toAlpha="1.0"

    android:duration="2000">

</alpha>

然后使用AnimationUtils类的静态方法loadAnimation()来加载XML文件,得到一个Animation对象,如下:

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

mImage.startAnimation(animation);

2)java代码实现:

需要用到Animation的子类AlphaAnimation来实现,代码如下:

AlphaAnimation alphaAnimation = new AlphaAnimation(0f, 1f); 

alphaAnimation.setDuration(2000);

mImage.startAnimation(alphaAnimation);

②ScaleAnimation

缩放动画,需要一个坐标点(轴点)实现,不同的轴点的缩放效果不同,因此需要先指定pivotX、pivotY确定轴点坐标。默认情况下,从对象view的左上角开始缩放,注意,不是父控件。

1)在xml实现:

该动画是通过标签<scale/>实现的:

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

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

       android:duration="3000"

       android:fromXScale="0.0"

       android:fromYScale="0.0"

       android:pivotX="50%"

       android:pivotY="50%"

       android:toXScale="1.0"

       android:toYScale="1.0">

</scale>

fromXScale:动画开始时,水平方向缩放系数float型值

fromYScale:动画开始时,垂直方向缩放系数float型值

toXScale:动画结束时,水平方向缩放系数float型值

toYScale:动画结束时,垂直方向缩放系数float型值

pivotX:缩放轴点的X坐标(其值可以为:数值、百分数、百分数p),例如:50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)

android:pivotY:缩放轴点的Y坐标,规律同pivotX。

定义好xml以后,在java中加载并开始该动画:

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

mImage.startAnimation(animation);

2)java代码实现:

使用Animation的子类ScaleAnimation来实现:

ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);

scaleAnimation.setDuration(3000);

mImage.startAnimation(scaleAnimation);

当pivotXType=Animation.ABSOLUTE时,pivotXValue的值代表绝对像素,与xml中android:pivotX为数值时对应;

当pivotXType=Animation.RELATIVE_TO_SELF时,pivotXValue的值代表相对于当前View定位,与xml中android:pivotX为百分数时对应;

当pivotXType=Animation.RELATIVE_TO_PARENT时,pivotXValue的值代表相对于当前View的父控件定位,与xml中android:pivotX为百分数p时对应。

③TranslateAnimation

平移动画,实现视图垂直/水平方向位移变化,指定开始的位置,和结束的位置即可。

1)xml实现:

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

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

           android:fillAfter="true"

           android:duration="3000"

           android:fromXDelta="50%"

           android:fromYDelta="50%"

           android:toXDelta="50%p"

           android:toYDelta="50%p">

</translate>

fromXDelta:平移开始X方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotX类似

fromYDelta:平移开始Y方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotY类似

toXDelta:平移结束X方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotX类似

toYDelta:平移结束Y方向坐标(其值可以为:数值、百分数、百分数p,且多可以为正负),其值含义与android:pivotY类似

定义好xml后,在java中加载并开始该动画:

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

mImage.startAnimation(animation);

2)java代码实现:

使用Animation的子类TranslateAnimation实现:

TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_PARENT,0,Animation.RELATIVE_TO_PARENT,0.5f);

translateAnimation.setDuration(3000);

translateAnimation.setFillAfter(true);       

mImage.startAnimation(translateAnimation);

④RotateAnimation

旋转动画,也需要一个轴点来实现旋转。默认情况下,从对象view的左上角开始旋转,也就是相对于当前view坐标为(0,0)位置。

1)xml实现:

该动画是通过标签</rotate>实现的:

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

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

        android:duration="3000"

        android:pivotX="50%"

        android:pivotY="50%"

        android:fromDegrees="0"

        android:toDegrees="90">

</rotate>

pivotX:旋转轴点的X坐标(其值可以为:数值、百分数、百分数p)

pivotY:旋转轴点的Y坐标

fromDegrees:旋转开始的角度,其值可以为正负

toDegrees:旋转结束的角度,其值可以为正负

ps:toDegrees -fromDegrees > 0,则顺时针旋转;否则,逆时针旋转。

定义好xml文件后,在java中加载并开始该动画:

Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.rotate);

mImage.startAnimation(animation);

2)java代码实现:

需要使用Animation的子类RotateAnimation实现:

 RotateAnimation rotateAnimation = new RotateAnimation(0,180);

rotateAnimation.setDuration(3000);

mImage.startAnimation(rotateAnimation);

这四种动画的共同属性:

duration: 动画持续时间,以毫秒为单位

fillAfter: 如果设置为true,控件动画结束时,将保持动画最后时的状态

fillBefore: 如果设置为true,控件动画结束时,还原到开始动画前的状态

fillEnabled: 与fillBefore 效果相同,都是在动画结束时,将控件还原到初始化状态

repeatCount: 重复次数,默认是0

repeatMode: 重复类型,有reverse和restart两个值,reverse表示倒序回放,restart表示重新放一遍,必须与repeatCount一起使用才能看到效果。

interpolator: 插值器,设置动画的变化速率,其实就是指定的动作效果,比如弹跳效果等。

⑤AnimationSet

以上四种动画既能分开独立实现,也可以组合实现复合动画AnimationSet。

单一的动画太过于单调,那么就可以将这些单一的动画组合成个性酷炫的动画,同时指定播放的顺序,并且集合里面可以再包含集合。需要注意的是,在集合设置的属性对该标签下的所有子控件都产生影响。

1)xml实现

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

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

     android:shareInterpolator="false">

    <scale

        android:duration="2000"

        android:fromXScale="1.0"

        android:fromYScale="1.0"

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

        android:pivotX="50%"

        android:pivotY="50%"

        android:toXScale="0"

        android:toYScale="0"/> 

    <set

        android:duration="2000"

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

        android:shareInterpolator="true"

        android:startOffset="2000">

        <scale

            android:fromXScale="0"

            android:fromYScale="0"

            android:pivotX="50%"

            android:pivotY="50%"

            android:toXScale="1"

            android:toYScale="1"/>

        <rotate

            android:fromDegrees="0"

            android:pivotX="50%"

            android:pivotY="50%"

            android:toDegrees="180"/>

    </set>

</set>

shareInterpolator:子元素是否共享插值器,true表示共同使用;false表示不共享。

其余属性多和其他类似,这里如果需要设置播放的顺序,则需要设置android:startOffset属性。

startOffset:设置开始的延迟事件,单位是ms,默认为0。

在java中加载并开始该动画:

 Animation animation = AnimationUtils.loadAnimation(mContext,R.anim.set);

mImage.startAnimation(animation);

2)java代码实现:

使用Animation的子类AnimationSet 来实现:

AnimationSet animationSet1 = new AnimationSet(false);//一级集合

ScaleAnimation scaleAnimation1 = new ScaleAnimation(1, 1.4f, 1, 1.4f, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);

AnimationSet animationSet2 = new AnimationSet(true);//二级集合

ScaleAnimation scaleAnimation2 = new ScaleAnimation(1.4f, 0, 1.4f, 0, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);

RotateAnimation rotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);     

animationSet2.addAnimation(scaleAnimation2); 

animationSet2.addAnimation( rotateAnimation);

animationSet2.setInterpolator(new DecelerateInterpolator());

animationSet2.setDuration(2000);

animationSet2.setStartOffset(2000);     

animationSet1.addAnimation(scaleAnimation1);

animationSet1.addAnimation(animationSet2);

animationSet1.setInterpolator(new AccelerateDecelerateInterpolator());

animationSet1.setDuration(2000);

mImage.startAnimation(animationSet1);

动画的监听:

如果在动画变化的过程中,需要添加什么功能,就可以设置监听,比如:

 translateAnimation.setAnimationListener(new Animation.AnimationListener() {

    @Override

    public void onAnimationStart(Animation animation) {

    }

    @Override

    public void onAnimationEnd(Animation animation) {

    }

    @Override

    public void onAnimationRepeat(Animation animation) {

    }

});

注意:视图动画有一个致命的缺陷,就是它只是改变了View的显示效果,而不会真正改变view的属性。比如屏幕的左上角有一个按钮,通过视图动画将它移动到屏幕的右下角。现在去点击右下角这个按钮,点击事件是不会触发的,因为实际上这个按钮还是停留在屏幕的左上角。也就是说,ViewAnimation动画不能改变视图的属性,即使保留结束后的帧,动画结束后获取的坐标还是和动画前一样的。

视图动画的局限性:

①只能作用于view,但有时需求不是对于整个view的,而只是对view的某个属性的,例如颜色的变化,也无法对非View的对象进行动画处理。

②没有改变view的属性,只改变了view的视觉效果而已,只是修改了视图绘制的地方,而没有改变视图的本身。

③动画效果固定,动画类型只有四种,缩放,平移,旋转,透明度的基本动画,无法对其他属性进行操作。

④动画虽然可以添加监听,但是动画开始后无法对动画的执行过程进行控制。

3.Drawable Animation(帧动画)

Drawable动画,也就是Frame动画(帧动画),它可以实现像播放幻灯片一样的效果,这种动画的实质其实是Drawable,所以这种动画的XML定义方式文件一般放在res/drawable/目录下。

可以使用xml或者java方式实现帧动画。推荐使用xml,具体如下:

①<animation-list> 必须是根节点,包含一个或者多个<item>元素,属性有:

oneshot:true代表只执行一次,false循环执行。

<item> 类似一帧的动画资源。

②<item>是animation-list的子项,包含属性:

drawable:一个frame的Drawable资源。

duration: 一个frame显示多长时间。

使用方法:

①res/drawable/目录下新建rocket_demo.xml文件

<?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>

②在java文件中使用xml文件

ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);

rocketImage.setBackgroundResource(R.drawable.rocket_demo);

rocketAnimation = (AnimationDrawable) rocketImage.getBackground();

rocketAnimation.start();

注意:AnimationDrawable的start()方法不能在Activity的onCreate方法中调用,因为此时AnimationDrawable还未完全附着到window上,最好的调运时机是在onWindowFocusChanged()方法中。

视图动画和帧动画都只能对view对象添加动画效果,而且只能对公开对象属性添加效果,比如可以对view的缩放和旋转添加动画,但是不能操作背景颜色。

4.Property Animation(属性动画)

Android 3.0以后引入了属性动画,属性动画可以实现许多View动画做不到的事,它不再仅限于实现移动、缩放、旋转和淡入淡出这几种动画操作了,而且也不再只是一种视觉上的动画效果了。它实质上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性。属性动画实现原理就是修改控件的属性值实现的动画。

属性动画通过改变View的属性完成动画,利用setxxx()和getxxx()函数可以对Object的任意属性改变,从而可以实现视图动画实现不了的功能。可以通过在object中添加属性的set函数,在ondraw方法中操作属性就可以完成动画属性动画还支持监听动画过程,在动画过程中自己操作控件进行改变。

属性动画使用最多的就是ValueAnimator、ObjectAnimator、AnimatorSet和TimeAnimator这几个类了。

①ValueAnimator

ValueAnimator是整个属性动画机制中最核心的一个类。因为属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,ValueAnimator就会自动完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式以及对动画设置监听器等。

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);

anim.setDuration(300);

anim.start();

调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法中允许传入多个float类型的参数,这里传入0和1就表示将值从0平滑过渡到1,然后调用ValueAnimator的setDuration()方法来设置动画运行的时长,最后调用start()方法启动动画。

但是这只是一个将值从0过渡到1的动画,看不到任何界面效果,如果需要知道动画的运行过程,可以借助监听器:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);

anim.setDuration(300);

anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

    @Override

    public void onAnimationUpdate(ValueAnimator animation) {

        float currentValue = (float) animation.getAnimatedValue();

        Log.d("TAG", "cuurent value is " + currentValue);

    }

});

anim.start();

通过addUpdateListener()方法添加了一个动画监听器,在动画执行的过程中会不断回调该方法,这样就可以看到动画过程中值的变化了。

ofFloat()方法当中是可以传入任意多个参数的,因此还可以构建出更加复杂的动画逻辑,比如说将一个值在5秒内从0过渡到5,再过渡到3,再过渡到10,就可以这样写:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);

anim.setDuration(5000);

anim.start();

如果不需要小数位数的动画过渡,只是希望将一个整数值从0平滑地过渡到100,只需要调用ValueAnimator的ofInt()方法就可以了。

此外,还可以调用setStartDelay()方法来设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放。

②ObjectAnimator

相比于ValueAnimator,ObjectAnimator才是最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡,而ObjectAnimator就不同了,它可以直接对任意对象的任意属性进行动画操作。

ObjectAnimator继承自ValueAnimator,底层的动画实现机制也是基于ValueAnimator来完成的,所以ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的。

如果想要将一个TextView在5秒中内从常规变换成全透明,再从全透明变换成常规:

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);

animator.setDuration(5000);

animator.start();

调用ofFloat()方法创建一个ObjectAnimator实例,只不过ofFloat()方法的参数不同了。第一个参数要求传入一个object对象,即想要对哪个对象进行动画操作。第二个参数是想要对该对象的哪个属性进行动画操作。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值。

比如想要将TextView进行一次360度的旋转:

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);

animator.setDuration(5000);

animator.start();

b63982e5580f435cb80ac3bc93660c5b.gif

如果想要将TextView先向左移出屏幕,然后再移动回来:

float curTransX = textview.getTranslationX();

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTransX, -500f, curTransX);

animator.setDuration(5000);

animator.start();

先调用TextView的getTranslationX()方法获取到当前TextView的translationX的位置,然后ofFloat()方法的第二个参数传入"translationX",后面三个参数用于告诉系统TextView应该怎么移动,现在运行一下代码,效果如下图:

71115c1d447a4ed5a34c1a6303da40b6.gif

还可以对TextView进行缩放操作,比如说将TextView在垂直方向上放大3倍再还原:

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "scaleY", 1f, 3f, 1f);

animator.setDuration(5000);

animator.start();

将ofFloat()方法的第二个参数改成了"scaleY",表示在垂直方向上进行缩放,效果如下图:

a2e78f47e26b4845ad8a76ea7b64b06d.gif

ObjectAnimator的用法很简单吧,但是ofFloat()方法的第二个参数到底可以传哪些值呢?其实我们可以传入任意值到ofFloat()方法的第二个参数当中。因为ObjectAnimator在设计的时候就没有针对View来进行设计,而是针对于任意对象的,它所负责的工作就是不断地向某个对象的某个属性进行赋值,然后对象根据属性值的改变再来决定如何展现出来。

比如下面一段代码:

ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);

其实这段代码的意思就是ObjectAnimator不断地改变textview对象中alpha属性的值,从1f变化到0f。然后textview对象需要根据alpha属性值的改变来不断刷新界面显示,从而让用户可以看出淡入淡出的动画效果。

那么textview对象中是不是有alpha属性这个值呢?没有,不仅textview没有这个属性,连它所有的父类也是没有这个属性的!这就奇怪了,textview当中并没有alpha这个属性,ObjectAnimator是如何进行操作的呢?其实ObjectAnimator内部的工作机制并不是直接对传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,alpha属性所对应的get和set方法应该就是:

public void setAlpha(float value);

public float getAlpha();

那么textview对象中是否有这两个方法呢?确实有,并且这两个方法是由View对象提供的,也就是说不仅TextView可以使用这个属性来进行淡入淡出动画操作,任何继承自View的对象都可以。

所有的属性都是这个工作原理,比如View当中一定也存在着setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()这些方法。

下面是view最常用的几个属性:

translationX,translationY:控制View的位置,值是相对于View容器左上角坐标的偏移。

rotationX,rotationY:控制相对于轴心旋转。

x,y:控制View在容器中的位置,即左上角坐标加上translationX和translationY的值。

alpha:控制View对象的alpha透明度值。

③组合动画

实现组合动画功能主要需要借助AnimatorSet类,这个类提供了一个play()方法,如果向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator),将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

1)after(Animator anim) 将现有动画插入到传入的动画之后执行

2)after(long delay) 将现有动画延迟指定毫秒后执行

3)before(Animator anim) 将现有动画插入到传入的动画之前执行

4)with(Animator anim) 将现有动画和传入的动画同时执行

有了这四个方法,就可以完成组合动画的逻辑了,比如想要让TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写:

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);

ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);

ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);

AnimatorSet animSet = new AnimatorSet();

animSet.play(rotate).with(fadeInOut).after(moveIn);

animSet.setDuration(5000);

animSet.start();

先把三个动画的对象全部创建出来,然后new出一个AnimatorSet对象,之后将这三个动画对象进行播放排序,让旋转和淡入淡出动画同时进行,并把它们插入到了平移动画的后面,最后是设置动画时长以及启动动画。效果如下图:

1a1588876b8f466aa5959fe00a5e45b9.gif

④Animator监听器

如果需要监听动画的各种事件,比如动画何时开始、何时结束,然后在开始或者结束的时候去执行一些逻辑处理,就可以使用Animator类中的addListener()方法,这个方法接收一个AnimatorListener,只需要实现AnimatorListener就可以监听动画的各种事件了。

由于ObjectAnimator继承自ValueAnimator,而ValueAnimator又继承自Animator,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的。另外AnimatorSet也是继承自Animator的,因此addListener()是个通用的方法。

anim.addListener(new AnimatorListener() {

     @Override

     public void onAnimationStart(Animator animation) {

     } 

     @Override

     public void onAnimationRepeat(Animator animation) {

     }

     @Override

     public void onAnimationEnd(Animator animation) {

     }

     @Override

     public void onAnimationCancel(Animator animation) {

     }

});

如果只想要监听动画的某一个事件,不想每次都要将四个接口全部实现一遍,就可以使用Android提供的一个适配器类AnimatorListenerAdapter:

anim.addListener(new AnimatorListenerAdapter() {

});

向addListener()方法中传入这个适配器对象,由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。如果只想监听动画结束这个事件,就只需要单独重写这一个方法就可以了:

anim.addListener(new AnimatorListenerAdapter(){

     @Override

     public void onAnimationEnd(Animator animation) {

     }

});

⑤使用XML编写动画

属性动画也提供了使用xml实现的功能。

通过XML来编写动画在重用性方面有很大的优势。

要使用XML编写动画,首先要在res目录下新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。然后在XML文件中一共可以使用如下三种标签:

<animator> 对应代码中的ValueAnimator

<objectAnimator> 对应代码中的ObjectAnimator

<set> 对应代码中的AnimatorSet

比如想要实现一个从0到100平滑过渡的动画,在XML当中就可以这样写:

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

    android:valueFrom="0"

    android:valueTo="100"

    android:valueType="intType"/>

如果想将一个视图的alpha属性从1变成0,就可以这样写:

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

    android:valueFrom="1"

    android:valueTo="0"

    android:valueType="floatType"

    android:propertyName="alpha"/>

也可以使用XML来完成复杂的组合动画操作,比如将一个视图先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作:

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

    android:ordering="sequentially" >

    <objectAnimator

        android:duration="2000"

        android:propertyName="translationX"

        android:valueFrom="-500"

        android:valueTo="0"

        android:valueType="floatType" >

    </objectAnimator>

    <set android:ordering="together" >

        <objectAnimator

            android:duration="3000"

            android:propertyName="rotation"

            android:valueFrom="0"

            android:valueTo="360"

            android:valueType="floatType" >

        </objectAnimator>

        <set android:ordering="sequentially" >

            <objectAnimator

                android:duration="1500"

                android:propertyName="alpha"

                android:valueFrom="1"

                android:valueTo="0"

                android:valueType="floatType" >

            </objectAnimator>

            <objectAnimator

                android:duration="1500"

                android:propertyName="alpha"

                android:valueFrom="0"

                android:valueTo="1"

                android:valueType="floatType" >

            </objectAnimator>

        </set>

    </set> 

</set>

XML文件写好后,就可以在代码中调用了:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);

animator.setTarget(view);

animator.start();

调用AnimatorInflater的loadAnimator方法将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某个对象上,最后再调用start()方法启动动画。

⑥TypeEvaluator

TypeEvaluator直译过来就是“类型计算器”,就是告诉动画系统如何从初始值过度到结束值。

前面用到的ValueAnimator.ofFloat()方法之所以能够实现初始值与结束值之间的平滑过度,就是因为系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,看一下FloatEvaluator的代码实现:

public class FloatEvaluator implements TypeEvaluator {

    public Object evaluate(float fraction, Object startValue, Object endValue) {

        float startFloat = ((Number) startValue).floatValue();

        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);

    }

}

FloatEvaluator实现了TypeEvaluator接口,然后重写了evaluate()方法。evaluate()方法中传入了三个参数,第一个参数fraction表示动画的完成度,根据它来计算当前动画的值应该是多少;第二、第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。

FloatEvaluator是系统内置好的功能,并不需要我们自己去编写,ValueAnimator的ofFloat()和ofInt()方法分别用于对浮点型和整型的数据进行动画操作,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。

⑦ValueAnimator的高级用法

因为属性动画可以对任意对象的任意属性进行动画操作,这里以Point对象为例。比如有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法中根据这个Point对象的坐标值进行绘制。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。

先定义一个Point类:

public class Point { 

    private float x;

    private float y; 

    public Point(float x, float y) {

        this.x = x;

        this.y = y;

    }

    public float getX() {

        return x;

    }

    public float getY() {

        return y;

    }

}

Point类只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标。

接下来定义PointEvaluator:

public class PointEvaluator implements TypeEvaluator{ 

    @Override

    public Object evaluate(float fraction, Object startValue, Object endValue) {

        Point startPoint = (Point) startValue;

        Point endPoint = (Point) endValue;

        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());

        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());

        Point point = new Point(x, y);

        return point;

    } 

}

PointEvaluator实现了TypeEvaluator接口并重写了evaluate()方法。其实evaluate()方法中的逻辑是先将startValue和endValue强转成Point对象,然后根据fraction来计算当前动画的x和y的值,最后组装到一个新的Point对象当中并返回。

PointEvaluator编写完成了,接下来就可以非常轻松地对Point对象进行动画操作了。比如说有两个Point对象,现在需要将Point1通过动画平滑过渡到Point2,就可以这样写:

Point point1 = new Point(0, 0);

Point point2 = new Point(300, 300);

ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);

anim.setDuration(5000);

anim.start();

代码很简单,先是new两个Point对象,并在构造函数中分别设置了它们的坐标点。然后调用ValueAnimator的ofObject()方法来构建ValueAnimator的实例,这里需要注意的是,ofObject()方法要求多传入一个TypeEvaluator参数,这里只需要传入刚才定义好的PointEvaluator的实例就可以了。

这就是自定义TypeEvaluator的全部用法,掌握了这些知识之后,就可以来尝试一下如何通过对Point对象进行动画操作,从而实现整个自定义View的动画效果。

新建一个MyAnimView继承自View:

public class MyAnimView extends View {

    public static final float RADIUS = 50f;

    private Point currentPoint;

    private Paint mPaint;

    public MyAnimView(Context context, AttributeSet attrs) {

        super(context, attrs);

        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);

        mPaint.setColor(Color.BLUE);

    }

    @Override

    protected void onDraw(Canvas canvas) {

        if (currentPoint == null) {

            currentPoint = new Point(RADIUS, RADIUS);

            drawCircle(canvas);

            startAnimation();

        } else {

            drawCircle(canvas);

        }

    }

    private void drawCircle(Canvas canvas) {

        float x = currentPoint.getX();

        float y = currentPoint.getY();

        canvas.drawCircle(x, y, RADIUS, mPaint);

    }

     private void startAnimation() {

        Point startPoint = new Point(RADIUS, RADIUS);

        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);

        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override

            public void onAnimationUpdate( ValueAnimator animation) {

                currentPoint = (Point) animation.getAnimatedValue();

                invalidate();

            }

        });

        anim.setDuration(5000);

        anim.start();

    }

}

首先在自定义View的构造方法当中初始化了一个Paint对象作为画笔,并将画笔颜色设置为蓝色,接着在onDraw()方法当中进行绘制。绘制的逻辑由currentPoint这个对象控制,如果currentPoint对象不等于空,就调用drawCircle()方法在currentPoint的坐标位置画出一个半径为50的圆,如果currentPoint对象是空,那么就调用startAnimation()方法启动动画。startAnimation()方法就是对Point对象进行动画操作,定义了一个startPoint和一个endPoint,坐标分别是View的左上角和右下角,并将动画的时长设为5秒。通过监听器对动画的过程进行了监听,每当Point值有改变的时候都会回调onAnimationUpdate()方法。在这个方法当中对currentPoint对象进行了重新赋值,并调用了invalidate()方法,这样的话onDraw()方法就会重新调用,并且由于currentPoint对象的坐标已经改变了,那么绘制的位置也会改变,于是一个平移的动画效果就实现了。

接下来在布局文件中使用这个自定义控件:

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

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    >

    <com.example.MyAnimView

        android:layout_width="match_parent"

        android:layout_height="match_parent" />

</RelativeLayout>

运行一下程序,效果如下:

7bc1a541eb4c41f5a2dc4a1acfef1b65.gif

这样就成功实现了通过对对象进行值操作来实现动画效果的功能,这就是ValueAnimator的高级用法。

⑧ObjectAnimator的高级用法

视图动画是只能实现移动、缩放、旋转和淡入淡出这四种动画操作的,功能限定死就是这些,基本上没有任何扩展性可言。比如我们想要实现对View的颜色进行动态改变,视图动画是没有办法做到的。

但是属性动画就不会受这些限制,它的扩展性非常强,对于动态改变View的颜色这种功能是完全可是胜任的,那么下面我们就来学习一下如何实现这样的效果。

由于ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个color属性,并提供它的get和set方法。这里可以将color属性设置为字符串类型,使用#RRGGBB这种格式来表示颜色值:

public class MyAnimView extends View {

   ...

   private String color;

   public String getColor() {

      return color;

   }

   public void setColor(String color) {

        this.color = color;

        mPaint.setColor(Color.parseColor(color));

        invalidate();

   }

   ...

}

注意在setColor()方法当中,将画笔的颜色设置成方法参数传入的颜色,然后调用了invalidate()方法。这段代码虽然只有三行,却执行了一个非常核心的功能,就是在改变了画笔颜色后立即刷新视图,然后onDraw()方法就会调用。在onDraw()方法中会根据当前画笔的颜色来进行绘制,这样颜色也就会动态进行改变了。

接下来的问题就是怎样让setColor()方法得到调用了,这当然要借助ObjectAnimator类,但是在使用ObjectAnimator之前还要完成一个非常重要的工作,就是编写一个用于告知系统如何进行颜色过度的TypeEvaluator。

创建ColorEvaluator并实现TypeEvaluator接口:

public class ColorEvaluator implements TypeEvaluator {

   private int mCurrentRed = -1;

   private int mCurrentGreen = -1;

   private int mCurrentBlue = -1;

   @Override

   public Object evaluate(float fraction, Object startValue, Object endValue) {

      String startColor = (String) startValue;

      String endColor = (String) endValue;

      int startRed = Integer.parseInt( startColor.substring(1, 3), 16);

      int startGreen = Integer.parseInt( startColor.substring(3, 5), 16);

      int startBlue = Integer.parseInt( startColor.substring(5, 7), 16);

      int endRed = Integer.parseInt( endColor.substring(1, 3), 16);

      int endGreen = Integer.parseInt( endColor.substring(3, 5), 16);

      int endBlue = Integer.parseInt( endColor.substring(5, 7), 16);

      // 初始化颜色的值

      if (mCurrentRed == -1) {

           mCurrentRed = startRed;

      }

     if (mCurrentGreen == -1) {

         mCurrentGreen = startGreen;

     }

     if (mCurrentBlue == -1) {

         mCurrentBlue = startBlue;

     }

    // 计算初始颜色和结束颜色之间的差值

    int redDiff = Math.abs(startRed - endRed);

    int greenDiff = Math.abs(startGreen - endGreen);

    int blueDiff = Math.abs(startBlue - endBlue);

    int colorDiff = redDiff + greenDiff + blueDiff;

    if (mCurrentRed != endRed) {

         mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0, fraction);

     } else if (mCurrentGreen != endGreen) {

       mCurrentGreen = getCurrentColor( startGreen, endGreen, colorDiff, redDiff, fraction);

    } else if (mCurrentBlue != endBlue) {

       mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff, redDiff + greenDiff, fraction);

    }

    // 将计算出的当前颜色的值组装返回

    String currentColor = "#" + getHexString(mCurrentRed) + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);

    return currentColor;

}

//根据fraction值来计算当前的颜色

private int getCurrentColor(int startColor, int endColor, int colorDiff,int offset, float fraction) {

    int currentColor;

    if (startColor > endColor) {

        currentColor = (int) (startColor - (fraction * colorDiff - offset));

        if (currentColor < endColor) {

             currentColor = endColor;

        }

    } else {

         currentColor = (int) (startColor + (fraction * colorDiff - offset));

        if (currentColor > endColor) {

            currentColor = endColor;

        }

    }

    return currentColor;

}

// 将10进制颜色值转换成16进制

private String getHexString(int value) {

     String hexString = Integer.toHexString(value);

     if (hexString.length() == 1) {

          hexString = "0" + hexString;

     }

     return hexString;

}

首先在evaluate()方法中获取到颜色的初始值和结束值,并通过字符串截取的方式将颜色分为RGB三个部分,并将RGB的值转换成十进制数字,那么每个颜色的取值范围就是0-255。接下来计算一下初始颜色值到结束颜色值之间的差值,这个差值很重要,决定着颜色变化的快慢,如果初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢;如果颜色值相差很大,比如说从黑到白,那么就要经历255*3这个幅度的颜色过度,变化就会非常快。

控制颜色变化的速度是通过getCurrentColor()这个方法来实现的,这个方法会根据当前的fraction值来计算目前应该过度到什么颜色,并且会根据初始和结束的颜色差值来控制变化速度,最终将计算出的颜色进行返回。

最后,由于计算出的颜色是十进制数字,还需要调用getHexString()方法转换成十六进制字符串,再将RGB颜色拼装起来作为最终的结果返回。

ColorEvaluator写完之后,剩下的就是一些简单调用的问题了,比如说想要实现从蓝色到红色的动画过度,历时5秒:

ObjectAnimator anim = ObjectAnimator.ofObject(myAnimView, "color", new ColorEvaluator(), "#0000FF", "#FF0000");

anim.setDuration(5000);

anim.start();

接下来我们需要将上面一段代码移到MyAnimView类当中,让它和刚才的Point移动动画结合到一起播放,这就要借助组合动画的技术了。修改MyAnimView中的代码,如下所示:

public class MyAnimView extends View {

    ...

    private void startAnimation() {

        Point startPoint = new Point(RADIUS, RADIUS);

        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);

        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override

            public void onAnimationUpdate( ValueAnimator animation) {

                currentPoint = (Point) animation.getAnimatedValue();

                invalidate();

            }

        });

        ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),  "#0000FF", "#FF0000");

        AnimatorSet animSet = new AnimatorSet();

        animSet.play(anim).with(anim2);

        animSet.setDuration(5000);

        animSet.start();

    }

 }

可以看到,并没有改动太多的代码,重点只是修改了startAnimation()方法中的部分内容。这里先是将颜色过度的代码逻辑移动到startAnimation()方法中,注意由于这段代码本身就是在MyAnimView当中执行的,因此ObjectAnimator.ofObject()的第一个参数直接传this就可以了。接着又创建了一个AnimatorSet,并把两个动画设置成同时播放,动画时长为五秒,最后启动动画。

效果如下图:

dabdc5e7ca68451a83fa0d456aa28599.gif

 位置动画和颜色动画非常融洽的结合到一起了,看上去效果还是相当不错的,这样我们就把ObjectAnimator的高级用法也掌握了。

⑨Interpolator

Interpolator直译过来就是补间器的意思,它的主要作用是可以控制动画的变化速率,比如实现一种非线性运动的动画效果图,即动画改变的速率不是一成不变的,像加速运动以及减速运动都属于非线性运动。

不过Interpolator并不是属性动画中新增的技术,实际上从Android 1.0版本开始就一直存在Interpolator接口了,之前的补间动画当然也是支持这个功能的。只不过在属性动画中新增了一个TimeInterpolator接口,这个接口是用于兼容之前的Interpolator的,这使得所有过去的Interpolator实现类都可以直接拿过来放到属性动画当中使用,那么我们来看一下现在TimeInterpolator接口的所有实现类:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

可以看到,TimeInterpolator接口已经有非常多的实现类了,这些都是Android系统内置好的,我们可以直接使用。每个Interpolator都有各自的实现效果,比如说AccelerateInterpolator就是一个加速运动的Interpolator,而DecelerateInterpolator就是一个减速运动的Interpolator。

使用属性动画时,系统默认的Interpolator是一个先加速后减速的Interpolator,对应的实现类就是AccelerateDecelerateInterpolator。

我们可以修改这一默认属性,将它替换成任意一个系统内置好的Interpolator。比如对Point对象的坐标稍做一下修改,让它变成一种垂直掉落,下降的速度越来越快。可以调用Animator的setInterpolator()方法,传入一个实现TimeInterpolator接口的实例,这里可以使用AccelerateInterpolator:

private void startAnimation() {

    Point startPoint = new Point(getWidth() / 2, RADIUS);

    Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);

    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override

        public void onAnimationUpdate(ValueAnimator animation) {

            currentPoint = (Point) animation.getAnimatedValue();

            invalidate();

        }

    });

    anim.setInterpolator(new AccelerateInterpolator(2f));

    anim.setDuration(2500);

    anim.start();

}

代码很简单,调用了setInterpolator()方法,然后传入了一个AccelerateInterpolator的实例,注意AccelerateInterpolator的构建函数可以接收一个float类型的参数,这个参数是用于控制加速度的。效果如下图:

de1f463afd574180a07841f63bd0c5f3.gif

效果非常明显,说明已经成功替换掉了默认的Interpolator,AccelerateInterpolator确实是生效了。但是现在的动画效果看上去仍然是怪怪的,因为一个小球从很高的地方掉落到地面上直接就静止了,这也是不符合物理规律的,小球撞击到地面之后应该要反弹起来,然后再次落下,接着再反弹起来,又再次落下,以此反复,最后静止。这个功能我们当然可以自己去写,只不过比较复杂,所幸的是,Android系统中已经提供好了这样一种Interpolator,我们只需要简单地替换一下就可以完成上面的描述的效果,代码如下:

private void startAnimation() {

    Point startPoint = new Point(getWidth() / 2, RADIUS);

    Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);

    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override

        public void onAnimationUpdate(ValueAnimator animation) {

            currentPoint = (Point) animation.getAnimatedValue();

            invalidate();

        }

    });

    anim.setInterpolator(new BounceInterpolator());

    anim.setDuration(3000);

    anim.start();

}

将设置的Interpolator换成了BounceInterpolator实例,而BounceInterpolator就是一种可以模拟物理规律,实现反复弹起效果的Interpolator。另外还将整体的动画时间稍微延长了一点,因为小球反复弹起需要比之前更长的时间。效果如下图:

209daf54eb124c51a067628d43621466.gif

 大家可以自己使用一下系统提供的其它几种Interpolator看一看效果。

接下来看一下Interpolator的内部实现机制是什么样的,并且来尝试写一个自定义的Interpolator。

首先看一下TimeInterpolator的接口定义:

public interface TimeInterpolator {

    float getInterpolation(float input);

}

接口里只有一个getInterpolation()方法。getInterpolation()方法中接收一个input参数,这个参数的值会随着动画的运行而不断变化,不过它的变化是非常有规律的,就是根据设定的动画时长匀速增加,变化范围是0到1。也就是说当动画一开始的时候input的值是0,到动画结束的时候input的值是1,而中间的值则是随着动画运行的时长在0到1之间变化的。

说到这个input的值,你可能想到前面使用过的fraction值。那么这里的input和fraction有什么关系或者区别呢?答案很简单,input的值决定了fraction的值。input的值是由系统经过计算后传入到getInterpolation()方法中的,然后我们可以自己实现getInterpolation()方法中的算法,根据input的值来计算出一个返回值,而这个返回值就是fraction了。

因此,最简单的情况就是input值和fraction值是相同的,这种情况由于input值是匀速增加的,因而fraction的值也是匀速增加的,所以动画的运动情况也是匀速的。系统中内置的LinearInterpolator就是一种匀速运动的Interpolator,那么我们来看一下它的源码是怎么实现的:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {

    }

    public LinearInterpolator(Context context, AttributeSet attrs) {

    }

    public float getInterpolation(float input) {

        return input;

    } 

    @Override

    public long createNativeInterpolator() {

        return NativeInterpolatorFactoryHelper. createLinearInterpolator();

    }

这里我们只看getInterpolation()方法,这个方法没有任何逻辑,就是把参数中传递的input值直接返回了,因此fraction的值就是等于input的值的,这就是匀速运动的Interpolator的实现方式。

这是最简单的一种Interpolator的实现,我们再来看一个稍微复杂一点的。我们知道系统默认情况下使用的是AccelerateDecelerateInterpolator,那我们就来看一下它的源码吧,如下所示:

public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {

    public AccelerateDecelerateInterpolator() {

    }

    public AccelerateDecelerateInterpolator( Context context, AttributeSet attrs) {

    }

    public float getInterpolation(float input) {

        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;

    }

    @Override

    public long createNativeInterpolator() {

        return NativeInterpolatorFactoryHelper. createAccelerateDecelerateInterpolator();

    }

}

getInterpolation()方法中的逻辑明显变复杂了,不再是简单地将参数中的input进行返回,而是进行了一个较为复杂的数学运算。来分析一下它的算法实现,可以看到,算法中主要使用了余弦函数,由于input的取值范围是0到1,那么cos函数中的取值范围就是π到2π。而cos(π)的结果是-1,cos(2π)的结果是1,那么这个值再除以2加上0.5之后,getInterpolation()方法最终返回的结果值还是在0到1之间。只不过经过了余弦运算之后,最终的结果不再是匀速增加的了,而是经历了一个先加速后减速的过程。我们可以将这个算法的执行情况通过曲线图的方式绘制出来,结果如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

 可以看到,这是一个S型的曲线图,当横坐标从0变化到0.2的时候,纵坐标的变化幅度很小,但是之后就开始明显加速,最后横坐标从0.8变化到1的时候,纵坐标的变化幅度又变得很小。

通过分析LinearInterpolator和AccelerateDecelerateInterpolator的源码,我们已经对Interpolator的内部实现机制有了比较清楚的认识了,那么接下来我们就开始尝试编写一个自定义的Interpolator。

编写自定义Interpolator最主要的难度都是在于数学计算方面的,既然属性动画默认的Interpolator是先加速后减速的一种方式,这里我们就对它进行一个简单的修改,让它变成先减速后加速的方式。新建DecelerateAccelerateInterpolator类,让它实现TimeInterpolator接口,代码如下:

public class DecelerateAccelerateInterpolator implements TimeInterpolator{

    @Override

    public float getInterpolation(float input) {

        float result;

        if (input <= 0.5) {

            result = (float) (Math.sin(Math.PI * input)) / 2;

        } else {

            result = (float) (2 - Math.sin(Math.PI * input)) / 2;

        }

        return result;

    }

 }

使用正弦函数来实现先减速后加速的功能的,因为正弦函数初始弧度的变化值非常大,刚好和余弦函数是相反的,而随着弧度的增加,正弦函数的变化值也会逐渐变小,这样也就实现了减速的效果。当弧度大于π/2之后,整个过程相反了过来,现在正弦函数的弧度变化值非常小,渐渐随着弧度继续增加,变化值越来越大,弧度到π时结束,这样从0过度到π,也就实现了先减速后加速的效果。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

 可以看到,这也是一个S型的曲线图,只不过曲线的方向和刚才是相反的。一开始纵坐标的变化幅度很大,然后逐渐变小,横坐标到0.5的时候纵坐标变化幅度趋近于零,之后随着横坐标继续增加纵坐标的变化幅度又开始变大,的确是先减速后加速的效果。

现在将DecelerateAccelerateInterpolator在代码中进行替换:

80c2bfd2569a45e1b5dc35e00599e89c.gif

 小球的运动确实是先减速后加速的效果,说明自定义的Interpolator已经可以正常工作了。

⑩ViewPropertyAnimator

ViewPropertyAnimator是在3.1系统中附增的一个新的功能。

我们都知道,属性动画的机制已经不是再针对于View进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。但是,在绝大多数情况下,我相信大家主要都还是对View进行动画操作的。Android开发团队也是意识到了这一点,没有为View的动画操作提供一种更加便捷的用法确实是有点太不人性化了,于是在Android 3.1系统当中补充了ViewPropertyAnimator这个机制。

先回顾一下之前的用法,比如想让一个TextView从常规状态变成透明状态,就可以这样写:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);

animator.start();

看上去复杂吗?好像也不怎么复杂,但确实也不怎么容易理解。我们要将操作的view、属性、变化的值都一起传入到ObjectAnimator.ofFloat()方法当中,虽然看上去也没写几行代码,但这不太像是我们平时使用的面向对象的思维。

下面就来看一下如何使用ViewPropertyAnimator来实现同样的效果,ViewPropertyAnimator提供了更加易懂、更加面向对象的API,如下所示:

textview.animate().alpha(0f);

果然非常简单!animate()方法就是在Android 3.1系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,这里我们调用了alpha()方法并传入0,表示将当前的textview变成透明状态。

和ObjectAnimator相比,ViewPropertyAnimator的用法明显更加简单易懂。除此之外,ViewPropertyAnimator还可以很轻松地将多个动画组合到一起,比如我们想要让textview运动到500,500这个坐标点上,就可以这样写:

textview.animate().x(500).y(500);

可以看出,ViewPropertyAnimator是支持连缀用法的,我们想让textview移动到横坐标500这个位置上时调用了x(500)这个方法,然后让textview移动到纵坐标500这个位置上时调用了y(500)这个方法,将所有想要组合的动画通过这种连缀的方式拼接起来,这样全部动画就都会一起被执行。

那么怎样去设定动画的运行时长呢?很简单,也是通过连缀的方式设定即可,比如我们想要让动画运行5秒钟,就可以这样写:

textview.animate().x(500).y(500).setDuration(5000);

除此之外,Interpolator技术我们也可以应用在ViewPropertyAnimator上面,如下所示:

textview.animate().x(500).y(500).setDuration(5000).setInterpolator(new BounceInterpolator());

ViewPropertyAnimator其实并没有什么太多的技巧可言,用法基本都是大同小异的,需要用到什么功能就连缀一下,因此更多的用法大家只需要去查阅一下文档,看看还支持哪些功能,有哪些接口可以调用就可以了。

除了用法之外,关于ViewPropertyAnimator有几个细节还是值得大家注意一下的:

1)整个ViewPropertyAnimator的功能都是建立在View类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后调用的所有方法,设置的所有属性都是通过这个实例完成的。

2)大家注意到,在使用ViewPropertyAnimator时,我们自始至终没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动。并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动。当然如果不想使用这一默认机制的话,我们也可以显式地调用start()方法来启动动画。

3)ViewPropertyAnimator的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此调用完一个方法之后可以直接连缀调用它的另一个方法,这样把所有的功能都串接起来,我们甚至可以仅通过一行代码就完成任意复杂度的动画功能。

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/124123955