Android属性动画(Property Animation)(上)

转自:http://blog.csdn.net/liuyi1207164339/article/details/53590451

1、概述

从Android3.0 (API11)开始引入了属性动画,跟早期的View动画相比,属性动画具有以下优点: 
1、属性动画允许对任意对象的属性执行动画操作,而早期的视图动画仅仅只能对View执行动画操作。 
2、View动画只能改变视图的几个方面,比如对视图进行缩放以及旋转等,但是像背景颜色这种就无法改变。 
3、View动画只是改变View在屏幕上的位置,但是却不能真正改变View本身。比如对于一个按钮,通过动画让其在屏幕上进行移动,但是按钮的有效点击区域还是其原来的位置,并没有发生改变。要想实现按钮的有效点击区域随着动画的改变而改变,这就需要自己手动实现这个逻辑,而属性动画就解决了这个问题。

一个属性动画在特定的时间段内改变对象的属性值,不管这个属性能不能绘制到屏幕上。为了对对象执行属性动画,首先需要指定想要改变的属性,比如对象在屏幕上的位置,然后是动画的持续时间,以及在动画执行期间改变的属性值的类型。

属性动画系统允许你定义一个动画的以下特性: 
1、Duration:动画持续时间,默认是300ms 
2、Time interpolation:时间差值,这个跟View动画里面的LinearInterpolator等差不多,定义动画的变化率 
3、Repeat count and behavior:定义重复次数以及重复模式。可以指定是否重复以及重复的次数,以及重复的时候是从头开始还是反向 
4、Animator sets:动画集合。可以定义一组动画,顺序执行或者一起执行,顺序执行的时候可以执行动画之间执行的时间间隔 
5、Frame refresh delay:帧刷新延迟。对于动画,定义多久刷新一次帧,默认是10ms,但是帧率最终取决于系统,一般不需要管

2、相关的API

ValueAnimator:主要的动画执行类 
ObjectAnimator:VauleAnimator的子类,大多数情况下动画的执行类使用ObjectAnimator就行,除非一些特殊情况下才会使用VauleAnimator做为动画执行类 
AnimatorSet:动画集合,用于控制一组动画的执行方式:一起,线性,动画的执行次序以及动画之间的时间间隔 
TypeEvaluator:动画估值,用于动画操作属性的值,系统自带的有IntEvaluator、FloatEvaluator等估值方式。 
TimeInterpolator:时间差值,这个在前面有介绍,用于指定属性值随着时间改变的方式,比如线性改变、加速改变、先加速再减速等等。

3、使用ObjectAnimator实现动画

使用ObjectAnimator可以很快实现一个动画效果,下面来看一个例子。

首先是布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content_object_animate"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.easyliu.test.animationdemo.ObjectAnimateActivity"
    tools:showIn="@layout/activity_object_animate">

    <Button
        android:id="@+id/btn_object_anim"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Object Animator"
        />

</RelativeLayout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

然后是Activity:

package com.easyliu.test.animationdemo;
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;

public class ObjectAnimateActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_object_animate);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        findViewById(R.id.btn_object_anim).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 执行动画操作
                ObjectAnimator.ofFloat(view,"alpha",0.0f,1f).setDuration(500).start();
            }
        });
    }

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

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

以上实现的是点击按钮,其透明度发生改变的动画,我们发现只需要一句话就可以实现动画效果! 
使用ObjectAnimator有几点需要注意的地方: 
1、使用ObjectAnimator就不需要再实现ValueAnimator.AnimatorUnpdateListener接口,因为执行动画的属性值会自动更新 
2、执行动画的属性值必须有setter函数,因为在动画的执行过程总会自动调用这个函数来更新属性的值,函数形式为setPropName,例如如果属性名字为foo,那么对应的setter方法为setFoo()。如果不存在setter方法,那么有三种解决方案: 
(1)增加一个setter方法,前提是有权限修改这个类 
(2)使用一个包装类来间接改变这个属性 
(3)使用ValueAnimator,这个稍后会讲 
3、如果对于ObjectAnimator只指定一个属性值,默认就是动画结束时候的属性值,此时就要求属性具有getter方法,因为此时属性的开始值需要通过getter方法来得到 
4、getter和setter方法必须对相同的类型进行操作,这个类型必须跟ObjectAnimator指定的类型保持一致 
5、如果操作属性的setter方法里面没有调用view的重绘,那就需要实现ValueAnimator.AnimatorUpdateListener接口,在接口方法里面手动调用,如下所示:

objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
@Override 
public void onAnimationUpdate(ValueAnimator valueAnimator) { 
// view.invalidate(); 
// view.postInvalidate(); 

});

4、使用ValueAnimator实现动画

ValueAnimator的使用方法和ObjectAnimator差不多,如下所示: 
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, (float) (view.getHeight() * 3)); 
valueAnimator.setDuration(500); 
valueAnimator.setTarget(view); 
valueAnimator.start();
 
我们发现ValueAnimator并没有直接指定要操作的属性,这样是看不到任何效果的,这样的好处是不需要操作的属性必须存在getter和setter方法,我们需要在ValueAnimator.AnimatorUpdateListener接口的方法里面获取到动画的当前值,然后对任意属性进行操作,如下所示:

//执行动画
private void performAnimate(final View view) {
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, (float) (view.getHeight() * 3));
    valueAnimator.setDuration(500);
    valueAnimator.setTarget(view);
    valueAnimator.start();
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float animatedValue=(float)valueAnimator.getAnimatedValue();
            view.setTranslationY(animatedValue);
            view.setTranslationX(animatedValue);
        }
    });
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在上述代码中,通过调用ValueAnimator的getAnimatedValue()方法来获取动画的当前值,然后对view的TranslationX和TranslationY属性进行操作。效果如下:

这里写图片描述

从上图中我们发现Button的x轴和y轴的移动速率是相同的,如果有一个需求,比如抛物线运动:此时x轴和y轴的移动速率是不同的,那这时候该怎么办呢?此时就需要自定义估值器TypeEvaluator了! 
下面自定义估值器:

//自定义估值器
public class PointTypeEvaluator implements TypeEvaluator<PointF> {
    @Override
    public PointF evaluate(float fraction, PointF pointStart, PointF pointEnd) {
        Log.d(TAG,fraction+"");
        //抛物线运动
        PointF pointF = new PointF();
        pointF.x = 600 * fraction;
        pointF.y = 0.5f * 10 * (fraction) * (fraction)*120;
        return pointF;
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

下面是执行动画:

//执行自定义估计器动画
private void performAnimate2(final View view) {
    ValueAnimator valueAnimator = new ValueAnimator();
    valueAnimator.setDuration(1000);
    valueAnimator.setTarget(view);
    valueAnimator.setObjectValues(new PointF(0.0f,0.0f));
    valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    //设置自定义估值器
    valueAnimator.setEvaluator(new PointTypeEvaluator());
    valueAnimator.start();
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            PointF pointF = (PointF) valueAnimator.getAnimatedValue();
            view.setTranslationX(pointF.x);
            view.setTranslationY(pointF.y);
        }
    });
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

通过自定义TypeEvaluator就实现了抛物线的效果~~

5、使用AnimateSet组合动画

可以使用AnimateSet把多个动画组合在一起执行,可以指定执行的模式:顺序执行或者一起执行。下面使用一个来自ApiDemo里面的例子,效果如下所示。ball1和ball2的动画为往下运动,ball3和ball4的动画为往下运动再往上回到起点。ball1、ball2、ball3三个球的动画同时执行,执行完成之后再执行ball4的动画。

这里写图片描述

Activity代码:

package com.easyliu.test.animationdemo;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.RelativeLayout;

import java.util.ArrayList;

public class ValueAnimateSetActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_value_animate_set);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
        RelativeLayout container = (RelativeLayout) findViewById(R.id.content_value_animate_set);
        final MyAnimationView myAnimationView = new MyAnimationView(this);
        //把动画视图加入布局
        container.addView(myAnimationView);
        findViewById(R.id.btn_value_animate_set_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 执行动画
                myAnimationView.startAnimation();
            }
        });

    }

    //自定义动画视图
    public class MyAnimationView extends View implements ValueAnimator.AnimatorUpdateListener {

        public final ArrayList<ShapeHolder> balls = new ArrayList<ShapeHolder>();
        AnimatorSet animation = null;
        private float mDensity;

        public MyAnimationView(Context context) {
            super(context);

            mDensity = getContext().getResources().getDisplayMetrics().density;

            ShapeHolder ball0 = addBall(50f, 25f);
            ShapeHolder ball1 = addBall(150f, 25f);
            ShapeHolder ball2 = addBall(250f, 25f);
            ShapeHolder ball3 = addBall(350f, 25f);
        }

        private void createAnimation() {
            if (animation == null) {
                ObjectAnimator anim1 = ObjectAnimator.ofFloat(balls.get(0), "y",
                        0f, getHeight() - balls.get(0).getHeight()).setDuration(500);

                ObjectAnimator anim2 = anim1.clone();
                anim2.setTarget(balls.get(1));
                anim1.addUpdateListener(this);

                ShapeHolder ball2 = balls.get(2);
                ObjectAnimator animDown = ObjectAnimator.ofFloat(ball2, "y",
                        0f, getHeight() - ball2.getHeight()).setDuration(500);
                animDown.setInterpolator(new AccelerateInterpolator());
                ObjectAnimator animUp = ObjectAnimator.ofFloat(ball2, "y",
                        getHeight() - ball2.getHeight(), 0f).setDuration(500);
                animUp.setInterpolator(new DecelerateInterpolator());
                AnimatorSet s1 = new AnimatorSet();
                s1.playSequentially(animDown, animUp);
                animDown.addUpdateListener(this);
                animUp.addUpdateListener(this);
                AnimatorSet s2 = (AnimatorSet) s1.clone();
                s2.setTarget(balls.get(3));

                animation = new AnimatorSet();
                animation.playTogether(anim1, anim2, s1);//一起执行,可以把AnimatorSet跟ValueAnimator组合在一起执行
                animation.playSequentially(s1, s2);// 顺序执行
            }
        }

        private ShapeHolder addBall(float x, float y) {
            OvalShape circle = new OvalShape();
            circle.resize(50f * mDensity, 50f * mDensity);
            ShapeDrawable drawable = new ShapeDrawable(circle);
            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            shapeHolder.setX(x - 25f);
            shapeHolder.setY(y - 25f);
            int red = (int) (100 + Math.random() * 155);
            int green = (int) (100 + Math.random() * 155);
            int blue = (int) (100 + Math.random() * 155);
            int color = 0xff000000 | red << 16 | green << 8 | blue;
            Paint paint = drawable.getPaint(); //new Paint(Paint.ANTI_ALIAS_FLAG);
            int darkColor = 0xff000000 | red / 4 << 16 | green / 4 << 8 | blue / 4;
            RadialGradient gradient = new RadialGradient(37.5f, 12.5f,
                    50f, color, darkColor, Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);
            return shapeHolder;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            for (int i = 0; i < balls.size(); ++i) {
                ShapeHolder shapeHolder = balls.get(i);
                canvas.save();
                canvas.translate(shapeHolder.getX(), shapeHolder.getY());
                shapeHolder.getShape().draw(canvas);
                canvas.restore();
            }
        }

        public void startAnimation() {
            createAnimation();
            animation.start();
        }

        public void onAnimationUpdate(ValueAnimator animation) {
            invalidate();
        }

    }

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146

6、监听动画事件

我们有的时候需要监听动画执行过程中的一些事件,比如当按钮的动画执行结束之后删除按钮,此时就需要为动画添加监听,添加方式如下所示:

// 添加动画监听
valueAnimator.addListener(new ValueAnimator.AnimatorListener() {
    @Override
    public void onAnimationCancel(Animator animator) {

    }

    @Override
    public void onAnimationStart(Animator animator) {

    }

    @Override
    public void onAnimationEnd(Animator animator) {
        //动画执行完成之后删除View
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null) {
            parent.removeView(view);
        }
    }

    @Override
    public void onAnimationRepeat(Animator animator) {

    }
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

效果如下所示,当动画执行结束之后,按钮就被移除了。

这里写图片描述

有的时候不需要Animator.AnimatorListener接口里面所有的方法,比如只需要End方法,此时可以使用AnimatorListenerAdapter来过滤掉不需要的方法,如下所示:

valueAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        //动画执行完成之后删除View
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null) {
            parent.removeView(view);
        }
    }
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

以上主要对属性动画的核心功能进行了一个详细的讲解,包括组合动画、自定义估值器等,下一篇讲解: 
1、在xml文件当中定义属性动画

2、布局动画

3、ViewPropertyAnimator等

代码下载地址:https://github.com/EasyLiu-Ly/AndroidBlogDemo 

参考: 
https://developer.android.google.cn/guide/topics/graphics/overview.html 
https://developer.android.google.cn/guide/topics/graphics/prop-animation.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值