第七章Andorid动画深入分析(Android开发艺术探索)

Android 平台提供了一套完整的动画框架

在Android3.0之前有两种动画,一种方式是补间动画 Tween Animation、另一种叫逐帧动画 Frame Animation(也称Drawable Animation );

Android3.0以后增加了属性动画 Property Animation

Tween Animation、Frame Animation只能用于View,被归类为View
Animation。

所以Andorid的动画分为2种:

View动画(View Animation)、属性动画(Property Animation)

7.1、View动画

7.1.1、View动画的种类
View动画的四种变换效果对应着Animation的四个子类:

TranslateAnimation(平移<transalte>)
ScaleAnimatino(缩放<scale>)
RotateAnimation(旋转<rotate>)
AlphaAnimation(透明<alpha>)

要使用view的动画,首先要在res/anim下面创建动画xml
基本属性如下:

这里写图片描述

标签表示动画集合,对应AnimitionSet类,view可以是单个动画,也可以是多个动画的组合
它的两个属性:
android:interpolater:默认是@android:anim/accelerate_interpolator即加速减速插值器
android:shareInterpolater:表示集合中的动画是否和集合用一样的插值器,默认为true

1、TranslateAnimation(平移):

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal" >

    <translate
    //持续的时间
        android:duration="100"
    //x的起始值
        android:fromXDelta="0"
    //Y的起始值
        android:fromYDelta="0"
    //插值器
        android:interpolator="@android:anim/linear_interpolator"
    //x的结束值
        android:toXDelta="100"
    //Y的结束值
        android:toYDelta="100" />

</set>

2、ScaleAnimatino(缩放)

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true" >

    <scale
    //水平方向的缩放起始值
        android:fromXScale="0.5"
    //竖直方向的缩放起始值
        android:fromYScale="0.5"
    //水平方向的缩放结束值
        android:toXScale="1.2"
    //竖直方向的缩放结束值
        android:toYScale="1.2"
    //缩放的轴点的X坐标
        android:pivotX="100"
    //缩放的轴点的Y坐标
        android:pivotY="100"/>

</set>

注意:轴点默认在View的中心,这个时候横向缩放的话,左右都会缩放、纵向的话,上下都会缩放
这个时候可以设置轴点的位置,如果在最左边那么只会缩放右边,如果在最右边那么只会缩放左边

3、RotateAnimation(旋转)

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true" >

    <rotate
    //旋转开始的角度
        android:fromDegrees="0"
    //旋转结束的角度
        android:toDegrees="90"
    /旋转的轴点的X坐标
        android:pivotX="100"
    //旋转的轴点的Y坐标
        android:pivotY="100" />
</set>

注意:轴点默认在View的中心,即view的旋转中心,围绕着view中心点和view左上角,效果明显是不一样的

4、AlphaAnimation(透明)

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true" >

    <alpha
    //透明度的起始值
        android:fromAlpha="0.1"
    //透明度的结束值
        android:toAlpha="1"
/>

</set>

每个动画都有的公共常用属性:

动画的持续时间:
andorid:duration = "100"
动画结束以后是否停留在结束位置
andorid:fillAtler = "true",true表示停留,false表示不停留

拓展:

fillBefore是指动画结束时画面停留在此动画的第一帧;
fillAfter是指动画结束是画面停留在此动画的最后一帧。
Java代码设置如下:
/*****动画结束时,停留在最后一帧*********
setFillAfter(true);
setFillBefore(false);  


/*****动画结束时,停留在第一帧*********
setFillAfter(false);
setFillBefore(true);  


xml设置如下:
/******动画结束时,停留在最后一帧**********
<set Android:fillAfter="true" android:fillBefore="false">


/******动画结束时,停留在第一帧**********
<set android:fillAfter="false" android:fillBefore="true">

PS:xml设置在scale标签里面设置是无效的,注意是set标签

下面来一个例子:

res/drawable/translate

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true">

    <translate
    android:duration="100"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:interpolator="@android:anim/linear_interpolator"
    android:toXDelta="100"
    android:toYDelta="100" />

</set>

res/drawable/scale

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true">

    <scale
        android:fromXScale="0.5"
        android:fromYScale="0.5"
        android:pivotX="100"
        android:pivotY="100"
        android:toXScale="1.2"
        android:toYScale="1.2" />

</set>

res/drawable/rotate

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true" >

    <rotate
    android:fromDegrees="0"
    android:toDegrees="90"
    android:pivotX="100"
    android:pivotY="100" />
</set>

res/drawable/alpha

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true">

    <alpha
        android:fromAlpha="0.1"
        android:toAlpha="1" />

</set>

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context="com.zx.eventbus.AnimationActivity">

    <Button
        android:id="@+id/btn_tanslate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="让我们移动" />

    <Button
        android:id="@+id/btn_scale"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="让我们缩放" />

    <Button
        android:id="@+id/btn_rotate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="让我们旋转" />

    <Button
        android:id="@+id/btn_alpha"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="让我们透明" />
</LinearLayout>

Activity:

package com.zx.eventbus;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class AnimationActivity extends AppCompatActivity {

    @Bind(R.id.btn_tanslate)
    Button btnTanslate;
    @Bind(R.id.btn_scale)
    Button btnScale;
    @Bind(R.id.btn_rotate)
    Button btnRotate;
    @Bind(R.id.btn_alpha)
    Button btnAlpha;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation);
        ButterKnife.bind(this);
    }

    private void showAnimation(int anim, Button button) {
        Animation animation = AnimationUtils.loadAnimation(this, anim);
        button.startAnimation(animation);
    }


    @OnClick({R.id.btn_tanslate, R.id.btn_scale, R.id.btn_rotate, R.id.btn_alpha})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_tanslate:
                showAnimation(R.anim.translate, btnTanslate);
                break;
            case R.id.btn_scale:
                showAnimation(R.anim.scale, btnScale);
                break;
            case R.id.btn_rotate:
                showAnimation(R.anim.rotate, btnRotate);
                break;
            case R.id.btn_alpha:
                showAnimation(R.anim.alpha, btnAlpha);
                break;
        }
    }
}

效果图如下:
这里写图片描述

除了xml中定义的动画外,还可以通过代码来应用动画
如:

AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(300);
button.setAnimation(alphaAnimation);

其他动画同理

拓展:

通过Animation的setAnimationListener方法可以给view动画添加监听
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(300);
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
    }
    @Override
    public void onAnimationEnd(Animation animation) {
    }
    @Override
    public void onAnimationRepeat(Animation animation) {
    }
});

7.1.3、自定义View动画

实际开发中很少自定义view动画,只需要知道是矩阵变换的过程就好

7.1.4、帧动画

帧动画是顺序播放一组预定好的图片
Andorid系统为我们提供了一个类:AnimationDrawable
使用步骤:

1、定义一个xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">

   <item android:drawable="@drawable/img1" android:duration="500"/>
   <item android:drawable="@drawable/img2" android:duration="500"/>
   <item android:drawable="@drawable/img3" android:duration="500"/>

</animation-list>

2、让其作为view的背景即可

button.setBackgroundResource(R.drawable.animation);
AnimationDrawable animationDrawable = (AnimationDrawable) button.getBackground();
animationDrawable.start();

帧动画使用比较简单,但是容易引起oom,所以使用帧动画的时候避免使用尺寸较大的图片

7.2、View动画的特殊使用场景
7.2.1、LayoutAnimation

LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,常常被用在listview,recycleview上面,给ViewGroup指定一个动画,那么它的子元素就也有了这个动画
为了给viewGroup子元素加上出场效果,遵循如下几个步骤:
1、定义LayoutAnimation

<layoutAnimation
    xmlns:android="http://schemas.android.com/apk/res/android"
//开始动画的时间延迟
    android:delay="0.5"
//子元素动画的顺序,normal(顺序),reverse(逆向展示),random(随机展示)
    android:animationOrder="reverse"
//为子元素指定具体的入场动画
    android:animation="@anim/anim_item"/>

2、为子元素指定具体的入场

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true" >

    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

    <translate
        android:fromYDelta="500"
        android:toYDelta="0" />

</set>

3、为ViewGroup指定android:layoutAnimation

<ListView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/anim_layout"
    android:background="#fff4f7f9"
    android:cacheColorHint="#00000000"
    android:divider="#dddbdb"
    android:dividerHeight="1.0px"
    android:listSelector="@android:color/transparent" />

除了在XML中定义,我们还可以在代码中定义

Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listview.setLayoutAnimation(controller);

7.2.2、Activity的切换效果

通常我们使用:

Intent in = new Intent(this,DemoActivity_2.class);
startActivity(in);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);

在finish的时候:

@Override
public void finish() {
    super.finish();
    overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}

注意:overridePendingTransition必须在startActivity或者finish的后面

也可以在style.xml中设置

<item name="android:windowAnimationStyle">@style/SmoothWindowAnimation</item>

<style name="SmoothWindowAnimation">
    <!-- 打开Activity时的入场动画 -->
    <item name="android:activityOpenEnterAnimation">@anim/slide_right_enter</item>
    <!-- 打开Activity时的退场动画 -->
    <item name="android:activityOpenExitAnimation">@anim/slide_left_exit</item>
    <!-- 关闭Activity时的入场动画 -->
    <item name="android:activityCloseEnterAnimation">@anim/slide_left_enter</item>
    <!-- 关闭Activity时的退场动画 -->
    <item name="android:activityCloseExitAnimation">@anim/slide_right_exit</item>
</style>

7.3、属性动画

7.3.1、使用属性动画

属性动画是3.0以后引入的新动画,
常用的动画类有:ValueAnimator、ObjectAnimator、AnimatorSet
Property Animation可以定义在xml文件中
这些xml文件定义的文件路径如下: res/animator/filename.xm

基本用法如下:

<set
//together表示集合中的子动画同时播放,sequentially表示子动画顺序播放
android:ordering=["together" | "sequentially"]]]>

<objectAnimator
//子动画作用对象的名称
android:propertyName="string"
//动画的持续时长
android:duration="int"
//属性的起始值
android:valueFrom="float | int | color"
//属性的结束值
android:valueTo="float | int | color"
//动画播放前的延迟时间
android:startOffset="int"
//动画的重复次数,默认是0,-1表示无限循环
android:repeatCount="int"
//动画的重复模式,repeat连续从头到尾重复,
reverse逆向重复,第一次,从头到尾,第二次从尾到头,第三次从头到尾,第四次从尾到头(就像画椭圆一样)
android:repeatMode=["repeat" | "reverse"]
//表示android:propertyName所指定的类型
android:valueType=["intType" | "floatType"]/>

<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>

<set]]>
        ...
</set>
</set>

文件需要有根元素,可以使用, , or . 可以作为一个集合,而且集合中还可以存在元素。

同样也可以通过代码进行设置:

ObjectAnimator的使用:

ObjectAnimator.ofFloat();

ValueAnimator 的使用

ValueAnimator anim = ObjectAnimator.ofInt();
anim.start();

AnimatorSet 的使用

AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat();
ObjectAnimator.ofFloat();
ObjectAnimator.ofFloat();
);
set.setDuration(1000).start();

ValueAnimator是Property Animation系统的核心类,它包含了大部分api
objectAnimator继承ValueAnimator,是动画的拓展类

属性动画基本使用如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">

    <objectAnimator
        android:duration="300"
        android:propertyName="x"
        android:valueTo="200"
        android:valueType="intType" />

    <objectAnimator
        android:duration="300"
        android:propertyName="Y"
        android:valueTo="200"
        android:valueType="intType" />

</set>

使用如下代码调用:

AnimatorSet set = AnimationUtils.loadAnimation(this,R.animator.anim_item)
set.setTarget(button);
set.start();

在实际开发中我们建议用代码的实现属性动画,因为代码实现比较简单,更加重要的是
很多时候起始值是无法确定的,需要动态的创建动画,更加灵活

7.3.2、理解插值器和估值器

这里我们将了解2个知识点:Interpolate(插值器)和TypeEvaluator(估值器)

常用的插值器如下:

这里写图片描述

如果系统提供的插值器无法满足我们的需求,我们还可以自定义插值器:
步骤很简单:

步骤一:继承TimeInterpolator

步骤二:重写getInterpolation(float input)方法

代码如下:

public class CustomInterpolator implements TimeInterpolator {

    @Override
    public float getInterpolation(float input) {
        // 编写相关的逻辑计算  
        //input *= 0.8f;  
        return input * input;
    }
}

7.3.3、属性动画的监听器

Property Animation提供了
Animator.AnimatorListener和Animator.AnimatorUpdateListener两个监听器用于动画在播放过程中的重要动画事件。

下面是两个监听器接口和方法的一些介绍和说明:

Animator.AnimatorListen

onAnimationStart() —— 动画开始时调用;
onAnimationEnd() —— 动画结束时调用;
onAnimationRepeat() —— 动画循环播放时调用;
onAnimationCancel() —— 动画被取消时调用。不管终止的方式如何,被取消的动画仍然会调onAnimationEnd();

Animator.AnimatorUpdateListener:

onAnimationUpdate() —— 动画每播放一帧时调用。在动画过程中,可侦听此事件来获取并使用 ValueAnimator 计算出来的属性值。

利用传入事件的 ValueAnimator 对象,调用其 getAnimatedValue() 方法即可获取当前的属性值。如果使用 ValueAnimator来实现动画的话 ,则必需实现此侦听器。

7.3.4、对任意属性做动画

这里先提出一个问题:

给button加个动画,宽度从当前宽度增加200px,也许你会说用view动画搞定,但是当你做的时候你会发现view动画不支持对宽度进行动画,不过可以使用缩放动画试试:

效果如下:
这里写图片描述

这样的效果当然不是我们想要的效果,文本都被拉伸了。

下面我们使用属性动画来试试:

private void performAnimate() {
    ViewWrapper viewWrapper = new ViewWrapper(btnTanslate);
    ObjectAnimator.ofInt(viewWrapper, "width", 500).setDuration(1000).start();
}
private static class ViewWrapper {
    private View mTarget;
    public ViewWrapper(View target){
        this.mTarget = target;
    }

    public int getWidth(){
        return mTarget.getLayoutParams().width;
    }
    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

很显然效果达到了

拓展:

使用ValueAnimator实现动画的步骤及实践
那一般使用ValueAnimator实现动画分为以下七个步骤:
      1. 调用ValueAnimation类中的ofInt(int...values)ofFloat(String propertyName,float...values)等静态方法实例化ValueAnimator对象,并设置目标属性的属性名、初始值或结束值等值;
      2.调用addUpdateListener(AnimatorUpdateListener mListener)方法为ValueAnimator对象设置属性变化的监听器;
      3.创建自定义的Interpolator,调用setInterpolator(TimeInterpolator value)为ValueAniamtor设置自定义的Interpolator;(可选,不设置默认为缺省值)
      4.创建自定义的TypeEvaluator,调用setEvaluator(TypeEvaluator value)为ValueAnimator设置自定义的TypeEvaluator;(可选,不设置默认为缺省值)
      5.在AnimatorUpdateListener 中的实现方法为目标对象的属性设置计算好的属性值。
      6.设置动画的持续时间、是否重复及重复次数等属性;
      7.为ValueAnimator设置目标对象并开始执行动画。

代码如下:

/**
 * 使用ValueAnimator改变Imageview的margin的值 
 */
public void marginValueAnimator(){
    //1.调用ofInt(int...values)方法创建ValueAnimator对象  
    ValueAnimator mAnimator = ValueAnimator.ofInt(0,screenWidth - mImageViewTest.getWidth());
    //2.为目标对象的属性变化设置监听器  
    mAnimator.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 3.为目标对象的属性设置计算好的属性值  
            int animatorValue = (int)animation.getAnimatedValue();
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) mImageViewTest.getLayoutParams();
            marginLayoutParams.leftMargin = animatorValue;
            mImageViewTest.setLayoutParams(marginLayoutParams);
        }
    });
    //4.设置动画的持续时间、是否重复及重复次数等属性  
    mAnimator.setDuration(2000);
    mAnimator.setRepeatCount(3);
    mAnimator.setRepeatMode(ValueAnimator.REVERSE);
    //5.为ValueAnimator设置目标对象并开始执行动画  
    mAnimator.setTarget(mImageViewTest);
    mAnimator.start();
}

关于属性动画更多的介绍请参照

Andorid属性动画完全解析上、中、下

http://blog.csdn.net/guolin_blog/article/details/43536355
http://blog.csdn.net/guolin_blog/article/details/43816093
http://blog.csdn.net/guolin_blog/article/details/44171115

Andorid属性动画完全解析上、下
http://blog.csdn.net/lmj623565791/article/details/38067475
http://blog.csdn.net/lmj623565791/article/details/38092093
其他
http://blog.csdn.net/yegongheng/article/details/38435553
http://blog.csdn.net/xyz_lmn/article/details/38667899

拓展:
看了这么多例子,我不知道那些系统的propertyName一共有哪些,我看了下ObjectAnimator源码才发现,下面贴出来:

static {
    PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
    PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
    PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
    PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
    PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
    PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
    PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
    PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
    PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
    PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
    PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
    PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
    PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
    PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
}

有了这些,你传入对应的propertyName,就可以实现各种各样的功能啦

package com.nineoldandroids.animation;里面找到的,
package android.animation;包里面没有。

7.3.5、属性动画的工作原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你提供的开始值和最终值,多次调用set方法,每次传的值都不一样。

下面我们看源码分析:

1、我们通常使用都是ObjectAnimator.ofInt().start();
那我们就从start()方法看看,

@Override
public void start() {
    // See if any of the current active/pending animators need to be canceled
    AnimationHandler handler = sAnimationHandler.get();
    if (handler != null) {
        int numAnims = handler.mAnimations.size();
//判断当前动画是否重复
        for (int i = numAnims - 1; i >= 0; i--) {
            if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                    anim.cancel();
                }
            }
        }
        numAnims = handler.mPendingAnimations.size();
//判断等待动画是否重复
        for (int i = numAnims - 1; i >= 0; i--) {
            if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
                ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
                if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                    anim.cancel();
                }
            }
        }
        numAnims = handler.mDelayedAnims.size();
//判断延迟动画是否重复
        for (int i = numAnims - 1; i >= 0; i--) {
            if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
                ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
                if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                    anim.cancel();
                }
            }
        }
    }
    if (DBG) {
        Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
        for (int i = 0; i < mValues.length; ++i) {
            PropertyValuesHolder pvh = mValues[i];
            Log.d(LOG_TAG, "   Values[" + i + "]: " +
                pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                pvh.mKeyframes.getValue(1));
        }
    }
    super.start();
}

代码看似很多,其实做的事情很简单

首先判断
如果当前动画(mAnimations)、等待的动画(mPendingAnimations)、延迟动画(mDelayedAnims)它们之中只要有和当前动画相同的动画(通过instanceof 进行比较),那么就调用 anim.cancel();来取消当前的动画;

接下来是一段LOG日志

最后如果都不重复的话就直接调用父类的启动动画方法了(super.start());

因为ObjectAnimator继承ValueAnimator那么我们再来看看ValueAnimator的start()方法

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mPlayingBackwards = playBackwards;
    if (playBackwards && mSeekFraction != -1) {
        if (mSeekFraction == 0 && mCurrentIteration == 0) {
            // special case: reversing from seek-to-0 should act as if not seeked at all
            mSeekFraction = 0;
        } else if (mRepeatCount == INFINITE) {
            mSeekFraction = 1 - (mSeekFraction % 1);
        } else {
            mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
        }
        mCurrentIteration = (int) mSeekFraction;
        mSeekFraction = mSeekFraction % 1;
    }
    if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
            (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
        // if we were seeked to some other iteration in a reversing animator,
        // figure out the correct direction to start playing based on the iteration
        if (playBackwards) {
            mPlayingBackwards = (mCurrentIteration % 2) == 0;
        } else {
            mPlayingBackwards = (mCurrentIteration % 2) != 0;
        }
    }
    int prevPlayingState = mPlayingState;
    mPlayingState = STOPPED;
    mStarted = true;
    mStartedDelay = false;
    mPaused = false;
    updateScaledDuration(); // in case the scale factor has changed since creation time
    AnimationHandler animationHandler = getOrCreateAnimationHandler();
    animationHandler.mPendingAnimations.add(this);
    if (mStartDelay == 0) {
        // This sets the initial value of the animation, prior to actually starting it running
        if (prevPlayingState != SEEKED) {
            setCurrentPlayTime(0);
        }
        mPlayingState = STOPPED;
        mRunning = true;
        notifyStartListeners();
    }
    animationHandler.start();
}

通过上述代码,我们可以看出,属性动画需要运行在looper的线程中,最终会调用 animationHandler.start();
然后调用到JNI层,最后回调回来。

这边有一个核心方法:doAnimationFrame

其中animationFrame内部调用animateValue

在初始化的时候如果属性没有提供初始值,则get方法将被会调用,我们看PropertyValuesHolder的setValue方法

private void setupValue(Object target, Keyframe kf) {
    if (mProperty != null) {
        Object value = convertBack(mProperty.get(target));
        kf.setValue(value);
    }
    try {
        if (mGetter == null) {
            Class targetClass = target.getClass();
            setupGetter(targetClass);
            if (mGetter == null) {
                // Already logged the error - just return to avoid NPE
                return;
            }
        }
        Object value = convertBack(mGetter.invoke(target));
        kf.setValue(value);
    } catch (InvocationTargetException e) {
        Log.e("PropertyValuesHolder", e.toString());
    } catch (IllegalAccessException e) {
        Log.e("PropertyValuesHolder", e.toString());
    }
}

可以看到get方法是通过反射调用的,当动画到下一帧的时候,调用setAnimatedValue将新的属性设置给对象,调用set方法。
set方法也是通过反射调用的

代码如下:

void setAnimatedValue(Object target) {
    if (mProperty != null) {
        mProperty.set(target, getAnimatedValue());
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = getAnimatedValue();
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值