android动画---ObjectAnimator基本使用

一、使用objectAnimator实现下图的效果(不会做gif图)

点击前:

点击后

方法介绍:

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

第一个参数用于指定这个动画要操作的是哪个控件

第二个参数用于指定这个动画要操作这个控件的哪个属性

第三个参数是可变长参数,这个就跟ValueAnimator中的可变长参数的意义一样了,就是指这个属性值是从哪变到哪。

activity_main.xml

<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"
    tools:context=".MainActivity"
    android:gravity="bottom|center_horizontal">
    <Button
        android:id="@+id/menu"
        style="@style/MenuStyle"
        android:background="@mipmap/ic_launcher" />

    <Button
        android:id="@+id/item1"
        style="@style/MenuItemStyle"
        android:background="@mipmap/logo"
        android:visibility="gone" />

    <Button
        android:id="@+id/item2"
        style="@style/MenuItemStyle"
        android:background="@mipmap/logo"
        android:visibility="gone" />

    <Button
        android:id="@+id/item3"
        style="@style/MenuItemStyle"
        android:background="@mipmap/logo"
        android:visibility="gone" />

    <Button
        android:id="@+id/item4"
        style="@style/MenuItemStyle"
        android:background="@mipmap/logo"
        android:visibility="gone" />

    <Button
        android:id="@+id/item5"
        style="@style/MenuItemStyle"
        android:background="@mipmap/logo"
        android:visibility="gone" />

</RelativeLayout>

MainActivity.java

public class MainActivity extends ActionBarActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    private Button mMenuButton;
    private Button mItemButton1;
    private Button mItemButton2;
    private Button mItemButton3;
    private Button mItemButton4;
    private Button mItemButton5;

    private boolean mIsMenuOpen = false;
    private int len = 200;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    private void initView() {
        mMenuButton = (Button) findViewById(R.id.menu);
        mMenuButton.setOnClickListener(this);

        mItemButton1 = (Button) findViewById(R.id.item1);
        mItemButton1.setOnClickListener(this);

        mItemButton2 = (Button) findViewById(R.id.item2);
        mItemButton2.setOnClickListener(this);

        mItemButton3 = (Button) findViewById(R.id.item3);
        mItemButton3.setOnClickListener(this);

        mItemButton4 = (Button) findViewById(R.id.item4);
        mItemButton4.setOnClickListener(this);

        mItemButton5 = (Button) findViewById(R.id.item5);
        mItemButton5.setOnClickListener(this);
    }

    /**
     * 打开菜单的动画
     * @param view 执行动画的view
     * @param index view在动画序列中的顺序
     * @param total 动画序列的个数
     * @param radius 动画半径
     */
    private void doAnimateOpen(View view, int index, int total, int radius) {
        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        }
        double degree = Math.PI * index / ((total - 1) ); // 计算每个button移动的角度
        int translationX = (int) (radius * Math.cos(degree)); // 计算每个button在x轴移动的距离
        int translationY = -(int) (radius * Math.sin(degree)); // 计算每个button在-y轴移动的距离
        Log.d(TAG, String.format("degree=%f, translationX=%d, translationY=%d",
                degree, translationX, translationY));
        AnimatorSet set = new AnimatorSet();
        //包含平移、缩放和透明度动画
        set.playTogether(
                // 移动
                ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
                ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
                // 缩放
                ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
                ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
                // 透明度
                ObjectAnimator.ofFloat(view, "alpha", 0f, 1));
        //动画周期为500ms
        set.setDuration(1 * 500).start();
    }

    /**
     * 关闭菜单的动画
     * @param view 执行动画的view
     * @param index view在动画序列中的顺序
     * @param total 动画序列的个数
     * @param radius 动画半径
     */
    private void doAnimateClose(final View view, int index, int total,
                                int radius) {
        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        }
        double degree = Math.PI * index / ((total - 1));// 计算每个button移动的角度
        int translationX = (int) (radius * Math.cos(degree));// 计算每个button在x轴移动的距离
        int translationY = -(int) (radius * Math.sin(degree));// 计算每个button在-y轴移动的距离
        Log.d(TAG, String.format("degree=%f, translationX=%d, translationY=%d",
                degree, translationX, translationY));
        AnimatorSet set = new AnimatorSet();
        //包含平移、缩放和透明度动画
        set.playTogether(
                // 移动
                ObjectAnimator.ofFloat(view, "translationX", translationX, 0),
                ObjectAnimator.ofFloat(view, "translationY", translationY, 0),
                // 缩放
                ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f),
                ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f),
                // 透明度
                ObjectAnimator.ofFloat(view, "alpha", 1f, 0f));
        //为动画加上事件监听,当动画结束的时候,我们把当前view隐藏
        set.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                view.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
            }
        });

        set.setDuration(1 * 500).start();
    }

    @Override
    public void onClick(View v) {
        if (v == mMenuButton) {
            if (!mIsMenuOpen) {
                mIsMenuOpen = true;
                doAnimateOpen(mItemButton1, 0, 5, len);
                doAnimateOpen(mItemButton2, 1, 5, len);
                doAnimateOpen(mItemButton3, 2, 5, len);
                doAnimateOpen(mItemButton4, 3, 5, len);
                doAnimateOpen(mItemButton5, 4, 5, len);
            } else {
                mIsMenuOpen = false;
                doAnimateClose(mItemButton1, 0, 5, len);
                doAnimateClose(mItemButton2, 1, 5, len);
                doAnimateClose(mItemButton3, 2, 5, len);
                doAnimateClose(mItemButton4, 3, 5, len);
                doAnimateClose(mItemButton5, 4, 5, len);
            }

        } else {
            Toast.makeText(this, "你点击了" + v, Toast.LENGTH_SHORT).show();
        }

    }
}

styles.xml

  <style name="MenuStyle">
        <item name="android:layout_width">50dp</item>
        <item name="android:layout_height">50dp</item>
    </style>

    <style name="MenuItemStyle">
        <item name="android:layout_width">45dp</item>
        <item name="android:layout_height">45dp</item>
    </style>

二、ObjectAnimator动画原理

在这张图中,将ValueAnimator的动画流程与ObjectAnimator的动画流程做了个对比。

可以看到ObjectAnimator的动画流程中,也是首先通过加速器产生当前进度的百分比,然后再经过Evaluator生成对应百分比所对应的数字值。这两步与ValueAnimator是完全一样的,唯一不同的是最后一步,在ValueAnimator中,我们要通过添加监听器来监听当前数字值。而在ObjectAnimator中,则是先根据属性值拼装成对应的set函数的名字,比如这里的scaleY的拼装方法就是将属性的第一个字母强制大写后,与set拼接,所以就是setScaleY。然后通过反射找到对应控件的setScaleY(float scaleY)函数,将当前数字值做为setScaleY(float scale)的参数将其传入。

这里在找到控件的set函数以后,是通过反射来调用这个函数的。

这就是ObjectAnimator的流程,最后一步总结起来就是调用对应属性的set方法,将动画当前数字值做为参数传进去。

根据上面的流程,这里有几个注意事项:

(1)、拼接set函数的方法:上面我们也说了是首先是强制将属性的第一个字母大写,然后与set拼接,就是对应的set函数的名字。注意,只是强制将属性的第一个字母大写,后面的部分是保持不变的。反过来,如果我们的函数名命名为setScalePointX(float ),那我们在写属性时可以写成“scalePointX”或者写成“ScalePointX”都是可以的,即第一个字母大小写可以随意,但后面的部分必须与set方法后的大小写保持一致。

(2)、如何确定函数的参数类型:上面我们知道了如何找到对应的函数名,那对应的参数方法的参数类型如何确定呢?我们在讲ValueAnimator的时候说过,动画过程中产生的数字值与构造时传入的值类型是一样的。由于ObjectAnimator与ValueAnimator在插值器和Evaluator这两步是完全一样的,而当前动画数值的产生是在Evaluator这一步产生的,所以ObjectAnimator的动画中产生的数值类型也是与构造时的类型一样的。那么问题来了,像我们的构造方法。

ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleY", 0, 3, 1);  

由于构造时使用的是ofFloat函数,所以中间值的类型应该是Float类型的,所以在最后一步拼装出来的set函数应该是setScaleY(float xxx)的样式;这时,系统就会利用反射来找到setScaleY(float xxx)函数,并把当前的动画数值做为参数传进去。

那问题来了,如果没有类似setScaleY(float xxx)的函数,我们只实现了一个setScaleY(int xxx)的函数怎么办?这里虽然函数名一样,但参数类型是不一样的,那么系统就会报一个错误:

意思就是对应函数的指定参数类型没有找到。

(3)、调用set函数以后怎么办?从ObjectAnimator的流程可以看到,ObjectAnimator只负责把动画过程中的数值传到对应属性的set函数中就结束了,注意传给set函数以后就结束了!set函数就相当我们在ValueAnimator中添加的监听的作用,set函数中的对控件的操作还是需要我们自己来写的。

那我们来看看View中的setScaleY是怎么实现的吧:

/**
 * Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
 * the view‘s unscaled width. A value of 1 means that no scaling is applied.
 *
 * @param scaleY The scaling factor.
 * @see #getPivotX()
 * @see #getPivotY()
 *
 * @attr ref android.R.styleable#View_scaleY
 */
public void setScaleY(float scaleY) {
    ensureTransformationInfo();
    final TransformationInfo info = mTransformationInfo;
    if (info.mScaleY != scaleY) {
        invalidateParentCaches();
        // Double-invalidation is necessary to capture view‘s old and new areas
        invalidate(false);
        info.mScaleY = scaleY;
        info.mMatrixDirty = true;
        mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
        invalidate(false);
    }
} 

大家不必理解这一坨代码的意义,因为这些代码是需要读懂View的整体流程以后才能看得懂的,只需要跟着我的步骤来理解就行。这段代码总共分为两部分:第一步重新设置当前控件的参数,第二步调用Invalidate()强制重绘;

所以在重绘时,控件就会根据最新的控件参数来绘制了,所以我们就看到当前控件被缩放了。

(4)、set函数调用频率是多少:由于我们知道动画在进行时,每隔十几毫秒会刷新一次,所以我们的set函数也会每隔十几毫秒会被调用一次。

讲了这么多,就是为了强调一点:ObjectAnimator只负责把当前运动动画的数值传给set函数。至于set函数里面怎么来做,是我们自己的事了。

好了,在知道了ObjectAnimator的原理以后,下面就来看看如何自定义一个ObjectAnimator的属性吧。


三、自定义ObjectAnimator属性

上面我们已经看了使用View自带的set函数所对应属性的方法,而且理解了ObjectAnimator的动画实现原理,下面我们来自定义一个属性来看看实现效果吧。

我们在开始之前再来捋一下ObjectAnimator的动画设置流程:ObjectAnimator需要指定操作的控件对象,在开始动画时,到控件类中去寻找设置属性所对应的set函数,然后把动画中间值做为参数传给这个set函数并执行它。

所以,我们说了,控件类中必须所要设置属性所要对应的set函数。所以为了自由控制控件的实现,我们这里自定义一个控件。大家知道在这个自定义控件中,肯定存在一个set函数与我们自定义的属性相对应。

我们先来看看这段要实现的效果:

1、保存圆形信息类——Point

为了保存圆形的信息,我们先定义一个类:(Point.java)

public class Point {
    private int mRadius;  

    public Point(int radius){
        mRadius = radius;
    }  

    public int getRadius() {
        return mRadius;
    }  

    public void setRadius(int radius) {
        mRadius = radius;
    }
}  

这个类很好理解,只有一个成员变量mRadius,表示圆的半径。

2、自定义控件——MyPointView

然后我们自定义一个控件MyPointView,完整代码如下:

public class MyPointView extends View {
    private Point mPoint = new Point(100);  

    public MyPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }  

    @Override
    protected void onDraw(Canvas canvas) {
        if (mPoint != null){
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(300,300,mPoint.getRadius(),paint);
        }
        super.onDraw(canvas);
    }  

    void setPointRadius(int radius){
        mPoint.setRadius(radius);
        invalidate();
    }  

} 

在这段代码中,首先来看我们前面讲到的set函数:

void setPointRadius(int radius){
        mPoint.setRadius(radius);
        invalidate();
    } 
  • 这个set函数所对应的属性应该是pointRadius或者PointRadius。前面我们已经讲了第一个字母大小写无所谓,后面的字母必须保持与set函数完全一致。
  • 在setPointRadius中,先将当前动画传过来的值保存到mPoint中,做为当前圆形的半径。然后强制界面刷新在界面刷新后,就开始执行onDraw()函数。

3、使用MyPointView

首先,在MyActivity的布局中添加MyPointView的使用(main.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:orientation="vertical"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">  

    <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:padding="10dp"
            android:text="start anim"
            />  

    <Button
            android:id="@+id/btn_cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:padding="10dp"
            android:text="cancel anim"
            />
    <TextView
            android:id="@+id/tv"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:gravity="center"
            android:padding="10dp"
            android:background="#ffff00"
            android:text="Hello qijian"/>  

    <com.example.BlogObjectAnimator1.MyPointView
            android:id="@+id/pointview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/tv"/>  

</RelativeLayout>  

布局代码很好理解,根据效果图中的布局效果来理解,非常容易,就不再多讲

然后看看在MyActivity中,点击start anim后的处理方法:

public class MyActivity extends Activity {
    private Button btnStart;
    private MyPointView mPointView;  

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);  

        btnStart = (Button) findViewById(R.id.btn);
        mPointView = (MyPointView)findViewById(R.id.pointview);  

        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                doPointViewAnimation();
            }
        });
    }
  …………
} 

在点击start anim按钮后,开始执行doPointViewAnimation()函数,doPointViewAnimation()函数代码如下:

private void doPointViewAnimation(){
     ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius", 0, 300, 100);
      animator.setDuration(2000);
      animator.start();
}  

在这段代码中,着重看ObjectAnimator的构造方法,首先要操作的控件对象是mPointView,然后对应的属性是pointRadius,然后值是从0到300再到100;

所以在动画开始以后,ObjectAnimator就会实时地把动画中产生的值做为参数传给MyPointView类中的setPointRadius(int radius)函数,然后调用setPointRadius(int radius)。由于我们在setPointRadius(int radius)中实时地设置圆形的半径值然后强制重绘当前界面,所以可以看到圆形的半径会随着动画的进行而改变。

四、注意——何时需要实现对应属性的get函数

我们再来看一下ObjectAinimator的下面三个构造方法:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
public static ObjectAnimator ofInt(Object target, String propertyName, int... values)
public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values)  

前面我们已经分别讲过三个函数的使用方法,在上面的三个构造方法中最后一个参数都是可变长参数。我们也讲了,他们的意义就是从哪个值变到哪个值的。

那么问题来了:前面我们都是定义多个值,即至少两个值之间的变化,那如果我们只定义一个值呢,如下面的方式:(同样以MyPointView为例)

ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);  

从效果图中看起来是从0开始的,但是看log可以看出来已经在出警告了:

我们点了三次start anim按钮,所以这里也报了三次,意思就是没找到pointRadius属性所对应的getPointRadius()函数;

仅且仅当我们只给动画设置一个值时,程序才会调用属性对应的get函数来得到动画初始值。如果动画没有初始值,那么就会使用系统默认值。比如ofInt()中使用的参数类型是int类型的,而系统的Int值的默认值是0,所以动画就会从0运动到100;也就是系统虽然在找到不到属性对应的get函数时,会给出警告,但同时会用系统默认值做为动画初始值。

如果通过给自定义控件MyPointView设置了get函数,那么将会以get函数的返回值做为初始值:

public class MyPointView extends View {
    private Point mPoint = new Point(100);  

    public MyPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }  

    @Override
    protected void onDraw(Canvas canvas) {
        if (mPoint != null){
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(300,300,mPoint.getRadius(),paint);
        }
        super.onDraw(canvas);
    }  

    public int getPointRadius(){
        return 50;
    }  

    public void setPointRadius(int radius){
        mPoint.setRadius(radius);
        invalidate();
    }  

} 

我们在这里添加了getPointRadius函数,返回值是Int.有些同学可能会疑惑:我怎么知道这里要返回int值呢?

我们前面说过当且仅当我们在创建ObjectAnimator时,只给他传递了一个过渡值的时候,系统才会调用属性对应的get函数来得到动画的初始值!所以做为动画的初始值,那么在创建动画时过渡值传的什么类型,这里的get函数就要返回类型

public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values)  

比如上面的ofObject,get函数所返回的类型就是与最后一个参数Object… values,相同类型的。

在我们在MyPointView添加上PointRadius所对应的get函数以后重新执行动画:

ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);
animator.setDuration(2000);
animator.start();  

从动画中可以看出,半径已经不是从0开始的了,而是从50开始的。

最后我们总结一下:当且仅当动画的只有一个过渡值时,系统才会调用对应属性的get函数来得到动画的初始值。

参考内容:

http://blog.csdn.net/harvic880925/article/details/50598322

http://blog.csdn.net/singwhatiwanna/article/details/17639987

时间: 2024-11-13 09:24:37

android动画---ObjectAnimator基本使用的相关文章

Android 动画 之 ObjectAnimator

android 3.0之后添加的一些动画   animator 中的 ObjectAnimator: 补间动画能实现的: 1.alpha 透明度 //第一个参数为 view对象,第二个参数为 动画改变的类型,第三,第四个参数依次是开始透明度和结束透明度. ObjectAnimator alpha = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f); alpha.setDuration(2000);//设置动画时间 alpha.setInter

android动画具体解释六 XML中定义动画

动画View 属性动画系统同意动画View对象并提供非常多比view动画系统更高级的功能.view动画系统通过改变绘制方式来变换View对象,view动画是被view的容器所处理的,由于View本身没有要操控的属性.结果就是View被动画了.但View对象本身并没有变化. 在Android3.0中,新的属性和对应的getter和setter方法被增加以克服此缺点. 属性动画系统能够通过改变View对象的真实属性来动画Views. 并且.View也会在其属性改变时自己主动调用invalidate(

Android开发艺术探索——第七章:Android动画深入分析

Android开发艺术探索--第七章:Android动画深入分析 Android的动画可以分成三种,view动画,帧动画,还有属性动画,其实帧动画也是属于view动画的一种,,只不过他和传统的平移之类的动画不太一样的是表现形式上有点不一样,view动画是通过对场景的不断图像交换而产生的动画效果,而帧动画就是播放一大段图片,很显然,图片多了会OOM,属性动画通过动态的改变对象的属性达到动画效果,也是api11的新特性,在低版本无法使用属性动画,但是我们依旧有一些兼容库,OK,我们还是继续来看下详细

android动画详解三 动画API概述

· 属性动画与view动画的不同之处 view动画系统提供了仅动画View 对象的能力,所以如果你想动画非View 对象,你就要自己实现代码. view动画系统实际上还被强制仅能对 View 的少数属性进行动画,比如缩放和旋转,而不能对背景色进行. view动画的另一个坏处是它仅修改View的绘制位置,而不是View的实际位置.例如,如果你动画一个移动穿越屏幕,button的绘制位置是正确的,但实际你可以点击它的位置却没有变,所以你必须去实现你自己的逻辑来处理它. 使用属性动画系统时,这个限制被

Android动画效果——1.帧动画2.补间动画3.跳转画面(三)

Android--动画效果1.帧动画2.补间动画3.跳转画面 插值器类 xml属性值 说明 LinearInterpolator @android:anim/linear_interpolatorr 动画以均匀的速度改变. AccelerateInterpolator @android:anim/accelerate_interpolator 在动画开始时改变速度较慢,然后开始加速. AccelerateDecelerateInterpolator @android:anim/accelerat

《Android动画高手成长记》跳跳球效果

在介绍本文动画效果实现之前,先来介绍属性动画相关的几个知识点. ValueAnimator与ObjectAnimator. Interpolator插值器与TypeEvaluator估值器. 在Android3.0之前,系统提供了两种动画效果实现方式,帧动画frame-by-frame animation和补间动画tweened animation.帧动画就是类似电影播放一样,将整部影片拆分成一片片的然后连贯起来播放.补间动画则可以实现对view的缩放.平移.旋转等操作.在3.0之后,出现了一种

Android 动画系列

Android种最常用的动画: ~1~Tween动画,就是对场景里的对象不断的进行图像变化来产生动画效果(旋转.平移.放缩和渐变) Tweene Animations 主要类: Animation   动画 AlphaAnimation 渐变透明度 RotateAnimation 画面旋转 ScaleAnimation 渐变尺寸缩放 TranslateAnimation 位置移动 AnimationSet  动画集 以自定义View为例,该View很简单,画面上只有一个图片. 现在我们要对整个V

Android动画学习笔记-Android Animation

转自:http://www.cnblogs.com/angeldevil/archive/2011/12/02/2271096.html 3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中又引入了一个新的动画系统:property animation,这三种动画模式在SDK中被称为property animation,view animation,drawable animation. 可通过NineOldAndr

Android 动画详解

这次主要就介绍android动画,android动画目前分为三种形式,Tween Animation 这个只能应用于view对象上面的,Drawable Animation这个是帧动画,就是类似我们有一些列的图片依次播放图片时出现的动画,Property Animation 这个是属性动画,这也是在android3.0之后引进的动画,在手机的版本上是android4.0就可以使用这个动画,下面我们主要就是针对这三种情况进行介绍. Tween Animation 这个动画在Property Ani