Android属性动画详解(上),初始属性动画的基本用法

来源:互联网 发布:java yield在Runnable 编辑:程序博客网 时间:2024/05/21 22:20

前言:前两天看了下郭大神写的属性动画,感觉挺好,所以就很不要脸的搬过来了,附上原文地址:Android属性动画完全解析(上),初识属性动画的基本用法,有兴趣的可以看看

关于属性动画

  • 属性动画是Android3.0版本引入的,为什么要引入属性动画这一机制呢?那咱们就要从补间动画上来找找原因了,熟悉补间动画的朋友都知道,如果是一般的动画(平移、旋转、缩放、渐变)补间动画完全就可以做到,而且补间动画还不存在版本兼容的问题,那么为什么还要引入属性动画呢?下面我们慢慢分析:

为什么要引用属性动画

  • 其实Android之前的补间动画机制还是非常健全的,在android.view.animation包下有很多类可以供我们来完成我们想要的效果,比如常见的平移、旋转、缩放、渐变,这些效果。并且我们还可以通过AnimationSet将这些动画组合起来,而且补间动画还支持interpolation来控制动画的播放速度等等,那么问题来了,既然补间动画的机制这么健全,为什么还要引入属性动画呢??
  • 其实补间动画的功能是相对的,在以前我们对动画的认知比较少,如果你的动画中只要求使用上面提到的四种对 view 的操作,那么补间动画真的可以完全实现了,但是很显然,在现在用户对APP的体验要求的越来越高,很明显补间动画已经力不从心了,为什么这么说呢?下面我们就来说说补间动画不能胜任的场景
  • 不知道大家有没有注意到,我在上面介绍补间动画的时候,说的都是“补间动画对view进行操作”,也就是说补间动画仅仅能够用来对view进行操作,只要是继承自view的组件补间动画都是可以操作的,但是如果想要对非view的对象进行操作,那么不好意思,补间动画就帮不上什么忙了,可能有朋友会感到不能理解(其实我也不能理解,对非view的对象操作,有这样的应用么?但是当看了郭大神后面的关于属性动画的几篇博客后,感觉这样的例子在现实中还是很常见的,属性动画的深入我在后面也会贴出),这里举个例子吧,比如说我们有一个自定义的view,在这个view当中我们定义了一个point对象,这个对象用于管理坐标,我们就可以通过这个point对象在onDraw()这个方法中进行绘制,就会形成一个动画,者这些操作都是补间动画不具备的。

补间动画的缺陷

  • 补间动画除了上面我们提到的缺陷以外,还有几个很严重的缺陷,这也是我们在以后的开发中渐渐抛弃补间动画的重要原因
    • 补间动画是不能对view的背景色进行动态的改变的,如果想要改变view的背景色,补间动画的功能限定死了就四种,其他的就只能我们自己去实现了
    • 补间动画改变的仅仅是view的显示效果,而并不会真正的改变view的属性,这点是最坑的,比如说,我们使用补间动画将一个view从屏幕的左上角移动到了屏幕的右下角,当我们点击移动后的view时,点击事件是绝对不会触发的,因为view其实还是停留在屏幕的左上角,要想触发点击事件,只能点击左上角,所以补间动画仅仅能够用来显示效果用

属性动画的优势

  • 新引入的属性动画已经不在是单单的针对于view进行设计的了,动画效果也不仅仅只是平移、旋转、缩放、渐变这么几种了。它实际上是一种不断对值进行操作的机制,将变化的值指定到指定对象的指定属性上,这个指定是没有任何限制的,可以是任意对象的任意属性。这时候我们不仅可以对一个view对象进行操作,同时还可以对该view中的对象进行动画操作,而我们要做的操作也非常简单,我们只需要告诉系统我们要运行的动画的时长,需要执行哪种类型的动画,以及动画的开始值和结束值就可以了,剩下的工作就可以依靠系统去替我们完成了。
  • 既然属性动画的实现机制是通过目标对象进行赋值并修改其属性实现的,那么之前所说的补间动画不能改变位置的说法在属性动画中也就不存在了,如果我们通过属性动画来移动一个view,那么这个view就是真正的移动了,而不再仅仅是在另一个位置上绘制了而已。
  • 好了 说了这么多,下面我们就来学习一下属性动画的基本用法吧

ValueAnimator

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

下面我们就来玩玩属性动画吧
其实属性动画是使用相当简单,我们就先从最简单的操作开始吧,既然属性动画的运行机制是通过不断地对值进行操作来实现的,那么我们就来做一个 0 到 1 值的过渡吧

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

就这么几行代码,其实我们就已经实现了从0到一的值的过渡,而构建ValueAnimator的实例很简单,我们只需要调用ValueAnimator的ofFloat()方法就可以完成,这里传入的 0 和 1 就表示我们设置的初始值 和 结束值,然后就是设置动画的运行时长setDuration()以及启动动画start();

用法就是这么简单,但是问题来了就这么几行代码我们怎么才能知道这个动画运行了呢?我们怎么才能获取到运行期间的过渡值呢?不用想 Android API中肯定给我们提供了能够访问过渡值的方法(而且很有可能是一个监听器,为什么呢? 因为监听就是用来监听变化的,既然值发生了变化,那么Android API肯定需要通过回调来告诉我们,使用监听器是必然的),下面我们可以试着来找一下:
在ValueAnimator这个类中有一个 addUpdateListener(ValueAnimator.AnimatorUpdateListener listener) 这么一个方法,那么我们就来试一下,是不是 试一下才知道:
来吧 代码走起来:

 //创建ValueAnimator实例        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                float animatedValue = (float) animation.getAnimatedValue();                Log.d("TAG","current value is = "+animatedValue);            }        });        valueAnimator.setDuration(500);        valueAnimator.start();

运行上面代码,控制台Log打印如下:
这里写图片描述

可以看到500毫秒内 我们设置的值确实是从 0 过渡到了 1,这就说明ValueAnimator确实是帮我们完成了值的过渡,但是ValueAnimator能做得操作真的只有这么简单么?当然不是的,复杂的逻辑我们后面慢慢将,我们先来看看ValueAnimator的ofFloat()方法吧,细心的同学可能已经发现了,onFloat()方法中接收的参数是 一个 float类型的可变数组,那也就是说,我们可以传递多个值咯,来试一下,看看效果:

这里我在ofFloat()中传递的参数依次是 0f、1f、3f、0f

current value is = 0.0current value is = 0.0current value is = 0.10287103current value is = 0.40194967current value is = 0.8491747current value is = 1.1114464current value is = 1.3324375current value is = 1.5573113current value is = 1.7713349current value is = 2.0current value is = 2.228666current value is = 2.4426885current value is = 2.6675625current value is = 2.8885536current value is = 2.862234current value is = 2.5475242current value is = 2.244172current value is = 1.9706244current value is = 1.6942958current value is = 1.4342914current value is = 1.2058493current value is = 0.9817581current value is = 0.7781377current value is = 0.6062565current value is = 0.44563937current value is = 0.30861282current value is = 0.20191145current value is = 0.11282444current value is = 0.049262524current value is = 0.013091326current value is = 0.0

如果在使用过程中你想要得不是float类型的数据,那也容易,可以使用ofInt()来获取整形数据,这里我们就不再列出了,有兴趣的可以自己了解下

那么除此之外,ValueAnimator还给我们提供了延时播放动画的方法setStartDelay(),我们也可以通过setRepeatMode()方法来设置动画的播放次数以及循环模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放以及倒序播放,大家可以自己试一下

ObjectAnimator

相对于ValueAnimator,ObjectAnimator才是我们最经常接触的类,因为ValueAnimator仅仅是对值进行平滑的过渡,也就只有我们需要动态改变某个值的时候才会用到,在做动画是我们使用的还是ObjectAnimator,ObjectAnimator是可以对任意对象的任意属性进行操作的

不过虽然说ObjectAnimator比较常用,但是其实ObjectAnimator是继承自ValueAnimator的,底层的动画实现机制其实使用的还是ValueAnimator,而ObjectAnimator可以让我们更加方便的完成动画操作,那么既然是继承自ValueAnimator,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,比如我们想要实现一个 textview在5秒内从常规变成全透明,在从全透明变成常规,那么我们就可以这么写:

//首先和ValueAnimator一样,先获取ObjectAnimator的实例        TextView textView = (TextView) findViewById(R.id.tv_content);        Button start = (Button) findViewById(R.id.bt_start);        alpha = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f, 1f);        start.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                alpha.setDuration(5000).start();            }        });

可以看到我们这里调用的还是ofFloat()方法,只不过传递的参数变了,使用ObjectAnimator的ofFloat时需要我们我们多传入两个参数,第一个参数要求我们传入一个Object对象,表示的是我们要操作的动画的对象,也就是我们想要对那个对象进行动画操作这里就传入那个对象,这里我们是要对Textview进行渐变操作,所以这里传入的是textview,第二个参数要求我们传入一个字符串,其实表示的就是对象的属性,前面已经讲过,属性动画其实操作的就是对象的属性,这里我们传入”alpha”,为什么会传这么一个参数呢?有什么代表意义么?后面我们会在列出几个常见的例子,到时候再来说说第二个参数传递的要求吧,我们先看下效果图:
这里写图片描述

学会了这个之后,是不是相当 so easy, 那么我们在来试试旋转吧,我们可以先让 Textview旋转个180度,然后在旋转回来,看代码:

//首先和ValueAnimator一样,先获取ObjectAnimator的实例        TextView textView = (TextView) findViewById(R.id.tv_content);        Button start = (Button) findViewById(R.id.bt_start);        alpha = ObjectAnimator.ofFloat(textView, "rotation", 0f, 180f, 0f);        start.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                alpha.setDuration(5000).start();            }        });

可以看到我们其实没怎么改代码,就是变了个属性而已,改了下动画的样式,让动画从0段旋转到180度,然后在旋转会0度,看下效果是怎样的:
这里写图片描述

由于gif是用GIFCam录制的,所以可能有点延迟,大家多多包涵

那么如果是想要将textview移出屏幕,然后在移回来呢?

//首先和ValueAnimator一样,先获取ObjectAnimator的实例        TextView textView = (TextView) findViewById(R.id.tv_content);        Button start = (Button) findViewById(R.id.bt_start);        //获取到当前view的位置,如果你的textview的布局是match_parent 那么获取的值将为0.0        float translationX = textView.getTranslationX();        Toast.makeText(this,"translationX = "+translationX,Toast.LENGTH_LONG).show();        alpha = ObjectAnimator.ofFloat(textView, "translationX", translationX, -500, translationX);        start.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                alpha.setDuration(5000).start();            }        });

这里我们首先调用了textview的getTranslationX()来获取当前的textview的translationX,保证我们在移回屏幕时能回到原来的位置,第二个参数我们传入的是 translationX,其实就是在告诉系统我们想要的效果是在 X轴上实现,运行下代码,效果如下:
这里写图片描述

现在渐变、旋转、平移我们都讲完了,就剩下一个缩放,来看代码:

//首先和ValueAnimator一样,先获取ObjectAnimator的实例        TextView textView = (TextView) findViewById(R.id.tv_content);        Button start = (Button) findViewById(R.id.bt_start);        alpha = ObjectAnimator.ofFloat(textView, "scaleY", 1, 3, 1,3);        start.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                alpha.setDuration(5000).start();            }        });

这里主要看第二个参数,scaleY 表示在垂直方向上进行缩放,运行下代码,效果显示如下:

这里写图片描述

到目前为止,ObjectAnimator的基本操作就到此为止了,想必大家一直有个疑惑,就是我们在使用ofFloat()时,我们传递的第二个参数到底是什么意思呢?目前我们已经使用了 alpha、rotation、translationX、scaleY这几个值,已经分别完成了渐变、旋转、平移、缩放等多个动画,那么ofFloat的传值有什么要求么?其实这个还真不好回答,可以说的是ofFloat()的第二个参数可以接受任意的字符串,很意外吧?其实前面我们提到过,属性动画的引入并不是仅仅针对view来进行设计的,它所负责的操作就是对某个对象的某个属性进行不断的赋值操作,说到这里可能大家也应该能够想到ObjectAnimator到底是怎么工作的了,下面我们就来具体的分析下

ObjectAnimator的工作原理

其实ObjectAnimator工作原理很简单,既然他是通过ValueAnimator对值的过渡完成的,那么就好理解了,比如说我们前面举得例子:

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

为什么我们设置了 “alpha”后就能改变view的透明度呢?其实很好理解,肯定是ObjectAnimator内部通过获取过渡值然后 指定的 对象的 alpha 这一属性 进行了动态的赋值,从而改变了指定对象的透明度,但是真的是如此吗?我们可以去textview中去找找有没有 “alpha” 这一个属性,在这里我已经找过了,很遗憾的告诉大家没有,不过textview中没有,就连textview的祖类 View 中也是没有 “alpha”这个属性的,既然没有,那么他是怎么动态改变指定对象的 “alpha”的呢? 其实大家想多了,我们在开发中想要对一个对象的某个属性进行赋值时,使用的时什么方法?是不是setXXX()、getXXX(),想明白了没?其实view之所以可以改变透明度,使用的就是setAlpha()这个方法,现在大家总应该明白了吧! ObjectAnimator内部会根据我们提供的属性值,去找对应的get和set方法,完成的属性的改变,那么View中肯定存在 setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()这些方法,不信的话大家可以去找一下

组合动画

前面我们已经搞定了属性动画的基本用法,但是这还没完,因为有时候仅仅单个动画是实现不了我们现实生活中想要实现的效果的,所以将多个动画组合在一起时尤其的重要,幸运的是,Android团队在设计属性动画的时候也充分考虑到了组合动画的功能,然后也提供了一套API来让我们将多个动画组合到一起

要想完美的将多个动画组合到一起,就必须使用AnimatorSet这个类来实现了,这个类中提供了一个 play()方法,供我们传入一个Animator(ValueAnimator/ObjectAnimator)将会给我们返回一个Animator.Builder的实例,而Animator的Builder对象中包含四个方法:

  • after(Animator anim); 将现有动画插入到传入动画之后执行
  • after(long detay); 将现有动画延迟指定毫秒数之后执行
  • before(Animator anim); 将现有动画插入到传入动画之前执行
  • with(Animator anim); 将现有动画与插入的动画同时执行

好了,有了这四个动画,我们就可以完成组合动画的逻辑了,比如有这么一个效果:我们现在想要让textview先从屏幕外移动到屏幕内,然后开始旋转360度,旋转的同时完成淡入淡出的操作,这个逻辑怎么实现呢?代码就可以这样写:

//首先和ValueAnimator一样,先获取ObjectAnimator的实例        TextView textView = (TextView) findViewById(R.id.tv_content);        Button start = (Button) findViewById(R.id.bt_start);        //定义一个从屏幕外移动到屏幕里的动画,首先需要获取textview的translationX        float translationX = textView.getTranslationX();        translation = ObjectAnimator.ofFloat(textView, "translationX", -500,translationX);        //定义一个旋转360的textview动画        rotation = ObjectAnimator.ofFloat(textView, "rotation",0,360);        //定义一个淡入淡出的textview动画        alpha = ObjectAnimator.ofFloat(textView, "alpha", 1f,0f,1f);        //定义一个动画的集合        animatorSet = new AnimatorSet();        start.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //开始编写动画的执行顺序,rotation 和 alpha动画插入到传入的动画(translation)之后执行,                // 因为alpha是通过with进行连接的,所以alpha动画会和rotation一起执行                animatorSet = new AnimatorSet();                animatorSet.play(rotation).with(alpha).after(translation);                animatorSet.setDuration(3000);                animatorSet.start();            }        });

是不是很简单,动画还是一样的动画,只不过我们将所有的动画管理起来了,交给了AnimationSet去控制他们的执行顺序,来看下效果:
这里写图片描述

Animator监听器

监听器其实是动画中我们经常要用到的东西,而Animation监听器是用来干嘛的呢?我们动画是可以执行了,但是动画到底什么时候结束,什么时候开始,这些状态我们怎么知道呢?这就需要用到Animation监听器了

大家都已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的,另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法:
添加一个监听器的代码如下:

anim.addListener(new AnimatorListener() {  02.    @Override  03.    public void onAnimationStart(Animator animation) {  04.    }  05.  06.    @Override  07.    public void onAnimationRepeat(Animator animation) {  08.    }  09.  10.    @Override  11.    public void onAnimationEnd(Animator animation) {  12.    }  13.  14.    @Override  15.    public void onAnimationCancel(Animator animation) {  16.    }  17.});  

可以看到,我们需要实现的方法有四个,onAnimationStart()方法会在动画开始的时候调用,onAnimationRepeat()方法会在动画重复时调用,onAnimationEnd()方法会在动画执行结束后调用,onAnimationCancel()方法会在动画被取消的时候调用。

但是也许有很多时候我们并不想实现这么多的方法,可能只是想要监听动画结束的那个方法,那么如果实现AnimatorListener这个借口,那么我们就不得不实现其他的三个方法,这样就显得非常繁琐,因此,Android API给我们提供了一个适配器,叫做AnimatorListenerAdapter,使用这个类就可以解决掉实现多余方法的繁琐了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {  02.}); 

所以这个时候我们只需要实现我们想要实现的方法就可以了

anim.addListener(new AnimatorListenerAdapter() {  02.    @Override  03.    public void onAnimationEnd(Animator animation) {  04.    }  05.}); 

使用XML编写动画

玩过补间动画的朋友肯定都用过XML来实现补间动画的效果,那么既然补间动画可以使用XML布局来实现,那么作为属性动画,使用XML布局实现动画效果那是必须的。

只不过使用XML布局来编写动画相对于代码编写的属性动画可能在效率上会比较慢一点,但在重用方面比较方便,比如将某个通用的XML布局动画抽取到一个布局文件中,那么在整个项目中我们都可以使用这个布局动画

如果想要使用布局动画,首先需要我们在res下的drawable文件夹下新建一个animator文件夹,所有关于动画的布局文件都应该放在这个文件夹下,然后使用属性动画我们可以在animator文件夹下使用三种标签:

  • animator 标签 对应代码中的ValueAnimator
  • objectAnimator 标签 对应代码中的ObjectAnimator
  • set 标签 对应代码中的AnimatorSet
那么比如说我们想要实现一个从0100平滑过渡的动画,在XML当中就可以这样写:<animator xmlns:android="http://schemas.android.com/apk/res/android"  android:valueFrom="0"  android:valueTo="100"  android:valueType="intType"/> 

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

01.<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  android:valueFrom="1"  android:valueTo="0"  android:valueType="floatType"  android:propertyName="alpha"/> 

其实XML编写动画在可读性方面还是挺高的,上面的内容相信不用我做解释大家也都看得懂吧。

另外,我们也可以使用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实现的效果和我们刚才通过代码来实现的组合动画的效果是一模一样的,每个参数的含义都非常清楚,相信大家都是一看就懂,我就不再一一解释了。

最后XML文件是编写好了,那么我们如何在代码中把文件加载进来并将动画启动呢?只需调用如下代码即可:

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

调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用start()方法启动动画就可以了,就是这么简单。

好的,通过本篇文章的学习,我们对属性动画已经有了颇为深刻的认识,那么本篇文章的内容到此为止,下篇文章当中将会介绍更多关于属性动画的其它技巧,感兴趣的朋友请继续阅读 Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

相关文章:
1、 Android 属性动画(Property Animation) 完全解析 (上)
2、Android 属性动画(Property Animation) 完全解析 (下)

Demo下载地址,戳这里,不要积分的噢!

0 0
原创粉丝点击