转自: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.animationdemoimport android.animation.ObjectAnimatorimport android.os.Bundleimport android.support.design.widget.FloatingActionButtonimport android.support.design.widget.Snackbarimport android.support.v7.app.AppCompatActivityimport android.support.v7.widget.Toolbarimport android.view.Viewpublic 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); } });}
在上述代码中,通过调用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; }}
下面是执行动画:
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); 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(); 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) { 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) { ViewGroup parent = (ViewGroup) view.getParent(); if (parent != null) { parent.removeView(view); } }});
以上主要对属性动画的核心功能进行了一个详细的讲解,包括组合动画、自定义估值器等,下一篇讲解:
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