属性动画实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性。属性动画机制已经不只是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再只是一种视觉上的动画效果了。所以我们仍然可以将一个View进行移动或者缩放,但同时也可以对自定义View中的Point对象进行动画操作了。我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束值,剩下的工作就可以全部交给系统去完成了。
-
ValueAnimator
ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
示例:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.start()
setStartDelay()方法来设置动画延迟播放的时间
setRepeatCount()设置动画循环播放的次数
setRepeatMode()方法设置循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。
-
ObjectAnimator
ObjectAnimator是经常接触到的类,它是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的。提供了ofInt、ofFloat、ofObject。
示例:
float curTranslationX = textview.getTranslationX();
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -500f, curTranslationX);
animator.setDuration(5000);
animator.start();
调用了TextView的getTranslationX()方法来获取到当前TextView的translationX的位置,然后ofFloat()方法的第二个参数传入"translationX",紧接着后面三个参数用于告诉系统TextView应该怎么移动。ofFloat()方法的第二个参数可以是
- alpha:表示View对象的透明度
- rotation、rotationX、rotationY:控制View对象围绕支点进行2D和3D旋转
- translationX、translationY:作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。
- x、y:描述了View对象在它的容器中的最终位置,是最初的左上角坐标和translationX和translationY值得累加
- scaleY、scaleX:控制View对象围绕它的支点进行2D缩放
ObjectAnimator内部的工作机制并不是直接对传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,这两个方法是由View对象提供的,因此alpha属性所对应的get和set方法应该就是:
public void setAlpha(float value);
public float getAlpha();
-
组合动画
实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:- after(Animator anim) 将现有动画插入到传入的动画之后执行
- after(long delay) 将现有动画延迟指定毫秒后执行
- before(Animator anim) 将现有动画插入到传入的动画之前执行
- with(Animator anim) 将现有动画和传入的动画同时执行
示例:
TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作。
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textviwe, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
-
Animator监听器
Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。
ValueAnimator、ObjectAnimator、AnimatorSet都是继承自Animator的,都可以使用addListener()这个方法。
添加一个监听器的代码如下:
anim.addListener(new AnimatorListener(){
@Override
public void onAnimationStart(Animator animator){ //在动画开始的时候调用
}
@Override
public void onAnimationRepeat(Animator animator){ //动画重复执行的时候调用
}
@Override
public void onAnimationEnd(Animator animator){ //在动画结束的时候调用
//删除动画
Log.e(TAG, "onAnimationEnd");
ViewGroup parent = (ViewGroup) mBlueBall.getParent();
if (parent != null)
parent.removeView(mBlueBall);
}
@Override
public void onAnimationCancel(Animator animator){ //在动画被取消的时候调用
}
});
有时并不用将这四个接口都实现,Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:
anim.addListener(new AnimatorListenerAdapter(){
});
向addListener()方法中传入这个适配器对象,由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。因此,如果想监听动画结束这个事件,就只需要单独重写这一个方法就可以了,如下所示:
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animator){ //在动画结束的时候调用
}
});
-
使用XML编写动画
通过XML来编写动画在重用方面将会变得非常轻松,比如某个通用的动画编写到XML里面,可以在各个界面当中轻松去重用它。
如果想要使用XML来编写动画,首先要在res目录下面新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。然后在XML文件中我们一共可以使用如下三种标签:
<animator> 对应代码中的ValueAnimator
<objectAnimator> 对应代码中的ObjectAnimator
<set> 对应代码中的AnimatorSet
示例:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1" **
android:valueTo="0" **
android:valueType="floatType" **
android:propertyName="alpha"/>**
使用XML来完成复杂的组合动画操作:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
andorid:duration="2000"
android:propertyName="translationX"
android:valueFrom="-500"
andorid:valueTo="0"
android:valueType="floatType"
/>
<set android:ordering="together"
<objectAnimator
andorid:duration="2000"
android:propertyName="rotation"
android:valueFrom="0"
andorid:valueTo="360"
android:valueType="floatType"
/>
<set android:ordering="sequentially">
<objectAnimator
andorid:duration="2000"
android:propertyName="alpha"
android:valueFrom="1"
andorid:valueTo="0"
android:valueType="floatType"
/>
<objectAnimator
andorid:duration="2000"
android:propertyName="alpha"
android:valueFrom="0"
andorid:valueTo="1"
android:valueType="floatType"
/>
</set>
</set>
</set>
使用set标签,orderring属性设置为together,sequentially表示一个接一个执行
在代码中把文件加载进来并将动画启动:
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用start()方法启动动画。
-
View的animate()方法
animate()方法是属性动画的一种简写方式
view.animate()
.alpha(0)
.y(300)
.setDuration(300)
.withStartAction(new Runnable() {
@Override
public void run(){
}
})
.withEndAction(new Runnable() {
@Override
public void run(){
runOnUIThread(new Runnable(){
@Override
public void run(){
}
});
}
}).start();
-
ValueAnimator的高级用法
如果有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。
TypeEvaluator的作用就是告诉动画系统如何从初始值过度到结束值。ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,就是因为系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator的代码实现:
public class FloatEvaluator implements TypeEvaluator{
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction*(((Number) endValue).floatValue() - startFloat);
}
}
loatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction用于表示动画的完成度的,根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。
-
对象的动画操作
因为系统完全无法知道如何从初始对象过度到结束对象,需要实现一个自己的TypeEvaluator来告知系统如何进行过度。
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(){
reutrn y;
}
}
Point类只有x和y两个变量用于记录坐标的位置,接下来定义PointEvaluator:
public valss PointEvaluator implements TypeEvaluator{
@Overrride
public Object evaluate(float fraction, Object startValue, Object endValue){
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint + fraction*(endPoint.getX() - startPoint.getX());
float y = startPoint + fraction*(endPoint.getY() - startPoint.getY());
Point point = new Point(x, y);
reutrn point;
}
}
PointEvaluator同样实现了TypeEvaluator接口并重写了evaluate()方法。在evaluate()方法中的先将startValue和endValue强制转为Point对象,然后根据fraction来计算当前动画的x和y的值,然后封装到一个新的Point对象返回。
对Point对象进行动画操作:
新建一个MyAninView继承自View:
public class MyAnimView extends View{
public static final float RADIUS = 50f;
private Point currentPoint;
private Paint mPaint;
public MyAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//用于绘制时消除锯齿
mPaint.setColor(Color.BULE);
}
@Override
protected void OnDraw(Canvas canvas){
if (currentPoint == null){
currentPoint = new Point(RADIUS , RADIUS );
drawCircle(canvas);
startAnimation();
} else{
drawCircle(canvas);
}
}
private void drawCircle(Canvas canvas){
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
private void startAnimation() {
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.getAnimateValue();
invalidate(); //请求重新draw(),但只会绘制调用者本身
}
});
anim.setDuration(5000);
anim.start();
}
}
首先在自定义View的构造方法当中初始化了一个Paint对象作为画笔,并将画笔颜色设置为蓝色,接着在onDraw()方法当中进行绘制。这里我们绘制的逻辑是由currentPoint这个对象控制的,如果currentPoint对象不等于空,那么就调用drawCircle()方法在currentPoint的坐标位置画出一个半径为50的圆,如果currentPoint对象是空,那么就调用startAnimation()方法来启动动画。
-
布局动画
布局动画是指作用在ViewGroup上,给ViewGroup增加View时添加一个动画过度效果。
最简单的布局动画是在IViewGroup的XML中,当ViewGroup添加View时,子View会呈现逐渐显示Android默认的过渡效果,:
android:animateLayoutChanges="true"
可以通过LayoutAnimationController类来自定义一个子View的过渡效果:
Linearlayout ll = (LinearLayout) findViewById(R.id.ll);
//设置过渡动画
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
//设置布局动画的显示属性
LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
//为ViewGroup设置布局动画
ll.setLayoutAnimation(lac);
通过以上代码,给LinearLayout增加了一个视图动画,让子View在出现的时候,有一个缩放的动画效果。
LayoutAnimationController构造函数的第一个参数是需要作用的动画,第二个参数是每个子View显示的delay时间。当delay不为0时,可以设置子View的顺序:
- LayoutAnimationController.ORDER_NORMAL
- LayoutAnimationController.ORDER_RANDOM 随机
- LayoutAnimationController.ORDER_REVERSE 反序
-
Interpolator插值器
这个方法主要是用来控制android动画的执行速率,可以使存在的动画效果: - AccelerateInterpolator ——在动画开始的地方速率改变比较慢,然后开始加速
- AnticipateInterpolator ——开始的时候向后然后向前甩
- AnticipateOvershootInterpolator ——开始的时候向后然后向前甩一定值后返回最后的值
- BounceInterpolator ——开始时弹出,动画结束的时候弹起
- CycleInterpolator ——动画循环播放特定的次数,速率改变沿着正弦曲线
- DecelerateInterpolator—— 在动画开始的地方快然后慢
- LinearInterpolator ——以常量速率改变
- OvershootInterpolator ——向前甩一定值后再回到原来位置
网友评论