欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

图文详解Android属性动画

程序员文章站 2023-12-18 07:59:39
 android中的动画分为视图动画(view animation)、属性动画(property animation)以及drawable动画。从android...

 android中的动画分为视图动画(view animation)、属性动画(property animation)以及drawable动画。从android 3.0(api level 11)开始,android开始支持属性动画,本文主要讲解如何使用属性动画。关于视图动画可以参见博文《android四大视图动画图文详解》。

一、概述

视图动画局限比较大,如下所述:

1、视图动画只能使用在view上面。

2、视图动画并没有真正改变view相应的属性值,这导致了ui效果与实际view状态存在差异,并导致了一系列怪异行为,比如在使用了视图动画translateanimation的view的ui上对其触摸,你可能惊讶地发现并没有触发触摸事件。

鉴于视图动画以上缺陷,从android 3.0引入了属性动画。属性动画具有以下特性:

1、属性动画应用面更广,不仅仅应用于view,可以将其应用到任意的对象上面,且该对象不需要具有ui界面。

2、当将属性动画作用于某个对象时,可以通过调用对象的setxxx方法实际改变对象的值。所以,当将属性动画作用于某个view时,view对象对应的属性值会被改变。

我们看一下属性动画时如何工作的。

其实属性动画的工作原理并不复杂,假设一个对象有一个属性x,我们想通过属性动画动态更改该值,假设我们想在40ms内将x的值从0渐变到40,那么如下图所示:

图文详解Android属性动画

随着时间的增长,对应的x值也相应地线性渐变,当动画完成时,x的值就是我们设置的最终值40。如果x值线性渐变,那么x的变化速度就是匀速的。其实,我们也可以变速地改变x的值,这会我们可以一开始加速增加x的值,后面减速增加x的值,如下图所示:

图文详解Android属性动画

如上图所示,在前20ms,x值加速增大,在后20ms,x值增大的速度降低。

其实,每种改变x值速度的方式都叫做时间插值器timeinterpolator,第一张图中使用的时间插值器叫做linearinterpolator,第二张图中使用的时间插值器叫做acceleratedecelerateinterpolator。动画开始后,时间插值器会根据对应的算法计算出某一时刻x的值,然后我们就可以用该计算出的值更新对象中x属性的值,这就是属性动画的基本工作原理。

属性动画中主要的类如下图所示:

图文详解Android属性动画

下面会对上述类分别进行讲解。

animator

属性动画主要的类都在android.animation命名空间下,animator是属性动画的基类,其是一个抽象类,该类定义了许多重要的方法,如下所示:

  • setduration(long duration)

通过setduration方法可以设置动画总共的持续时间,以毫秒为单位。

  • start()

通过start方法可以启动动画,动画启动后不一定会立即运行。如果之前通过调用setstartdelay方法设置了动画延迟时间,那么会在经过延迟时间之后再运行动画;如果没有设置过动画的延迟时间,那么动画在调用了start()方法之后会立即运行。在调用了start()方法之后,动画的isstarted()方法就会返回true;在动画真正运行起来之后,动画的isrunning()方法就会返回true,这时候动画才会调用timeinterpolator才开始工作计算属性在某个时刻的值。调用动画的start()方法所在的线程必须绑定了一个looper对象,如果没有绑定就会报错。当然,ui线程(即主线程)早就默认绑定了一个looper对象,所以在主线程中我们就无需担心这个问题。如果我们想在一个view上使用属性动画,那么我们应该保证我们是在ui线程上调用的动画的start()方法。start()方法运行后会触发动画监听器animatorlistener的onanimationstart方法的执行。

  • setstartdelay(long startdelay)

可以通过调用setstartdelay方法设置动画的延迟运行时间,比如调用setstartdelay(1000)意味着动画在执行了start()方法1秒之后才真正运行,这种情况下,在调用了start()方法0.5秒之后,isstarted()方法返回true,表示动画已经启动了,但是isrunning()方法返回false,表示动画还未真正运行;在start()方法执行1秒之后,isstarted()方法还是返回true,isrunning()方法也返回了true,表示动画已经真正开始运行了。通过调用getstartdelay()方法可以返回我们设置的动画延迟启动时间,默认值是0。

  • setinterpolator(timeinterpolator value)

我们可以通过调用setinterpolator方法改变动画所使用的时间插值器,由于视图动画也需要使用时间插值器,所以我们可以使用android.view.animation命名空间下的一系列插值器,将其与属性动画一起工作。通过动画的getinterpolator方法可以获取我们设置的时间插值器。

  • settarget(object target)

可以通过调用动画的settarget方法设置其要操作的对象,这样可以更新该对象的某个属性值。实际上,该方法对于valueanimator作用不大,因为valueanimator不是直接与某个对象打交道的。settarget方法对于objectanimator作用较大,因为objectanimator需要绑定某个要操作的对象,下面会详细介绍。

  • pause()

android中api level 19在animator中加入了pause()方法,该方法可以暂停动画的执行。调用pause()方法的线程必须与调用start()方法的线程是同一个线程。如果动画还没有执行start()或者动画已经结束了,那么调用pause()方法没有任何影响,直接被忽略。当执行了pause()方法后,动画的ispaused()方法会返回true。pause()方法运行后会触发动画监听器animatorpauselistener的onanimationpause方法的执行。

  • resume()

如果动画通过调用pause()方法暂停了,那么之后可以通过调用resume()方法让动画从上次暂停的地方继续运行。resume()方法也是从api level 19引入的,并且调用resume()方法的线程必须与调用start()方法的线程是同一个线程。如果动画没有处于暂停状态(即ispaused()返回false),那么调用resume()方法会被忽略。resume()方法运行后会触发动画监听器animatorpauselistener的onanimationresume方法的执行。

  • end

end()方法执行后,动画会结束运行,直接从当前状态跳转到最终的完成状态,并将属性值分配成动画的终止值,并触发动画监听器animatorlistener的onanimationend方法的执行。

  • cancel()

cancel()方法执行后,动画也会结束运行,但是与调用end方法不同的是,其不会将属性值分配给动画的终止值。比如一个动画在400ms内将对象的x属性从0渐变为40,当运行到第200ms时调用了cancel()方法,那么属性x的最终值是20,而不是40,这是与调用end()方法不同的,如果在第200ms调用了end()方法,那么属性x的最终值是40。调用cancel()方法后,会先触发animatorlistener的onanimationcancel方法的执行,然后触发onanimationend方法的执行。

  • clone()

animator实现了java.lang.cloneable接口。animator的clone()方法会对动画进行拷贝,但是该方法默认实现的只是浅拷贝,子类可以重写该方法以实现深拷贝。

  • addlistener (animator.animatorlistener listener)

可以通过addlistener方法向animator添加动画监听器,该方法接收的是animatorlistener接口类型的参数,其具有四个方法:onanimationstart、onanimationcancel、onanimationend、onanimationrepeat。我们上面已经介绍了前三个方法,onanimationrepeat方法会在动画在重复播放的时候被回调。android中的animatorlisteneradapter类是个抽象类,其实现了animatorlistener接口,并为所有方法提供了一个空实现。

  • addpauselistener (animator.animatorpauselistener listener)

可以通过addpauselistener方法可以向animator添加动画暂停相关的监听器,该方法接收的是animatorpauselistener接口类型的参数,具有两个方法:onanimationpause和onanimationresume,上面已经提到过,在此不再赘述。animatorlisteneradapter同样也实现了animatorpauselistener接口,并为所有方法提供了一个空实现。

valueanimator

valueanimator继承自抽象类animator。要让属性动画渐变式地更改对象中某个属性的值,可分两步操作:第一步,动画需要计算出某一时刻属性值应该是多少;第二步,需要将计算出的属性值赋值给动画的属性。valueanimator只实现了第一步,也就是说valueanimator只负责以动画的形式不断计算不同时刻的属性值,但需要我们开发者自己写代码将计算出的值通过对象的setxxx等方法更新对象的属性值。

valueanimator中有两个比较重要的属性,一个是timeinterpolator类型的属性,另一个是typeevaluator类型的属性。timeinterpolator指的就是时间插值器,在上面我们已经介绍过,在此不再赘述。typeevaluator是什么呢?typeevaluator表示的是valueanimator对哪种类型的值进行动画处理。valueanimator提供了四个静态方法offloat()、ofint()、ofargb()和ofobject(),通过这四个方法可以对不同种类型的值进行动画处理,这四个方法对应了四种typeevaluator,下面会详细说明。

  • public static valueanimator offloat (float… values)

offloat方法接收一系列的float类型的值,其内部使用了floatevaluator。通过该方法valueanimator可以对float值进行动画渐变,其使用方法如下所示:

 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();

其效果如下所示:

图文详解Android属性动画

我们通过构造函数指定了动画的起始值为0,终止值为500,动画的默认持续时间是300毫秒,我们通过setduration()方法设置为3000毫秒。该动画会在3秒内,将值从0到500动画渐变。valueanimator提供了一个addupdatelistener方法,可以通过该方法向其添加animatorupdatelistener类型的监听器。animatorupdatelistener有一个onanimationupdate方法,valueanimator会每隔一定时间(默认间隔10ms)计算属性的值,每当计算的时候就会回调onanimationupdate方法。在该方法中,我们通过调用valueanimator的getanimatedvalue()方法获取到当前动画计算出的属性值,然后我们将该值传入textview的settranslationy()方法中,从而更新了textview的位置,这样就通过valueanimator以动画的形式移动textview。

  • public static valueanimator ofint (int… values)

ofint方法与offloat方法很类似,只不过ofint方法接收int类型的值,ofint方法内部使用了intevaluator,其具体使用可参考上面offloat的使用代码,在此不再赘述。

  • public static valueanimator ofargb (int… values)

从api level 21开始,valueanimator中加入了ofargb方法,该方法接收一些列代表了颜色的int值,其内部使用了argbevaluator,可以用该方法实现将一个颜色动画渐变到另一个颜色,我们从中可以不断获取中间动画产生的颜色值。你可能纳闷,既然传入的还是int值,那直接用ofint方法不就行了吗,干嘛还要新增一个ofargb方法呢?实际上用ofint方法是不能完成颜色动画渐变的。我们知道一个int值包含四个字节,在android中第一个字节代表alpha分量,第二个字节代表red分量,第三个字节代表green分量,第四个字节代表blue分量,且我们常用16进制表示颜色,这样看起来更明显易懂一些,比如int值0xffff0000表示的红色,0xff00ff00表示的是绿色,最前面的ff表示的是alpha。ofargb方法会通过argbevaluator将颜色拆分成四个分量,然后分别对各个分量进行动画计算,然后将四个计算完的分量再重新组合成一个表示颜色的int值,这就是ofargb方法的工作原理。使用方法如下所示:

 //valueanimator.ofargb()方法是在api level 21中才加入的
  if(build.version.sdk_int >= 21){
    //起始颜色为红色
    int startcolor = 0xffff0000;
    //终止颜色为绿色
    int endcolor = 0xff00ff00;
    valueanimator valueanimator = valueanimator.ofargb(startcolor, endcolor);
    valueanimator.setduration(3000);
    valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {
      @override
      public void onanimationupdate(valueanimator animation) {
        int color = (int)animation.getanimatedvalue();
        textview.setbackgroundcolor(color);
      }
    });
    valueanimator.start();
  }

效果如下所示:

图文详解Android属性动画

我们将textview的颜色通过动画从红色渐变到绿色。

  • public static valueanimator ofobject (typeevaluator evaluator, object… values)

由于我们要进行动画处理的值是各种各样的,可能不是float、int或颜色值,那我们怎么使用属性动画呢?为此,valueanimator提供了一个ofobject方法,该方法接收一个typeevaluator类型的参数,我们需要实现该接口typeevaluator的evaluate方法,只要我们实现了typeevaluator接口,我们就能通过ofobject方法处理任意类型的数据。我们之前提到ofargb方法是从api level 21才引入的,如果我们想在之前的这之前的版本中使用ofargb的功能,怎么办呢?我们可以扩展typeevaluator,从而通过ofobject方法实现ofargb方法的逻辑,如下所示:

  //起始颜色为红色
  int startcolor = 0xffff0000;
  //终止颜色为绿色
  int endcolor = 0xff00ff00;
  valueanimator valueanimator = valueanimator.ofobject(new typeevaluator() {
    @override
    public object evaluate(float fraction, object startvalue, object endvalue) {
      //从初始的int类型的颜色值中解析出alpha、red、green、blue四个分量
      int startint = (integer) startvalue;
      int starta = (startint >> 24) & 0xff;
      int startr = (startint >> 16) & 0xff;
      int startg = (startint >> 8) & 0xff;
      int startb = startint & 0xff;

      //从终止的int类型的颜色值中解析出alpha、red、green、blue四个分量
      int endint = (integer) endvalue;
      int enda = (endint >> 24) & 0xff;
      int endr = (endint >> 16) & 0xff;
      int endg = (endint >> 8) & 0xff;
      int endb = endint & 0xff;

      //分别对alpha、red、green、blue四个分量进行计算,
      //最终合成一个完整的int型的颜色值
      return (int)((starta + (int)(fraction * (enda - starta))) << 24) |
          (int)((startr + (int)(fraction * (endr - startr))) << 16) |
          (int)((startg + (int)(fraction * (endg - startg))) << 8) |
          (int)((startb + (int)(fraction * (endb - startb))));
    }
  }, startcolor, endcolor);
  valueanimator.setduration(3000);
  valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {
    @override
    public void onanimationupdate(valueanimator animation) {
      int color = (int)animation.getanimatedvalue();
      textview.setbackgroundcolor(color);
    }
  });
  valueanimator.start();

以上代码实现的效果与ofargb实现的效果是一样的,都是将textview从红色渐变到绿色,就不再附图了,但是我们可以在api level 11及以后的版本中都可以使用以上ofobject的代码,通用性更强。

objectanimator

objectanimator继承自valueanimator。我们之前提到,要让属性动画渐变式地更改对象中某个属性的值,可分两步操作:第一步,动画需要计算出某一时刻属性值应该是多少;第二步,需要将计算出的属性值赋值给动画的属性。valueanimator只实现了第一步,也就是说valueanimator只负责以动画的形式不断计算不同时刻的属性值,但需要我们开发者自己写代码在动画监听器animatorupdatelistener的onanimationupdate方法中将计算出的值通过对象的setxxx等方法更新对象的属性值。objectanimator比valueanimator更进一步,其会自动调用对象的setxxx方法更新对象中的属性值。

objectanimator重载了offloat()、ofint()、ofargb()和ofobject()等静态方法,我们下面依次说明。

  • offloat(object target, string propertyname, float… values)

使用方法如下所示:

 float value1 = 0f;
  float value2 = 500f;
  final objectanimator objectanimator = objectanimator.offloat(textview, "translationy", value1, value2);
  objectanimator.setduration(3000);
  objectanimator.start();

以上代码实现的效果与通过valueanimator的offloat方法实现的效果相同,此处不再附图,但是可以看出使用objectanimator代码更简洁。在构造函数中,我们将textview作为target传递给objectanimator,然后指定textview要变化的属性是translationy,最后指定渐变范围是从0到500。当动画开始时,objectanimator就会不断调用textview的settranslationy方法以更新其值。我们此处演示的是objectanimator与view一起工作,其实objectanimator可以与任意的object对象工作。如果要更新某个对象中名为propery的属性,那么该object对象必须具有一个setproperty的setter方法可以让objectanimator调用。在offloat方法最后如果只填入了一个float值,那么objectanimator需要调用对象的getxxx方法获取对象初始的属性值,然后从该初始的属性值渐变到终止值。

  • ofint(object target, string propertyname, int… values)

参见offloat的使用方法。

  • ofargb(object target, string propertyname, int… values)

使用代码如下所示:

 //objectanimator.ofargb()方法是在api level 21中才加入的
  if(build.version.sdk_int >= 21){
    int startcolor = 0xffff0000;
    int endcolor = 0xff00ff00;
    objectanimator objectanimator = objectanimator.ofargb(textview, "backgroundcolor", startcolor, endcolor);
    objectanimator.setduration(3000);
    objectanimator.start();
  }

效果图参见valueanimator中对应的图片。

ofobject(object target, string propertyname, typeevaluator evaluator, object… values)
使用代码如下所示:

  

 int startcolor = 0xffff0000;
  int endcolor = 0xff00ff00;
  objectanimator objectanimator = objectanimator.ofobject(textview, "backgroundcolor", new typeevaluator() {
    @override
    public object evaluate(float fraction, object startvalue, object endvalue) {
      int startint = (integer) startvalue;
      int starta = (startint >> 24) & 0xff;
      int startr = (startint >> 16) & 0xff;
      int startg = (startint >> 8) & 0xff;
      int startb = startint & 0xff;

      int endint = (integer) endvalue;
      int enda = (endint >> 24) & 0xff;
      int endr = (endint >> 16) & 0xff;
      int endg = (endint >> 8) & 0xff;
      int endb = endint & 0xff;

      return (int)((starta + (int)(fraction * (enda - starta))) << 24) |
          (int)((startr + (int)(fraction * (endr - startr))) << 16) |
          (int)((startg + (int)(fraction * (endg - startg))) << 8) |
          (int)((startb + (int)(fraction * (endb - startb))));
    }
  }, startcolor, endcolor);
  objectanimator.setduration(3000);
  objectanimator.start();

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();*/
```

效果如下所示:

图文详解Android属性动画

动画anim1用于旋转textview,anim2用于在x轴方向偏移textview,anim3用于在y轴方向偏移textview,anim4用于缩放textview。我们在以上代码中提供了三种方式通过animationset把这四个动画组合到一起,第二种方式和第三种方式被注释起来了。

其实有很多种办法实现上述效果,这里只介绍一下上述三种方式的思路。

第一种方式中,调用了animatorset.playsequentially(anim1, anim2, anim4),该方法将anim1、anim2以及anim4按顺序串联起来放到了animatorset中,这样首先会让动画anim1执行,anim1执行完成后,会依次执行动画anim2,执行完anim2之后会执行动画anim3。通过调用animatorset.playtogether(anim2, anim3),保证了anim2和anim3同时执行,即动画anim1完成之后会同时运行anim2和anim3。

第二种方式中,我们首先创建了一个animatorset变量anim23,然后通过anim23.playtogether(anim2, anim3)将anim2和anim3组合成一个小的动画集合。然后我们再把anim1、anim23以及anim4一起传入到animatorset.playsequentially(anim1, anim23, anim4)中,这样anim1、anim23、anim4会依次执行,而anim23中的anim2和anim3会同时执行。该方式同时也演示了可以将一个animatorset作为动画的一部分放入另一个animatorset中。

第三种方式中,我们使用了animatorset的play方法,该方法返回animatorset.builder类型,animatorset.play(anim1).before(anim2)确保了anim1执行完了之后执行anim2,animatorset.play(anim2).with(anim3)确保了anim2和anim3同时执行,animatorset.play(anim4).after(anim2)确保了anim2执行完了之后执行anim4。需要说明的是animatorset.play(anim1).before(anim2)与animatorset.play(anim2).after(anim1)是完全等价的,之所以在上面代码中有的写before,有的写after,只是为了让大家多了解一下api。

希望本文对大家学习属性动画有所帮助。

上一篇:

下一篇: