目录
做了好几年的Android开发,最近才发现自己有好多需要深入的领域,还有许多需要钻研的地方。本文主要总结下动画这一方面的一些知识点。
一 Android 动画分类
Android动画有三种:一种是传统动画,一种是属性动画(Property Animation),而传统动画又包括帧动画(Frame Animation)和补间动画(Tweened Animation)。
帧动画是最简单的一种动画,就是对图片资源的连续播放,更多依赖的是图片资源,并且原生的AnimationDrawable,这个动画有个缺点就是在加载该动画的时候,会把所有的图片资源都加载到内存中,会占用内存。通常在项目中如果该帧动画的图片资源很多的时候,建议不要采用该动画,否则会占用很大的内容。
补间动画就是对View进行淡入淡出(alpha)、平移(translate)、缩放(scale)、旋转(rotation),对应的类为AlphaAnimation、RotateAnimation、ScaleAnimation、Transformation。该动画与属性动画最大的一个区别,仅仅改变的是View的显示效果,并没有对该原View进行改变。例如页面上有一个ImageView,本身带有一个点击事件,然后对ImageView做了平移位置之后,那么这个点击的区域还是在之前的位置,并没有跟着ImageView移动到最后平移之后的位置。这也说明了补间动画只是改变了View的显示效果。
本文重点介绍下属性动画。
属性动画针对的是任意对象的任意属性进行动画。既可以View对象,也可以是非View对象,这一点是补间动画无法做到的。另外这个属性动画可以针对属性进行动画,例如可以改变一个View的背景色进行动态改变。那么也就看到了其实这个属性动画运行机制就是不断的对值进行操作,并将该值赋值到对象的指定属性上。
二 属性动画
刚才提到的属性动画的运行机制就是通过不断对值就行修改,那么可以通过属性动画完成从初始值到结束值的过渡。而这个动画过渡通常用ValueAnimator来实现。
1.ValueAnimator
自动的完成一个从初始值到结束值的过渡。可以设置这个过渡的过程的运行时间、初始值、结束值等。基本步骤:
- (1)通过ofInt()/ofFloat()/ofObject()等传入起始站和结束值,返回一个ValueAnimator对象
- (2)通过setDuration()/setRepeatCount()等设置该动画的一些基本属性
- (3)调用start()来执行该动画
- (4)当然也提供了addUpdateListener()来监听这个值过渡的一个变化情况
代码如下:
public void valueAnimator(View view) {
ValueAnimator animator = ValueAnimator.ofInt(1, 20);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
Log.d(TAG, "value = " + value);
tvAnimator.setText("第一个按钮对应的动画的value的变化为:" + value);
}
});
animator.start();
}
2.ObjectAnimator
在最前面也提到了属性动画可以对任意属性值进行赋值修改,不仅仅是基本的alpha、rotation、translationX、scaleY等,而任何一个有get/set方法的属性,都可以使用该类来设置一个初始值到终值的过渡。这其实也说明了ObjectAnimator并不是对属性值进行修改,而是直接对寻找对应属性的get/set方法。在使用Android studio进行设置的时候,也会自动检查是否合法。
举个简单的例子说明下用法。
第一个就是一个简单的alpha的一个变化,就是将TextView的透明度值的一个变化
public void objectAnimator(View view) {
ObjectAnimator animator = ObjectAnimator.ofFloat(tvAnimator, "alpha", 1f, 0f, 1f);
animator.setDuration(2000);
animator.start();
}
第二个就是完成了一个TextView的背景色从红色到黄色的一个过渡。
public void objectAnimatorBackground(View view) {
ObjectAnimator animator = ObjectAnimator.ofArgb(tvAnimator, "backgroundColor", Color.RED, Color.YELLOW);
animator.setDuration(3000);
animator.start();
}
3.组合动画
通过一个实例来说明下组合动画的使用方法。
在说明组合动画之前,特别先要提到的是ValueAnimator有一个 ofObject(TypeEvaluator evaluator, Object... values)。这个方法可以完成任意一个对象的值从初始值到结束值的平滑过渡。这个方法里面有两个参数,第一个evaluator就是决定动画如何从初始值过渡到结束值,需要自定义类实现TypeEvaluator接口来实现相关的过渡值的代码,而第二个参数values就是需要变化的值,需要自己定义变化值的类。举例说明下方法。
实例:有一个圆点在自定义View控件的中间做正弦曲线移动。
(1)定义Point类来封装点坐标
这个类很简单,就是定义了x和y的坐标。在自定义View中进行绘制圆点的运动曲线的时候,其实就需要每移动到每个点的x和y的坐标。
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
(2)使用TypeEvaluator自定义正弦曲线轨迹
这时候就用到了刚才在ofObject的第一个参数TypeEvaluator,那就需要实现TypeEvaluator接口,在这里完成一个从初始值到最终值的过渡。从代码中可以看到最后得到一个x和y存在的y=sin(x)关系的圆点坐标。
public class PointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point start = (Point) startValue;
Point end = (Point) endValue;
float x = start.getX() + fraction * (end.getX() - start.getX());
// float y = start.getY() + fraction * (end.getY() - start.getY());
float y = (float) (Math.sin(x * Math.PI / 180) * 100) + end.getY();
Point result = new Point(x, y);
return result;
}
}
(3)在View的onDraw()方法中实现动画效果
在这个要实现的动画效果有三种动画效果:
- 1)通过ValueAnimator产生从起始值到终点值的Point的过渡值
int width = getMeasuredWidth();
int height = getMeasuredHeight();
//Log.v("PointView", "width = " + width + " , height = " + height);
//1.在屏幕的中间位置让点按照正弦曲线移动
Point start = new Point(0, height / 2);
Point end = new Point(width, height / 2);
Point middle = new Point(width / 2, height / 2);
ValueAnimator dotAnim = ValueAnimator.ofObject(new PointEvaluator(), start, end, middle);
dotAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
cx = point.getX();
cy = point.getY();
//Log.d("PointView", "width cx = " + cx + " , cy = " + cy);
invalidate();
}
});
- 2)通过ObjectAnimator产生圆点的Paint的颜色从Color.RED到Color.GREEN的过渡值
ObjectAnimator colorAnimator = ObjectAnimator.ofArgb(paint, "color", Color.RED, Color.GREEN);
- 3)通过ObjectAnimator产生坐标系的Paint的颜色从Color.BLUE到Color.RED的过渡值
ObjectAnimator lineColorAnimator = ObjectAnimator.ofArgb(linePaint, "color", Color.BLUE, Color.RED);
- 4)通过AnimatorSet将多个动画集合起来一起播放
这样就出现了三个动画,需要一起执行。一般有两种方式实现:
第一种为:
AnimatorSet set = new AnimatorSet();
set.setDuration(7000);
//设置动画播放的顺序
set.play(dotAnim).with(colorAnimator).with(lineColorAnimator);
//播放动画
set.start();
简答的看下AnimatorSet里面的几个方法:
方法名 | 作用 |
play(Animator anim) | 播放传入的Animator动画 |
with(Animator anim) | 将现有的动画和传入的动画同时执行 |
before(Animator anim) | 将现有的动画插入到传入动画之前执行 |
after(Animator anim) | 将现有的动画插入到插入的动画之后执行 |
第二种为:
set.playTogether(dotAnim, colorAnimator, lineColorAnimator);
set.start();
方法名 | 作用 |
playTogether(Animator... items) | 将传入的动画一起执行 |
playSequentially(Animator... items) | 将传入的动画按照传入的顺序顺序执行 |
这样一个动画的集合就完成了,当显示这个自定义View的时候,就会显示一个圆点在做曲线移动,最终停留在中间。
因为考虑需要用到View的width和height,所以在onMeasure()之后,在onLayout()里面启动动画。
(4)动画监听
当然组合动画也设置了监听事件,可以对该组合动画开始、结束、取消、重复执行事件进行监听。但是注意该监听事件要放到set.start()之前进行设置。
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("PointView", "onAnimationStart = " + animation);
}
@Override
public void onAnimationEnd(Animator animation) {
Log.v("PointView", "onAnimationEnd = " + animation);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
有的时候其实在该监听事件中有些事件在使用过程中有可能会用不到,那么在实现这四个接口就显得很繁琐。 所以也提供了一个适配类,只需要在里面添加需要监听事件的方法即可。
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
4.XML定义
与补间动画一样,同样也可以使用xml进行定义动画。需要在res建一个animator的文件夹,在里面可以定义三种标签的<animator>、<objectAnimator>、<set>,其中<animator>对应ValueAnimator,<objectAnimator>对应ObjectAnimator,<set>对应AnimatorSet。
然后通过如下代码将xml加载到java代码中,给到对应需要该动画的相应对象,target对象不仅仅限于View,可以是任意对象。
private Animator xmlAnimator(View targetView, int anim) {
//加载xml文件
Animator animator = AnimatorInflater.loadAnimator(MainActivity.this, anim);
//将该动画加载到对应的view
animator.setTarget(targetView);
animator.start();
return animator;
}
三 总结
一句话总结下三种动画的特点:
1.帧动画就是将图片进行连续播放,内存开销比较大。
2.补间动画仅仅针对View进行淡入淡出、平移、缩放、旋转,改变的仅仅是View的显示效果。
3.属性动画可以完成针对任意对象的任意属性进行从初始值到结束值的过渡,不局限于View,不局限于淡入淡出、平移、缩放、旋转。
相关代码都已经上传github。github地址为:https://github.com/wenjing-bonnie/AndroidAnimation.git