Android属性动画(一)之基本用法

来源:互联网 发布:日语教材 知乎 编辑:程序博客网 时间:2024/05/01 21:43

转载声明:http://blog.csdn.net/lmj623565791/article/details/38067475
本篇文章主要是结合鸿洋大神的博客和《Android开发艺术探索》所做的笔记。

一丶概述

属性动画是API 11新加入的特性,和View动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画,甚至还可以没有对象。属性动画还对动画效果进行了加强,不再像View动画那样只能支持四种简单的变换。比如:你希望View有一个颜色的切换动画;你希望可以使用3D旋转动画;你希望当动画停止时,View的位置就是当前的位置;这些View动画都无法做到。这就是属性动画(Property Animation)产生的原因。
属性动画可以对任意对象的属性进行动画而不仅仅是View,动画默认时间间隔是300ms,默认帧率10ms/帧。其可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。但是属性动画是API 11才有的,所以要兼容以前的版本,需要使用nineoldandroids,网址是:http://nineoldandroids.com,使用方法和原生的android.animation.*中类的功能完全一样。
总结:既然View动画已经比较健全,为什么还要引入属性动画?
答:①View动画只能作用在View上,无法对一个非View的对象进行动画操作。View动画还有一个缺陷,就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?很遗憾,我们只能靠自己去实现了。说白了,View动画机制就是使用硬编码的方式来完成的,功能限定死就是这些,基本上没有任何扩展性可言。
②View动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。比如说,现在屏幕的左上角有一个按钮,然后我们通过View动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。

二丶相关API

Property Animation故名思议就是通过动画的方式改变对象的属性了,我们首先需要了解几个属性:
Duration动画的持续时间,默认300ms。
Time interpolation:时间差值,乍一看不知道是什么,但是我说LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定义动画的变化率。
Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。
Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。
Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。
相关的类
ObjectAnimator 动画的执行类,后面详细介绍
ValueAnimator 动画的执行类,后面详细介绍
AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
AnimatorInflater 用户加载属性动画的xml文件
TypeEvaluator 类型估值,主要用于设置动画操作属性的值。
TimeInterpolator 时间插值,上面已经介绍。
总的来说,属性动画就是,动画的执行类来设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性。

三丶ObjectAnimator实现动画

对于ObjectAnimator,提供了ofInt、ofFloat、ofObject。属性动画的运行机制是不断对值进行操作实现的,我们需要给ObjectAnimator 提供初始值和结束值,并且告诉它动画所需运行的时长,而初始值和结束值之间的动画过渡就是由ObjectAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡。

public static ObjectAnimator ofFloat(Object target, String propertyName, float… values)

  1. 这几个方法都是设置动画的元素丶作用的属性丶动画开始丶结束以及中间的任意属性。当对于属性值,即values参数,当只设置一个的时候,会认为设置的值为终点;如果设置两个,两个一个为开始,一个为结束。
  2. 动画更新的过程中,会不断调用setPropName更新元素的属性,所有使用ObjectAnimator更新某个属性,必须得有getter(设置一个属性值的时候)和setter方法。
  3. 对于ofFloat的参数,第一是我们要操作的对象,第二个参数是我们要操作的属性,比如:alpha、rotation、translationX和scaleY等,通过这几个值分别可以完成淡入淡出、旋转、水平移动、垂直缩放这几种动画。但其实,第二个参数可以是任意值,因为ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法。
  4. 如果我们想让一张图片的透明度从1变到0再变到1,那么可以这样写
animation.ofFloat(view, "alpha", 1.0F, 0.5F,1.0F)

下面是一个小Demo:
* activity_main布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:id="@+id/id_container" >    <ImageView        android:id="@+id/id_ball"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:src="@mipmap/mv"        android:scaleType="centerCrop"        android:onClick="rotateyAnimRun"        /></RelativeLayout>  

很简单,就一张妹子图片~

  • MainActivity
package com.wangjian.propertyanim;import android.animation.ObjectAnimator;import android.app.Activity;import android.os.Bundle;import android.view.View;public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void rotateyAnimRun(View view) {        ObjectAnimator//                .ofFloat(view, "rotationX", 0.0F, 360.0F)//                .setDuration(500)//                .start();    }}

运行效果:
这里写图片描述

动画更新的过程需要不断重绘,如果你操作对象的该属性方法里面,比如上例的setRotationX如果内部没有调用view的重绘,则你需要自己按照下面方式手动调用(ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法。)。

anim.addUpdateListener(new AnimatorUpdateListener()          {              @Override              public void onAnimationUpdate(ValueAnimator animation)              {  //              view.postInvalidate();  //              view.invalidate();              }          });  

看了上面的例子,因为设置的操作的属性只有一个,那么如果我希望一个动画能够让View既可以缩小、又能够淡出(3个属性scaleX,scaleY,alpha),只使用ObjectAnimator咋弄?
想法是不是很不错,可能会说使用AnimatorSet啊,这一看就是一堆动画塞一起执行,但是我偏偏要用一个ObjectAnimator实例实现呢~下面看代码:

    public void rotateyAnimRun(final View view) {        ObjectAnimator anim = ObjectAnimator//                .ofFloat(view, "zhy", 1.0F, 0.5F,1.0F)//                .setDuration(500);//        anim.start();        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                float cVal = (Float) animation.getAnimatedValue();                Log.e("TAG",cVal+"");                view.setAlpha(cVal);                view.setScaleX(cVal);                view.setScaleY(cVal);            }        });    }

把设置属性的那个字符串,随便写一个该对象没有的属性(这里设置的是”zhy”),就是不管~~咱们只需要它按照时间插值和持续时间计算的那个值,我们自己手动调用。我们手动修改了alpha丶scaleX和scaleY属性,实现了同时更改三个属性的效果。

运行结果:
这里写图片描述

现在来理一下这个代码:
这里我们通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,每播放一帧动画,就会回调一次,这是一个非常重要的回调方法,在这个回调方法中可以实现很多绚丽功能。在回调方法中,我们调用了getAnimatedValue()方法,并且打印了它的值,打印结果如下:

这里写图片描述

从打印结果可以看出cVal的值是从1.0变化到0.5,再从0.5变化到了1.0,这就对了,我们上面有这么一段代码:

        ObjectAnimator anim = ObjectAnimator//                .ofFloat(view, "zhy", 1.0F, 0.5F,1.0F)//                .setDuration(500);//

这说明cVal的值就是当前动画所对应的值,并且在回调方法中我们为view设置了新的alpha丶scaleX和scaleY,也就是说alpha丶scaleX和scaleY也是从1.0到0.5再到1.0,和“zhy”这个属性的变化频率是相同的,当然”zhy”这个属性是不存在的,我们使用它的主要作用就是为了获取当前动画的进度。

四丶ValueAnimator实现动画

ValueAnimator要实现动画,使用方法和ObjectAnimator类似,并且ValueAnimator是ObjectAnimator的基类。ObjectAnimator底层的动画实现机制也是基于ValueAnimator来完成的,既然是继承关系,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它们的用法也非常类似。
ValueAnimator可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
比如ValueAnimator要实现垂直移动300,代码如下:

public void verticalRun(View view)      {          ValueAnimator animator = ValueAnimator.ofFloat(0f, 300f);          animator.setTarget(mBlueBall);          animator.setDuration(1000).start();      }  

仔细观察你会发现ValueAnimator的ofFloat方法中并没有指定我们操作的是哪个属性,这点是不是和上面我们用ObjectAnimator实现的那个能够让View既可以缩小、又能够淡出(3个属性scaleX,scaleY,alpha)的动画比较相似,在那个动画中,我们虽然指定了一个”zhy”的属性,但是我们知道并没有这个属性,这点和ValueAnimator很像。
如果只运行上面这段代码,那么并不会实现垂直移动,它所做的仅仅是实现了1000ms内将值从0变化到300的动画,要想实现真正的垂直移动,那么需要借助监听器,即anim.addUpdateListener()。
那么,ValueAnimator和ObjectAnimator区别是什么呢?
ValueAnimator并没有在属性上做操作,所以并不需要操作的对象的属性一定要有getter和setter方法,你可以自己根据当前动画的计算值,来操作任何属性。而ObjectAnimator对属性做操作(其实是对属性的set和get方法做操作),必须得有getter(设置一个属性值的时候)和setter方法。

下面是ValueAnimator的一些具体用法的实例:

  • 布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/id_container"    android:layout_width="match_parent"    android:layout_height="match_parent"    >    <ImageView        android:id="@+id/id_ball"        android:layout_width="50dp"        android:layout_height="50dp"        android:src="@mipmap/bol_blue" />    <LinearLayout        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:orientation="horizontal">        <Button            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:onClick="verticalRun"            android:text="垂直" />        <Button            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:onClick="paowuxian"            android:text="抛物线" />    </LinearLayout></RelativeLayout>  

左上角一个小球,底部两个按钮~我们先看一个垂直匀速运动的代码:

package com.wangjian.propertyanim;import android.animation.ValueAnimator;import android.app.Activity;import android.content.Context;import android.os.Bundle;import android.util.Log;import android.view.View;import android.view.WindowManager;import android.widget.ImageView;public class MainActivity extends Activity {    private ImageView mBlueBall;    private int mScreenHeight;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mBlueBall = (ImageView) findViewById(R.id.id_ball);        WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);        mScreenHeight = wm.getDefaultDisplay().getHeight();    }    /**     * 垂直匀速移动     * @param view     */    public void verticalRun( View view)    {        ValueAnimator animator = ValueAnimator.ofFloat(0, mScreenHeight                - mBlueBall.getHeight());        animator.setTarget(mBlueBall);        animator.setDuration(1000).start();//      animator.setInterpolator(value)        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()        {            @Override            public void onAnimationUpdate(ValueAnimator animation)            {                mBlueBall.setTranslationY((Float) animation.getAnimatedValue());            }        });    }}

与ObjectAnimator不同的就是我们自己设置元素属性的更新~虽然多了几行代码,但是貌似提高灵活性。通过代码可以发现,我们通过ValueAnimator.ofFloat(0, mScreenHeight- mBlueBall.getHeight())先设置一个将值从0变化到屏幕高度的一个动画,我们可以将这个变化理解为数学中的X轴,那么Y轴就是球在某刻所在的位置,即translationY属性,它与X轴的变化率是一样的,即1:1,下图就是变化图:
这里写图片描述

这样就实现了小球的垂直匀速移动,运行效果如下:
这里写图片描述

至于抛物线效果后面再说。

五丶监听动画的事件

属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:AnimatorUpdateListener和AnimatorListener,对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们需要知道动画如何结束。
所以我们可以添加一个动画的AnimatorListener监听:

public void fadeOut(View view)      {          ObjectAnimator anim = ObjectAnimator.ofFloat(mBlueBall, "alpha", 0.5f);          anim.addListener(new AnimatorListener()          {              @Override              public void onAnimationStart(Animator animation)              {                  Log.e(TAG, "onAnimationStart");              }              @Override              public void onAnimationRepeat(Animator animation)              {                  // TODO Auto-generated method stub                  Log.e(TAG, "onAnimationRepeat");              }              @Override              public void onAnimationEnd(Animator animation)              {                  Log.e(TAG, "onAnimationEnd");                  ViewGroup parent = (ViewGroup) mBlueBall.getParent();                  if (parent != null)                      parent.removeView(mBlueBall);              }              @Override              public void onAnimationCancel(Animator animation)              {                  // TODO Auto-generated method stub                  Log.e(TAG, "onAnimationCancel");              }          });          anim.start();      }  

但是这样添加动画监听有一个弊端,那就是我们有的时候只需要监听动画结束就好了,没必要重写所有的方法,那么可以使用AnimatorListenerAdapter,AnimatorListenerAdapter继承了AnimatorListener接口,然后空实现了所有的方法。

anim.addListener(new AnimatorListenerAdapter()  {      @Override      public void onAnimationEnd(Animator animation)      {          Log.e(TAG, "onAnimationEnd");          ViewGroup parent = (ViewGroup) mBlueBall.getParent();          if (parent != null)              parent.removeView(mBlueBall);      }  });  

也可以添加一个addUpdateListener监听:

 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()        {            @Override            public void onAnimationUpdate(ValueAnimator animation){            }        });

并且,animator还有cancel()和end()方法:cancel动画立即停止,停在当前的位置;end动画直接到最终状态。

六丶AnimatorSet的使用

实例:

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"       android:id="@+id/id_container"      >      <ImageView          android:id="@+id/id_ball"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_centerInParent="true"          android:src="@drawable/bol_blue" />      <LinearLayout          android:layout_width="fill_parent"          android:layout_height="wrap_content"          android:layout_alignParentBottom="true"          android:orientation="horizontal" >          <Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:onClick="togetherRun"              android:text="简单的多动画Together" />          <Button              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:onClick="playWithAfter"              android:text="多动画按次序执行" />      </LinearLayout>  </RelativeLayout>  
  • MainActivity
package com.wangjian.propertyanim;import android.animation.AnimatorSet;import android.animation.ObjectAnimator;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.view.animation.LinearInterpolator;import android.widget.ImageView;public class MainActivity extends Activity {    private ImageView mBlueBall;    private int mScreenHeight;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mBlueBall = (ImageView) findViewById(R.id.id_ball);    }    public void togetherRun(View view)    {        ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",                1.0f, 2f);        ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",                1.0f, 2f);        AnimatorSet animSet = new AnimatorSet();        animSet.setDuration(2000);        animSet.setInterpolator(new LinearInterpolator());        //两个动画同时执行        animSet.playTogether(anim1, anim2);        animSet.start();    }    public void playWithAfter(View view)    {        float cx = mBlueBall.getX();        ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX",                1.0f, 2f);        ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY",                1.0f, 2f);        ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall,                "x",  cx ,  0f);        ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall,                "x", cx);        /**         * anim1,anim2,anim3同时执行         * anim4接着执行         */        AnimatorSet animSet = new AnimatorSet();        animSet.play(anim1).with(anim2);        animSet.play(anim2).with(anim3);        animSet.play(anim4).after(anim3);        animSet.setDuration(1000);        animSet.start();    }}

写了两个效果:
第一:使用playTogether两个动画同时执行,当然还有playSequentially依次执行~~
第二:如果我们有一堆动画,如何使用代码控制顺序,比如1,2同时;3在2后面;4在1之前等~就是效果2了
有一点注意:animSet.play().with();也是支持链式编程的,但是不要想着狂点,比如 animSet.play(anim1).with(anim2).before(anim3).before(anim5); 这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行:

运行效果:
这里写图片描述

0 0