控件动画和属性动画(及Evaluator估值器、Interpolator插值器的使用)

本文对比了控件动画和属性动画的差异,指出属性动画在Android3.0以下不支持动画过程中的交互。控件动画包括透明度、平移、缩放和旋转等,而属性动画能改变View属性。介绍了ValueAnimator、ObjectAnimator和AnimatorSet的使用,并强调了在Animator中结合Interpolator和TypeEvaluator实现复杂动画效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

控件动画和属性动画的区别和相同点:

1.控件动画(View Animation)的父类是Animation;属性动画的父类是Animator;

Animation其下包含了四个直接的子类

  • AlphaAnimation: 透明度动画
  • TranslateAnimation:平移动画
  • ScaleAnimation:缩放动画
  • RotateAnimation:旋转动画
  • AnimationSet:动画集合

2.View动画不支持动画时的互动,即在动画过程中不能交互,点击VIew看到的所在地方无反应,点击动画前的地方反而有反应。属性动画在动画过程中支持互动(但在Android3.0下使用属性动画也是不支持动画过程中动画的,且不能改变View的属性)

3.使用上有点不同,View动画的使用方式是

ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);

属性动画的使用方法:

ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
}
fadeAnim.start();

4.都是控件的移动而不是控件内容的移动。

5.都可以通过xml文件和代码来实现。


控件动画的绘制过程:

1.调用View#startAnimation(Animation)

public void startAinmation(Animation){
//......
     //保存Animation对象
       setAinmation(animation);
      //.......
     invalidate(true);
}

2.由上面的代码可知这会导致一次重绘,重绘该控件必然会调用View#draw(ViewGroup parent, Canvas canvas, long  drawTime),

有兴趣的可以查看这两篇文章:控件树绘制的完整流程(代码概述)Android绘制的坐标变换

boolean draw(ViewGroup parent, Canvas canvas, long  drawTime){

//......
//获取上面所传递的mCurrentAnimation对象
final a = getAnimation();
if(a != null){
//计算当前时间点的变换,就是坐标变换,这是绘制时作坐标变换的一个因素(空间动画变换矩阵),并将结果保存到parent.mChildTransformation中,
//因为这样的话,如果可以在fillafter为true时,控件显示在动画后的位置,那么父控件在派发触摸事件的时候可以根据变换正确的把事件传递到该VIew中。
more = drawAnimation(parent, drawing, a, scalingRequired);//more为是否还有下一帧,即是否需要继续动画。
transformToApply = parent.mChildTransformation;//transformToApply就是之前介绍绘制原理时会应用到坐标变换的一个关键对象。
}
//.....
}



/***************************看View#drawAnimation(ViewGroup, long, Animaton, boolean)是做了什么,如果返回more。*************************/

drawAnimation(ViewGroup parent, long drawTime, Animaton a, boolean scalingRequired){
    //........
   //判断是否初始化,即是否第一帧
   if(!initialized){
     //回调onAnimationOnstart(); View的默认实现是将PFLAG_ANIMATION_STARTED标记加入View.mPrivateFlags中,用以标记控件正在运行动画。因此重写
     //onAnimationStart()时一定要调用父类的onAnimationStart(); 以保证控件系统正常工作。
     onAnimationStart();  
  }
  //计算当前时刻变换并保存到父控件的mChildTranslation中。
  boolean more= a.getTransformation(drawingTime, parent.mChildTransformation, 1f);
  //为了提高效率需要指定invalidate() 的边界一遍在下次绘制时仅更新此控件的区域。正常来说mLeft,mTop,mRight,mBottom是控件更新区域,但是动画时控件移动了,
  //绘制更新区域就变了,需要根据动画变换。代码就不写出来了,有点多。
}




/****************************动画结束处理**********************************************

boolean draw(ViewGroup parent, Canvas canvas, long drawTime){

  //........
  //到这里控件已经完成绘制了。
  if(a != null && !more){
       //注意了,没有直接调用onAnimationEnd(),而是把处理交给了父控件。
       parent.finishAnimatingView(this, a);
    }
}



/*************************************看 parent.finishAnimatingView(this, a);的处理过程,比较有趣:**********************************/

 parent.finishAnimatingView(this, a){
    //....
   //注意到父控件的一个成员:mDisappearingChildren;当控件进行动画时将这个控件从父控件中移除,ViewGroup会将其从mChildren中移除,但会放到
   //mDisappreaingChildren中,并等待动画结束。由于mDisappreaingChildren中的控件仍然会得到绘制,因此在执行了ViewGroup.removeView()之后,
   //用户仍然可以看到动画中的控件。
   if(disappearChildren != null){
     //此时才是真正移除了view
     disappearingChilddren.remove(view);
     //....
     view.dispatchDetachedFromWindow();
     //clearAnimation()终止动画并将mCurrentAnimation设置为null。这样下次绘制便没有动画产生变换了,那么下次的绘制就会回到动画前的位置。
     view.clearAnimation();
  }  
  //当FillAfter为true时会启动重绘,因为上面一句已经是清空动画了,没有了动画变换,绘制控件回到原来的位置.虽然跟上面都是调用同一个方法,但是效果不一样。
  //第二次是重绘,因为clearAnimation()中调用了一个方法invalidateIfNeed(); 

  if(animation!= null && animation.getFillAfter()){
          view.clearAnimation();
   }

 //最后调用View#onAnimationEnd(),动画终止
if((view.mPrivateFlags & PFLAG_ANIMATION_STARTED) == PFLAG_ANIMATION_STARTED){
view.onAnimationEnd();
}
}

//总结一下就是由View#startAnimation()开始,然后在draw(ViewGroup parent, Canvas canvas, long drawTime)中计算、坐标变换,
//绘制区域计算。由父控件ViewGroup#finishAnimatingView()结束。




属性动画小结:

与属性动画相关的View中的几个成员:

mRenderNode

mTransformationInfo

还有setTranslationX、setAlpha、setRotate、setScaleX。还有对应的get方法。其中setTranslationX、setRotate、setScaleX的参数是保存在mRenderNode中的,Alpha放在mTranslationInfo中。

而View动画(Animation)相关的参数是mCurrentAnimation;通过View#setAnimation()或者View.startAnimation(Animation)添加并启动动画。

属性动画的更详细解说请点击http://www.jb51.net/article/76466.htm

常用类:ValueAnimator(extends Animator)、ObjectAnimator(extends ValueAnimator)、AnimatorSet(extends Animator)。


ValueAnimator:

只是做单纯的值动画,就是类似估值器的功能,不过就是多了一些回到方法,当然实现上也会比估值器复杂一点,但是表面看上去是差不多。常用的方法有:

ValueAnimator static ofInt(int...values);//getAnimatorValue()中返回Integer

ValueAnimator static ofFloat(float...values);//getAnimatorValue()中返回Float(是对象不是基本类型)

ValueAnimator static ofIArgb(int...values);//getAnimatorValue()中返回int

需要做其他操作只需添加监听器即可。
使用方法:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 500f);
 
 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
     float deltaY = (float)animation.getAnimatedValue();
     textView.setTranslationY(deltaY);
   }
 });
 
 //默认duration是300毫秒
 valueAnimator.setDuration(3000);
 valueAnimator.start();

ObjectAnimator(extends ValueAnimator):

重载了Animator中的方法:

ofFloat(Object target, String propertyName, float… values)

ofInt(Object target, String propertyName, int… values)

ofArgb(Object target, String propertyName, int… values)

这个类的使用和上一个不一样的地方是,ofxxx()方法中多了一个叫Target的参数,就是动画的目标View。还有一个叫propertyName的参数,就是Target的属性名。如:ofInt(target, "abc", 0, 100);那么每一帧都会调用一次target.setAbc(currentValue),所以target类中必须要有getAbc这个方法,因为它是直接通过“Abc”这个字符串

去反射调用的。如果value只有一个值,如:ofInt(target, "abc", 100),即100是最终值,此时没有起始值,需要通过target.getAbc()来获得起始值,所以没有起始值时,必须要有一个getAbc()方法。


AnimatorSet:

AnimatorSet继承自Animator。AnimatorSet表示的是动画的集合,我们可以通过AnimatorSet把多个动画集合在一起,让其串行或并行执行,从而创造出复杂的动画效果。

我们想让TextView先进行旋转,然后进行平移,最后进行伸缩,我们可以通过AnimatorSet实现该效果,代码如下所示:


 //anim1实现TextView的旋转动画
  Animator anim1 = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
  anim1.setDuration(2000);
  //anim2和anim3TextView的平移动画
  Animator anim2 = ObjectAnimator.ofFloat(textView, "translationX", 0f, 300f);
  anim2.setDuration(3000);
  Animator anim3 = ObjectAnimator.ofFloat(textView, "translationY", 0f, 400f);
  anim3.setDuration(3000);
  //anim4实现TextView的伸缩动画
  Animator anim4 = ObjectAnimator.ofFloat(textView, "scaleX", 1f, 0.5f);
  anim4.setDuration(2000);
 
 
  //第一种方式
  AnimatorSet animatorSet = new AnimatorSet();
  animatorSet.playSequentially(anim1, anim2, anim4);
  animatorSet.playTogether(anim2, anim3);
  animatorSet.start();
 
  //第二种方式
  /*AnimatorSet anim23 = new AnimatorSet();
  anim23.playTogether(anim2, anim3);
  AnimatorSet animatorSet = new AnimatorSet();
  animatorSet.playSequentially(anim1, anim23, anim4);
  animatorSet.start();*/
 
  //第三种方式
  /*AnimatorSet animatorSet = new AnimatorSet();
  animatorSet.play(anim1).before(anim2);
  animatorSet.play(anim2).with(anim3);
  animatorSet.play(anim4).after(anim2);
  animatorSet.start();*/


在使用Animator过程中进行平移动画时,一个Animator只能产生一个currentValue,也就是只能计算X或者Y的变化,那么可以在监听器中使用估值器IntEvaluator(extends TypeEvaluator),使用方法:float currentValue1 = mEvaluator.evaluate(currentFraction, 0, 100);currentFraction为当前百分进度(float,0~1),可以通过Animator.getFraction()得到。那么就可以在一个Animator中同时改变TranslationX和TranslationY。


动画中配合插值器(Interpolator)和估值器(TypeEvaluator)使用:

Interpolator(extends BaseInterpolator):

主要是这个方法:Interpolator#getInterpolation(float input);

getInterpolation(float input){

      int i = f(input);//一个关于input的函数映射。
     /****************
若是LinearInterpolator(extends Interpolator)的话,int i = input;
这个可以是双曲线,可以是反曲线,可以是各种线型。但是input的值和返回值都必须是0~1才有效。0是开始值,1表示结束值,这个类基本是为动画专门设计的,改变动画值
随时间的速率。
*/

 return i;
 }

如何配合Animator和Animation来使用呢。拿ValueAnimator来示例:

//这是动画监听器的方法
Interpolator interpolator = new Interpolator();
IntEvaluator evaluator = new IntEvaluator();//IntEvaluator extends TypeEvaluator
onAnimationUpdate(ValueAnimator animator){
   float fraction = animator.getFraction();
   fraction = interpolator.getInterpolation(faction);
   int currentValue  = evaluator.evaluate(fraction, start, end);
   //.....
}
注意:插值器就是一个专门为处理0~1的输入通过一个函数映射输出0~1的工具。内部并没有定时去调用计算方法的功能,需要利用动画的定时功能(定时刷新10ms一帧)去调用Interpolator#getInterpolation(input)


在Animation或者Animator都可以通过setInterpolator(Interpolator)去设置插值器,如Animator设置了插值器后,getAnimatedValue()获得那个经过插值器处理的当前时刻的值。



注意:使用动画时,最好使用硬件加速,这样比较流畅。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值