Android动画之属性动画(二)

定义

The property animation system is a robust framework that allows you to animate almost anything. You can define an animation to change any object property over time, regardless of whether it draws to the screen or not. A property animation changes a property's (a field in an object) value over a specified length of time. 

属性动画系统是一个很健壮的框架。基本允许你在任何View加动画。你可以定义一个动画来改变对象的属性,不管它是否绘制在Screen上。属性动画在一段具体的时间内改变属性的值。

属性设置

你可以定义以下属性来设置一个动画
Duration  动画运行时间,默认的是300ms
Time interpolation 时间插值器,指定动画属性值的运行函数的计算方式
Repeat count and behavior 重复次数和行为,可以顺着重复还可以倒着重复
Animator sets 将动画放入集合中,顺序播放或一起播放
Frame refresh delay 可以指定刷新帧动画的频度,默认的刷新频度是10ms,但是应用程序刷新帧的速度,最终取决于系统的整体速度和系统如何快速服务于底层计时器。

属性动画和View Animation(补间动画)的区别

View Animation是只能针对View来设置动画,对于非View的对象,只能用自己的代码实现。View Animation加在View上的动画也非常有限,比如scale,rotate,类似改变view背景都是不行的。
View Animation的另一个缺点是,它只是改变View绘制的地方,实质上并没有改变原来的View。
对于属性动画就不存在以上的问题了。属性动画它改变的是真实的View。

最后,View Animation构建时间少,代码少。如果View Animation满足你的一切需求,就没有必要使用属性动画。

Animator类

以下类都继承自Animator

ValueAnimator

简述
属性动画的主要时序引擎,还计算动画中的属性值。它包含计算动画值的所有核心功能,并包含每个动画的时间细节,是否重复动画,接收更新事件的监听器,设置自定义计算类型的能力。
一般设置动画过程包含两个步骤:一计算动画的值,二将这些值设置到对象上,并按这些值进行播放。ValueAnimator不能执行步骤二,所以你必须监听ValueAnimator更新的计算值和修改对象,你必须实现自己的逻辑。

使用方法

 

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); 
animation.setDuration( 1000 ); 
animation.start();
ValueAnimator animation = ValueAnimator.ofObject( new  MyTypeEvaluator(), startPropertyValue, endPropertyValue); 
animation.setDuration( 1000 ); 
animation.start();

 

实例
进行单个属性设置,使用ValueAnimator的工厂方法ofFloat()、ofObject()、ofInt()方法就可以解决。接下来的实例我们来讨论一下多个动画属性值的解决方案。将View从左上位置移到右下位置。
很显然我们需要更新x,y轴方向的坐标。简单的做法就是创建两个动画,并同步播放它们,但是它们的效率不高。我们可以使用PropertyValuesHolder来将多个值联系起来。另外,你可以使用自定义的对象来封装动画的值。
使用PropertyValuesHolder
属性动画为了支持3.0及以下版本,NineOldAndroids提供了一个AnimatorProxy的类来包装view,并处理动画view的绘制。
实现步骤如下:
activity_main.xml

 

< RelativeLayout  xmlns:android = "<a href="http://schemas.android.com/apk/res/android" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/apk/res/android"
     xmlns:tools = "<a href="http://schemas.android.com/tools" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/tools"
     android:layout_width = "match_parent"
     android:layout_height = "match_parent"
     tools:context = ".MainActivity" >
 
     < FrameLayout
         android:id = "@+id/container"
         android:layout_width = "match_parent"
         android:layout_height = "match_parent"
         android:layout_alignParentTop = "true" />
 
     < LinearLayout
         android:id = "@+id/controlBtns"
         android:layout_width = "match_parent"
         android:layout_height = "wrap_content"
         android:layout_alignParentBottom = "true"
         android:orientation = "horizontal" >
 
         < Button
             android:id = "@+id/btnStart"
             android:layout_width = "wrap_content"
             android:layout_height = "wrap_content"
             android:text = "Start"  />
 
         < Button
             android:id = "@+id/btnReset"
             android:layout_width = "wrap_content"
             android:layout_height = "wrap_content"
             android:text = "Reset"  />
 
     </ LinearLayout >
 
     < ImageView
         android:id = "@+id/image"
         android:layout_width = "50dp"
         android:layout_height = "50dp"
         android:layout_above = "@+id/controlBtns"
         android:src = "#ffff4444"  />
</ RelativeLayout >

MainActivty类

import  android.os.Bundle;
import  android.support.v7.app.AppCompatActivity;
import  android.view.View;
import  android.widget.ImageView;
 
import  com.nineoldandroids.animation.PropertyValuesHolder;
import  com.nineoldandroids.animation.ValueAnimator;
import  com.nineoldandroids.view.animation.AnimatorProxy;
 
public  class  MainActivity  extends  AppCompatActivity  implements  ValueAnimator.AnimatorUpdateListener {
 
     private  ImageView imageView;
     private  View container;
     private  AnimatorProxy animatorProxy;
 
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
 
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         imageView = (ImageView)  this .findViewById(R.id.image);
         container = findViewById(R.id.container);
         animatorProxy = AnimatorProxy.wrap(imageView);
 
         final  float  originX = animatorProxy.getX();
         final  float  originY = animatorProxy.getY();
         findViewById(R.id.btnStart).setOnClickListener( new  View.OnClickListener() {
             @Override
             public  void  onClick(View v) {
 
                 PropertyValuesHolder widthPropertyHolder = PropertyValuesHolder.ofFloat( "posX" , animatorProxy.getX(), container.getWidth() - imageView.getWidth());
                 PropertyValuesHolder heightPropertyHolder = PropertyValuesHolder.ofFloat( "posY" , animatorProxy.getY(),  0 );
                 ValueAnimator translationAnimator = ValueAnimator.ofPropertyValuesHolder(widthPropertyHolder, heightPropertyHolder);
                 translationAnimator.addUpdateListener(MainActivity. this );
                 translationAnimator.setDuration( 1000 );
                 translationAnimator.start();
                 findViewById(R.id.btnStart).setEnabled( false );
 
             }
         });
 
         findViewById(R.id.btnReset).setOnClickListener( new  View.OnClickListener() {
             @Override
             public  void  onClick(View v) {
                 findViewById(R.id.btnStart).setEnabled( true );
                 animatorProxy.setX(originX);
                 animatorProxy.setY(originY);
             }
         });
 
     }
 
     @Override
     public  void  onAnimationUpdate(ValueAnimator animation) {
         float  posX = (Float) animation.getAnimatedValue();
         float  posY = (Float) animation.getAnimatedValue();
         animatorProxy.setX(posX);
         animatorProxy.setY(posY);
     }
}

运行的效果如下:


我们还可以自己创建一个对象来封装动画的变化值,如下所示

修改MainActivity的代码如下:

 

import  android.os.Bundle;
import  android.support.v7.app.AppCompatActivity;
import  android.util.Log;
import  android.view.View;
import  android.widget.ImageView;
 
import  com.nineoldandroids.animation.TypeEvaluator;
import  com.nineoldandroids.animation.ValueAnimator;
import  com.nineoldandroids.view.animation.AnimatorProxy;
 
public  class  MainActivity  extends  AppCompatActivity  implements  ValueAnimator.AnimatorUpdateListener {
 
     private  ImageView imageView;
     private  View container;
     private  AnimatorProxy animatorProxy;
     float  originX, originY;
 
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
 
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         imageView = (ImageView)  this .findViewById(R.id.image);
         container = findViewById(R.id.container);
         animatorProxy = AnimatorProxy.wrap(imageView);
         findViewById(R.id.btnStart).setOnClickListener( new  View.OnClickListener() {
             @Override
             public  void  onClick(View v) {
                 originX = animatorProxy.getX();
                 originY = animatorProxy.getY();
                 ValueAnimator translationAnimator = ValueAnimator.ofObject( new  PositionTypeEvaluator(),  new  Position(originX, originY),
                         new  Position(container.getWidth() - imageView.getWidth(),  0 ));
                 translationAnimator.addUpdateListener(MainActivity. this );
                 translationAnimator.setDuration( 1000 );
                 translationAnimator.start();
                 findViewById(R.id.btnStart).setEnabled( false );
 
             }
         });
 
         findViewById(R.id.btnReset).setOnClickListener( new  View.OnClickListener() {
             @Override
             public  void  onClick(View v) {
                 findViewById(R.id.btnStart).setEnabled( true );
                 animatorProxy.setX(originX);
                 animatorProxy.setY(originY);
             }
         });
 
     }
 
     @Override
     public  void  onAnimationUpdate(ValueAnimator animation) {
         Position currentPos = (Position) animation.getAnimatedValue();
         animatorProxy.setX(currentPos.getPosX());
         animatorProxy.setY(currentPos.getPosY());
     }
 
     private  class  PositionTypeEvaluator  implements  TypeEvaluator<Position> {
 
         @Override
         public  Position evaluate( float  fraction, Position startValue, Position endValue) {
             Log.d( "postion" , "fraction" +fraction);
             float  posX = startValue.getPosX() + (endValue.getPosX() - startValue.getPosX()) * fraction;
             float  posY = startValue.getPosY() + (endValue.getPosY() - startValue.getPosY()) * fraction;
             return  new  Position(posX, posY);
         }
     }
 
     private  class  Position {
 
         private  float  posX;
         private  float  posY;
 
         public  Position( float  posX,  float  posY) {
             this .posX = posX;
             this .posY = posY;
         }
 
         public  float  getPosX() {
             return  posX;
         }
 
         public  void  setPosX( float  posX) {
             this .posX = posX;
         }
 
         public  float  getPosY() {
             return  posY;
         }
 
         public  void  setPosY( float  posY) {
             this .posY = posY;
         }
     }
}

运行结果类似上图

那fraction是什么呢?截了个图。从0到1的小数。

fraction The elapsed, interpolated fraction of the animation.

这个应该动画过程中的插值小数。


ObjectAnimator

A subclass of ValueAnimator that allows you to set a target object and object property to animate. This class updates the property accordingly when it computes a new value for the animation. You want to use ObjectAnimator most of the time, because it makes the process of animating values on target objects much easier. However, you sometimes want to use ValueAnimator directly becauseObjectAnimator has a few more restrictions, such as requiring specific acessor methods to be present on the target object.

ObjectAnimator是ValueAnimator的子类,可以改变对象的属性值完成动画。我更愿意使用ObjectAnimator,因为它设置对象的动画更为简单。有时候你可能还得使用ValueAnimator,因为ObjectAnimator有一些限制,比如需要特定的方法展示在指定的对象上。

使用ObjectAnimator实现动画方式有两种,一种是XML,一种用JAVA代码实现。

就我自己使用而言,XML代码实现的动画,格式更清晰,一目了然就知道动画中包含哪些步骤,其次XML格式的不支持9和9以下的版本。Java代码实现,更容易做一些动态的处理,比如你的属性动画和屏幕的大小有直接的关系。动画的大小和比例需要根据运行屏幕的大小来动态计算,这个时候就推荐使用Java代码来实现。

其中的AnimatorSet提供组合动画能力的类。并可设置组中动画的时序关系,如同时播放、有序播放或延迟播放。

实例

XML方式实现

我们先用XML的方式实现一个动画。新建一个anim/animation.xml文件,表示动画的内容

< set  xmlns:android = "<a href="http://schemas.android.com/apk/res/android" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/apk/res/android"
  android:ordering = "sequentially" >
  < set  android:ordering = "together" >
  < objectAnimator
  android:duration = "500"
  android:propertyName = "scaleX"
  android:valueTo = "1.05f"  />
  < objectAnimator
  android:duration = "500"
  android:propertyName = "scaleY"
  android:valueTo = "1.05f"  />
  < objectAnimator
  android:duration = "500"
  android:propertyName = "translationY"
  android:valueTo = "100"  />
  </ set >
  < set  android:ordering = "together" >
  < objectAnimator
  android:duration = "500"
  android:propertyName = "scaleX"
  android:valueFrom = "1.05f"
  android:valueTo = "0.3f"  />
  < objectAnimator
  android:duration = "500"
  android:propertyName = "scaleY"
  android:valueFrom = "1.05f"
  android:valueTo = "0.3f"  />
  < objectAnimator
  android:duration = "500"
  android:propertyName = "translationY"
  android:valueTo = "-100"  />
  </ set >
</ set >

 

通过xml文件很好分析动画的步骤。主要分为两个环节,一个是扩大下移的过程,一个是缩小上移的过程。

 

布局anim.xml文件如下所示

 

< LinearLayout  xmlns:android = "<a href="http://schemas.android.com/apk/res/android" "="" style="text-decoration: none; color: blue !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; background-image: none !important; background-position: initial initial !important; background-repeat: initial initial !important;">http://schemas.android.com/apk/res/android"
     android:layout_width = "match_parent"
     android:layout_height = "match_parent"
     android:gravity = "center"
     android:orientation = "vertical" >
     < FrameLayout
         android:id = "@+id/layout"
         android:layout_width = "match_parent"
         android:layout_height = "150dp"
         android:layout_marginLeft = "16dp"
         android:layout_marginRight = "16dp"
         android:background = "@android:color/white" />
     < Button
         android:id = "@+id/click"
         android:layout_width = "wrap_content"
         android:layout_height = "wrap_content"
         android:text = "start" />
</ LinearLayout >

 

主类代码如下所示:

 

import  android.animation.AnimatorInflater;
import  android.animation.AnimatorSet;
import  android.os.Bundle;
import  android.support.v7.app.AppCompatActivity;
import  android.view.View;
import  android.widget.Button;
 
public  class  MainActivity  extends  AppCompatActivity  implements  View.OnClickListener {
 
     private  View view;
     private  Button button;
 
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
 
         super .onCreate(savedInstanceState);
         setContentView(R.layout.anim);
         view = findViewById(R.id.layout);
         button = (Button) findViewById(R.id.click);
         button.setOnClickListener( this );
     }
 
 
     @Override
     public  void  onClick(View v) {
         if  (v.getId() == R.id.click) {
             AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator( this , R.anim.animation);
             animatorSet.setTarget(view);
             animatorSet.start();
         }
 
     }
}

动画效果如下:


Java代码实现(动态计算,缩小到固定的大小)

@Override
     public  void  onClick(View v) {
         if  (v.getId() == R.id.click) {
             final  int   width= 97 ;
             final  int   height= 58 ;
 
             float  scaleTo= 1 .05f;
             Util util= new  Util();
             float  widthRatio = util.dp2px( this , width) / (view.getWidth() * scaleTo);
             float  heightRatio = util.dp2px( this , height) / (view.getHeight() * scaleTo);
             AnimatorSet animatorSet =  new  AnimatorSet();
             //放大并下移
             Animator animator = zoomAndTranslateAnimation(view,  1 , scaleTo,  1 , scaleTo, 100 );
             animator.setDuration( 500 );
             //缩小并上移
             Animator animator1 = zoomAndTranslateAnimation(view, scaleTo, widthRatio, scaleTo, heightRatio, - 100 );
             animator1.setDuration( 500 );
             animatorSet.playSequentially(animator, animator1);
             animatorSet.start();
 
         }
 
     }
 
     public  Animator zoomAndTranslateAnimation(View view,  float  xScaleValueFrom,  float  xScaleValueTo,  float  yScaleValueFrom,
                                               float  yScaleValueTo,  float  translation) {
         PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat( "scaleX" , xScaleValueFrom, xScaleValueTo);
         PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat( "scaleY" , yScaleValueFrom, yScaleValueTo);
         PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat( "translationY" , translation);
         ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, translationY);
         return  animator;
     }
 
 
     class  Util {
         public  int  dp2px(Context context,  int  dp) {
             DisplayMetrics metrics = context.getResources().getDisplayMetrics();
             return  ( int ) (dp * metrics.density);
         }
     }
}

另外,还可以通过AnimatorSet.Builder的方式将要播放的动画联系在一起。

AnimatorSet animatorSet =  new  AnimatorSet();
//放大并下移
Animator animator = zoomAndTranslateAnimation(view,  1 , scaleTo,  1 , scaleTo, 100 );
animator.setDuration( 500 );
AnimatorSet.Builder builder=animatorSet.play(animator);
 
Aniamtor animator1=........
.......
....
builder.with(animator1);


参考链接:https://github.com/android-cn/android-open-project-analysis/blob/master/tech/animation/README.md(非常好的总结,推荐阅读)




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值