View Animation
视图动画也叫补间动画,可以在一个视图容器内执行一系列简单变换(位置、大小、旋转、透明度),譬如如果你有一个T extView对象,你可以移动、旋转、缩放、透明度设置其文本,当然如果它有一个背景图像,背景图像也会随着文本变化。
视图动画可以通过XML或android代码定义,建议使用xml文件定义,可读性和可重用性更高。Animation抽象类是所有视图动画类的基类,所以基类会提供一些通用的动画属性方法,先来看下视图动画的种类,以及基类提供的方法。
Alpha属性(渐变透明度)
Rotate属性(旋转)
Scale属性(尺寸伸缩)
Translate属性(移动)
视图动画注意点:视图动画执行之后并为改变view的真实布局属性值!譬如有一个button在屏幕的上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的button是没有任何反应的,而点击屏幕上方原来button在的位置却有button点击事件的相应。
Drawable Animation
Drawable动画其实就是Frame动画(帧动画),它允许你实现像播放幻灯片一样的效果,所以这种动画的xml定义方式文件一般放在res/drawable目录下。我们依旧可以使用xml或java的方式实现帧动画,但依旧推荐使用xml,具体如下:
<animation-list>必须是根节点,包含一个或者多个<item>元素,属性有:
android:oneshot 为true代表只执行了一次,false循环执行;
<item>类似一帧的动画资源。
<item>是animation-list的一帧,包含的属性如下:
android:drawable 一个frame的Drawable资源;
android:duration一个frame显示多长时间
首先在res/drawable文件夹下新建一个xml文件:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/img1" android:duration="1000" />
<item android:drawable="@drawable/img2" android:duration="1000" />
<item android:drawable="@drawable/img3" android:duration="1000" />
<item android:drawable="@drawable/img4" android:duration="1000" />
</animation-list>
在activity中调用:
pic.setBackgroundResource(R.drawable.animation);
AnimationDrawable animation2 = (AnimationDrawable) pic.getBackground();
animation2.start();
Property Animation
属性动画机制已经不再是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再是一种视觉上的动画效果了,它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性,所以我们仍然可以将一个view进行移动或缩放,但同时也可以对自定义的view中的point对象进行动画操作,我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去做了。
ValueAnimator
ValueAnimator是整个属性动画机制中最核心的一个类,属性动画的运行机制是通过不断的对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的,它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需要运行的时长,那么ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。
最简单的实现:将一个值从0平滑过渡到1,时长300ms:
ValueAnimator vanim = ValueAnimator.ofFloat(0f, 1f);
vanim.setDuration(300);
vanim.start();
调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法当中允许传入多个float类型的参数(用来表示从哪过渡到哪再过渡到哪),也许并不需要小数位数的动画过渡,可能只是希望将一个整数值从0平滑过渡到100,那么只要调用ofInt()方法就可以了。通过addUpdateListener()方法来添加一个动画监听器,在动画执行的过程中会不断回调:
ValueAnimator vanim = ValueAnimator.ofFloat(0f, 1f);
vanim.setDuration(300);
vanim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float) valueAnimator.getAnimatedValue();
Log.e("eee", String.valueOf(value));
}
});
vanim.start();
除此之外,还可以调用setStartDelay()方法设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环播放包括RESTART和REVERSE两种,分别表示重新播放和倒序播放。
ObjectAnimator
相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,ValueAnimator只不过是对值进行了一个平滑的动画过渡,但实际使用到这种功能的场景好像并不多,而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,虽说ObjectAnimator会更加常用一些,但它其实是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的,因此ValueAnimator仍然是属性动画中最核心的一个类。既然是继承关系,那么ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,用法也类似,下面的代码是在5秒内将imageview的透明度从不透明到全透明再恢复到不透明:
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
同样是调用了ofFloat()方法来创建一个ObjectAnimator的实例,只不过参数有点变化,第一个参数要求传入一个object对象,想要对哪个对象进行动画操作就传入哪个对象,第二个参数是想要对该对象进行的某种动画操作,后面的参数就不固定参数数量了,代表变化的动画效果。通过setDuration设置时长,start()方法启动动画。
那么对于第二个参数疑问,到底能设定哪些属性呢?是任何值!纳尼?哈哈,慢慢看。
上面的代码改变的是imageview的alpha属性,那么它有没有这个属性呢,答案是没有,不仅它没有,它的父类也没有,那么问题来了,ObjectAnimator是如何进行操作的呢,其实ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,那么imageview是否含有这两个方法呢,确实有,而且是它的父类view提供的,也就是说任何继承自view的对象都可以进行这项操作。
组合动画
实现组合动画功能主要需要借助于AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象将会返回一个AnimatorSet.Builder实例,其中包括有以下四个方法:
after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有的动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行
ObjectAnimator moveIn = ObjectAnimator.ofFloat(imageView, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
上面的代码就实现了:imageview先从屏幕外移动近屏幕,然后开始旋转360°,在旋转的同时进行淡入淡出的操作。
Animator监听器
Animator类中提供了一个addListener()这个方法,这个方法接收一个AnimatorListener,只要实现这个AnimatorListener就可以监听动画的各种事件了。动画开始、动画结束、动画取消、动画重复。
animSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
很多时候我们并不需要监听那么多个事件,可能只要监听其中一个,那么每次都要将四个接口全部实现一遍就显得非常繁琐,为此Android提供了一个适配器类,叫做AnimatorListenerAdapter,可以自己选择要实现哪个事件。
ValueAnimation的高级用法
首先了解一下TypeEvaluator的用法
① 我们定义一个Point类,只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标:
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;
}
}
② 接下来定义一个PointEvaluator继承TypeEvaluator并重写evaluate()方法,首先将startValue和endValue强制转换成Point对象,然后同样根据fraction来计算当前动画的x和y值,最后组装到一个新的Point对象中病返回:
public class PointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
Point point = new Point(x, y);
return point;
}
}
③ 对Point对象进行动画操作,比如有两个Point对象,从startPoint通过动画平滑过渡到endPoint
Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setDuration(5000);
anim.start();
ObjectAnimator的高级用法
ObjectAnimator的内部工作机制是通过寻找特定属性的get和set方法,然后通过不断地对值进行改变从而实现动画效果,因此要造自定义动画中设定一个color属性,并提供它的get和set方法。
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
invalidate();
}
在setColor()方法中设定画笔颜色为参数传入的参数,并调用invalidate()方法,也即改变画笔颜色之后立即刷新视图。接下来就是借助ObjectAnimator类调用setColor()方法,当然首先要编写一个ColorEvaluator来告知系统怎样进行颜色的过渡。
public class ColorEvaluator implements TypeEvaluator {
private int mCurrentRed = -1;
private int mCurrentGreen = -1;
private int mCurrentBlue = -1;
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//首先获取到颜色的初始值和结束值
String startColor = (String) startValue;
String endColor = (String) endValue;
//将颜色截取为rgb三个部分
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
// 初始化颜色的值
if (mCurrentRed == -1) {
mCurrentRed = startRed;
}
if (mCurrentGreen == -1) {
mCurrentGreen = startGreen;
}
if (mCurrentBlue == -1) {
mCurrentBlue = startBlue;
}
// 计算初始颜色和结束颜色之间的差值
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
fraction);
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
redDiff + greenDiff, fraction);
}
// 将计算出的当前颜色的值组装返回
String currentColor = "#" + getHexString(mCurrentRed)
+ getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
return currentColor;
}
// 通过fraction计算得到当前的颜色值
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
// 将十进制的颜色值转换成十六进制
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}
结合上一小节介绍的改变位置将两个动画组合在一起(利用AnimatorSet):
ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),
"#000000", "#FF0000");
anim2.setRepeatCount(2);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim).with(anim2);
animatorSet.setDuration(5000);
animatorSet.start();
实现的效果是:一个黑色的小球从屏幕左上角移动到屏幕右下角,颜色最终变换为红色。Interpolator
补间器主要作用是控制动画的变化速率,比如去实现一种非线性运动的动画效果,比如AccelerateInterpolator是一个加速度运动的Interpolator,而DecelerateInterpolator是一个减速运动的Interpolator,而在使用属性动画时,系统默认的Interpolator是一个先加速后减速的Interpolator,对应的实现类就是AccelerateDecelerateInterpolator,这个属性是可以任意修改的,并且也是可以自定义的。
哈哈,木有错!那么也就是说,当我们自定义一个Interpolator,只要重写getInterpolation()方法就可以实现自己想要的变化速度。
ViewPropertyAnimator的用法
其实就是为view的动画操作提供的一种更加便捷的用法,在之前使用ObjectAnimator时(部分属性):
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
而使用ViewPropertyAnimator来实现同样的效果:
imageView.animate().alpha(0f).setDuration(5000);
imageView.animate()方法返回的是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,使用连缀的方式就可以将不同的动画效果组合在一起。
注意事项:
- 整个ViewPropertyAnimator的功能都是建立在view类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后调用的所有方法设置的所有属性都是通过这个实例完成的。
- 在使用ViewPropertyAnimator时,至始至终都没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动,并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动,当然如果不想使用这种机制的话,也可以显示的调用start方法来启动动画。
-
ViewPropertyAnimator的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此调用完一个方法之后可以直接连缀调用它的另一个方法,把所有的功能都串接起来。
调用BounceInterpolator()方法,使小球做自由落体,当碰到目标点时弹起,并带有颜色的变化: