Android动画机制全解析

简介: 导论本文着重讲解Android3.0后推出的属性动画框架Property Animation——Animator。产生原因        3.0之前已有的动画框架——Animation存在一些局限性, Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个V...

导论

本文着重讲解Android3.0后推出的属性动画框架Property Animation——Animator。

产生原因

        3.0之前已有的动画框架——Animation存在一些局限性, Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件。

        而在Animator框架中使用最多的是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值,多个ObjectAnimator组合到AnimatorSet形成一个动画。而且ObjectAnimator能够自动驱动,可以调用setFrameDelay(longframeDelay)设置动画帧之间的间隙时间,调整帧率,减少动画过程中频繁绘制界面,而在不影响动画效果的前提下减少CPU资源消耗。因此,Anroid推出的强大的属性动画框架,基本可以实现所有的动画效果。

强大的原因

        因为属性动画框架操作的是真实的属性值,直接变化了对象的属性,因此可以很灵活的实现各种效果,而不局限于以前的4种动画效果。

ObjectAnimator

        ObjectAnimator是属性动画框架中最重要的实行类,创建一个ObjectAnimator只需通过他的静态工厂类直接返回一个ObjectAnimator对象。传的参数包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过java反射机制来调用set函数修改对象属性值。还包括属性的初始值,最终值,还可以调用setInterpolator设置曲线函数。

ObjectAnimator实例

ObjectAnimator
.ofFloat(view, "rotationX", 0.0F, 360.0F)
.setDuration(1000)
.start();

        这个例子很简单,针对view的属性rotationX进行持续时间为1000ms的0到360的角度变换。
PS:可操纵的属性参数:x/y;scaleX/scaleY;rotationX/ rotationY;transitionX/ transitionY等等。

PS:X是View最终的位置、translationX为最终位置与布局时初始位置的差。所以若就用translationX即为在原来基础上移动多少,X为最终多少。getX()的值为getLeft()与getTranslationX()的和。


1. translationX和translationY:这两个属性作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。

2. rotation、rotationX和rotationY:这三个属性控制View对象围绕支点进行2D和3D旋转。

3. scaleX和scaleY:这两个属性控制着View对象围绕它的支点进行2D缩放。

4. pivotX和pivotY:这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。

5. x和y:这是两个简单实用的属性,它描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和translationX和translationY值的累计和。

6. alpha:它表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明(不可见)。


动画绘制过程的监听

animator.addUpdateListener(new AnimatorUpdateListener() {
			@Override
			public void onAnimationUpdate(ValueAnimator arg0) {
			}
		});

该方法用来监听动画绘制过程中的每一帧的改变,通过这个方法,我们可以在动画重绘的过程中,实现自己的逻辑。

同时修改多个属性值

当然这个可以使用Animationset来实现,这里我们使用一种取巧的方法来实现:
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "xxx", 1.0F, 0.0F)
				.setDuration(500);
		anim.start();
		anim.addUpdateListener(new AnimatorUpdateListener() {
			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				floatcVal = (Float) animation.getAnimatedValue();
				view.setAlpha(cVal);
				view.setScaleX(cVal);
				view.setScaleY(cVal);
			}
		});

我们可以监听一个并不存在的属性,而在监听动画更新的方法中,去修改view的属性,监听一个不存在的属性的原因就是,我们只需要动画的变化值,通过这个值,我们自己来实现要修改的效果,实际上,更直接的方法,就是使用ValueAnimator来实现,其实ObjectAnimator就是 ValueAnimator的子类,这个在下面会具体讲到。

为不具有get/set方法的属性提供修改方法

        Google在应用层为我们提供了2种解决方法,一种是通过自己写一个包装类,来为该属性提供get/set方法,还有一种是通过ValueAnimator来实现,ValueAnimator的方法我们在下面会具体讲解,这里讲解下如何使用自定义的包装类来给属性提供get/set方法。

包装类

private static class WrapperView {
			private View mTarget;

			public WrapperView(View target) {
				mTarget = target;
			}

			public int getWidth() {
				return mTarget.getLayoutParams().width;
			}

			public void setWidth(int width) {
				mTarget.getLayoutParams().width = width;
				mTarget.requestLayout();
			}
		}

使用方法:

ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();

这样就间接给他加上了get/set方法,从而可以修改其属性实现动画效果。

多动画效果的另一种实现方法——propertyValuesHolder

public void propertyValuesHolder(View view) {
		PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,
				0f, 1f);
		PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f,
				0, 1f);
		PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f,
				0, 1f);
		ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ)
				.setDuration(1000).start();
	}

ValueAnimator

        说简单点,ValueAnimator就是一个数值产生器,他本身不作用于任何一个对象,但是可以对产生的值进行动画处理。
ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
		animator.setTarget(view);
		animator.setDuration(1000).start();
		animator.addUpdateListener(new AnimatorUpdateListener() {
			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				Float value = (Float) animation.getAnimatedValue();
				imageView.setTranslationY(value);
			}
		});

        通过这个动画我们可以发现,和我们在上面提供的使用ObjectAnimator的方法很像,的确,我前面说这个才是专业的写法,就是这个原因,动画生成的原理就是通过差值器计算出来的一定规律变化的数值作用到对象上来实现对象效果的变化,因此我们可以使用ObjectAnimator来生成这些数,然后在动画重绘的监听中,完成自己的效果。
        ValueAnimator是计算动画过程中变化的值,包含动画的开始值,结束值,持续时间等属性。但并没有把这些计算出来的值应用到具体的对象上面,所以也不会有什么的动画显示出来。要把计算出来的值应用到对象上,必须为ValueAnimator注册一个监听器ValueAnimator.AnimatorUpdateListener,该监听器负责更新对象的属性值。在实现这个监听器的时候,可以通过getAnimatedValue()的方法来获取当前帧的值。
        ValueAnimator封装了一个TimeInterpolator,TimeInterpolator定义了属性值在开始值与结束值之间的插值方法。ValueAnimator还封装了一个TypeAnimator,根据开始、结束值与TimeIniterpolator计算得到的值计算出属性值。ValueAnimator根据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后根据TimeInterpolator计算出另一个因子,最后TypeAnimator通过这个因子计算出属性值,例如在10ms时(total 40ms):
首先计算出时间因子,即经过的时间百分比:t=10ms/40ms=0.25
经插值计算(inteplator)后的插值因子:大约为0.15,如果使用了AccelerateDecelerateInterpolator,计算公式为(input即为时间因子):
(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
最后根据TypeEvaluator计算出在10ms时的属性值:0.15*(40-0)=6pixel。如果使用TypeEvaluator为FloatEvaluator,计算方法为 :
public Float evaluate(float fraction, Number startValue, Number endValue) {
		float startFloat = startValue.floatValue();
		return startFloat + fraction * (endValue.floatValue() - startFloat);
	}

参数分别为上一步的插值因子,开始值与结束值。

ValueAnimator与ObjectAnimator实例

package com.example.animtest;

import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.BounceInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;

public class AnimateFreeFall extends Activity {

	private int screenHeight;
	private int screenWidth;
	private ImageView imageView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		setContentView(R.layout.animate_free_fall);
		DisplayMetrics metrics = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(metrics);
		screenHeight = metrics.heightPixels;
		screenWidth = metrics.widthPixels;

		imageView = (ImageView) findViewById(R.id.im);
	}

	public void clean(View view) {
		imageView.setTranslationX(0);
		imageView.setTranslationY(0);
	}

	public void freefall(View view) {
		final ValueAnimator animator = ValueAnimator.ofFloat(0, screenHeight
				- imageView.getHeight());
		animator.setTarget(view);
		animator.setInterpolator(new BounceInterpolator());
		animator.setDuration(1000).start();
		animator.addUpdateListener(new AnimatorUpdateListener() {

			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				Float value = (Float) animation.getAnimatedValue();
				imageView.setTranslationY(value);
			}
		});
	}

	public void parabola(View view) {
		ValueAnimator animator = ValueAnimator.ofObject(
				new TypeEvaluator<PointF>() {

					@Override
					public PointF evaluate(float fraction, PointF arg1,
							PointF arg2) {
						PointF p = new PointF();
						p.x = fraction * screenWidth;
						p.y = fraction * fraction * 0.5f * screenHeight * 4f
								* 0.5f;
						return p;
					}
				}, new PointF(0, 0));
		animator.setDuration(800);
		animator.setInterpolator(new LinearInterpolator());
		animator.start();
		animator.addUpdateListener(new AnimatorUpdateListener() {

			@Override
			public void onAnimationUpdate(ValueAnimator animator) {
				PointF p = (PointF) animator.getAnimatedValue();
				imageView.setTranslationX(p.x);
				imageView.setTranslationY(p.y);
			}
		});
	}
}


        效果如下图:


        有一点需要注意的是,由于ofInt,ofFloat等无法使用,我们自定义了一个TypeValue,每次根据当前时间返回一个PointF对象,(PointF和Point的区别就是x,y的单位一个是float,一个是int point float的意思)PointF中包含了x,y的当前位置,然后在更新监听中更新。

        自定义TypeEvaluator传入的泛型可以根据自己的需求,自己设计个Bean。

动画事件的监听

        通过监听这个事件在属性的值更新时执行相应的操作,对于ValueAnimator一般要监听此事件执行相应的动作,不然Animation没意义(但是可用于计时),在ObjectAnimator(继承自ValueAnimator)中会自动更新属性,所以不必监听。在函数中会传递一个ValueAnimator参数,通过此参数的getAnimatedValue()取得当前动画属性值。
PS:根据应用动画的对象或属性的不同,可能需要在onAnimationUpdate函数中调用invalidate()函数刷新视图通过动画的各种状态,我们可以监听动画的各种状态。

ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 0.5f);
		anim.addListener(new AnimatorListener() {
			@Override
			public void onAnimationStart(Animator animation) {
			}

			@Override
			public void onAnimationRepeat(Animator animation) {
			}

			@Override
			public void onAnimationEnd(Animator animation) {
			}

			@Override
			public void onAnimationCancel(Animator animation) {
			}
		});
		anim.start();

可以看见,API提供了开始、重复、结束、取消等各种状态的监听,同时,API还提供了一种简单的监听方法,可以不用监听所有的事件:
anim.addListener(new AnimatorListenerAdapter() {
			@Override
			public void onAnimationEnd(Animator animation) {
			}
		});

通过AnimatorListenerAdapter来选择你需要监听的事件

动画监听的实例应用

package com.example.animtest;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class AnimateMoveInSecond extends Activity {

	private ImageView imageView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.animate_move_in_second);
		imageView = (ImageView) findViewById(R.id.imageView1);
	}

	public void doit(View view) {
		ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha",
				1.0f, 0f);
		animator.setDuration(1000);
		animator.start();
		animator.addListener(new AnimatorListenerAdapter() {

			@Override
			public void onAnimationEnd(Animator animation) {
				super.onAnimationEnd(animation);
				ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,
						"alpha", 0f, 1.0f);
				animator.setDuration(1000);
				animator.start();
				imageView.setTranslationY(400);
			}
		});
	}
}


效果如下图:

AnimatorSet

        AnimatorSet用于实现多个动画的协同作用。效果如下:



ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView, "scaleX",
				1f, 2f);
		ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView, "scaleY",
				1f, 2f);
		ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView,
				"translationY", 0f, 500f);
		AnimatorSet set = new AnimatorSet();
		set.setDuration(1000);
		set.playTogether(animator1, animator2, animator3);
		set.start();

AnimatorSet中有一系列的顺序控制方法:playTogether、playSequentially、animSet.play().with()、defore()、after()等。用来实现多个动画的协同工作方式。

使用xml来创建动画

        属性动画于以前的动画一样,也支持通过xml文件来创建动画,下面是一个简单的例子:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType" >
</objectAnimator>

public void scaleX(View view)
{
// 加载动画
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
anim.setTarget(mMv);
anim.start();
}

布局动画

         布局动画是指ViewGroup在布局时产生的动画效果

LayoutTransition动画

        通过LayoutTransition来实现容器在添加子view的时候的动画过渡效果:


package com.example.animtest;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.Keyframe;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateLayoutTransition extends Activity {

	private LinearLayout ll;
	private LayoutTransition mTransition = new LayoutTransition();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.animate_layout_transition);

		ll = (LinearLayout) findViewById(R.id.ll);
		setupCustomAnimations();
		ll.setLayoutTransition(mTransition);
	}

	public void add(View view) {
		final Button button = new Button(this);
		ll.addView(button);
		button.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				ll.removeView(button);
			}
		});
	}

	// 生成自定义动画
	private void setupCustomAnimations() {
		// 动画:CHANGE_APPEARING
		// Changing while Adding
		PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
		PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
		PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0,
				1);
		PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom",
				0, 1);
		PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX",
				1f, 0f, 1f);
		PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY",
				1f, 0f, 1f);

		final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
				this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleX,
				pvhScaleY).setDuration(
				mTransition.getDuration(LayoutTransition.CHANGE_APPEARING));
		mTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);
		changeIn.addListener(new AnimatorListenerAdapter() {
			public void onAnimationEnd(Animator anim) {
				View view = (View) ((ObjectAnimator) anim).getTarget();
				// View也支持此种动画执行方式了
				view.setScaleX(1f);
				view.setScaleY(1f);
			}
		});

		// 动画:CHANGE_DISAPPEARING
		// Changing while Removing
		Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
		Keyframe kf1 = Keyframe.ofFloat(.9999f, 360f);
		Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
		PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe(
				"rotation", kf0, kf1, kf2);
		final ObjectAnimator changeOut = ObjectAnimator
				.ofPropertyValuesHolder(this, pvhLeft, pvhTop, pvhRight,
						pvhBottom, pvhRotation)
				.setDuration(
						mTransition
								.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
		mTransition
				.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeOut);
		changeOut.addListener(new AnimatorListenerAdapter() {
			public void onAnimationEnd(Animator anim) {
				View view = (View) ((ObjectAnimator) anim).getTarget();
				view.setRotation(0f);
			}
		});

		// 动画:APPEARING
		// Adding
		ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 90f,
				0f).setDuration(
				mTransition.getDuration(LayoutTransition.APPEARING));
		mTransition.setAnimator(LayoutTransition.APPEARING, animIn);
		animIn.addListener(new AnimatorListenerAdapter() {
			public void onAnimationEnd(Animator anim) {
				View view = (View) ((ObjectAnimator) anim).getTarget();
				view.setRotationY(0f);
			}
		});

		// 动画:DISAPPEARING
		// Removing
		ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "rotationX", 0f,
				90f).setDuration(
				mTransition.getDuration(LayoutTransition.DISAPPEARING));
		mTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut);
		animOut.addListener(new AnimatorListenerAdapter() {
			public void onAnimationEnd(Animator anim) {
				View view = (View) ((ObjectAnimator) anim).getTarget();
				view.setRotationX(0f);
			}
		});

	}
}

上面的例子中自定义了 LayoutTransition来修改默认的过渡动画,如果保持默认则使用系统默认的动画效果。
过渡的类型一共有四种:
LayoutTransition.APPEARING 当一个View在ViewGroup中出现时,对此View设置的动画
LayoutTransition.CHANGE_APPEARING当一个View在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画
LayoutTransition.DISAPPEARING当一个View在ViewGroup中消失时,对此View设置的动画
LayoutTransition.CHANGE_DISAPPEARING当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画
LayoutTransition.CHANGE 不是由于View出现或消失造成对其他View位置造成影响,然后对其他View设置的动画。
注意动画到底设置在谁身上,此View还是其他View。

AnimateLayoutChanges动画

        ViewGroup的xml属性中有一个默认的animateLayoutChanges属性,设置该属性,可以添加ViewGroup增加view的过渡效果:



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="add"
android:text="Add Button" />
</LinearLayout>

LayoutAnimation动画

        通过设置LayoutAnimation也同样可以实现布局动画效果,实例如下:



package com.example.animtest;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.LayoutAnimationController;
import android.view.animation.ScaleAnimation;
import android.widget.LinearLayout;

public class AnimateLayoutAnimation extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.animate_layout_animation);
		
		LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
		ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
		sa.setDuration(2000);
		// 第二个参数dely : the delay by which each child's animation must be offset
		LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F);
		// 设置显示的顺序 这个必须要在dely不为0的时候才有效
		lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
		ll.setLayoutAnimation(lac);
	}
}

View的animate方法

        3.0后android对View也提供了直接作用的动画API:
view.animate().alpha(0).y(100).setDuration(1000)
				.withStartAction(new Runnable() {
					@Override
					public void run() {
					}
				}).withEndAction(new Runnable() {
					@Override
					public void run() {
						runOnUiThread(new Runnable() {
							@Override
							public void run() {
							}
						});
					}
				}).start();

Interpolators(插值器)

        插值器和估值器,是实现非线性动画的基础,了解这些东西,才能作出不一样的动画效果。所谓的插值器,就是通过一些数学物理公式,计算出一些数值,提供给动画来使用。就好比我们定义了起始值是0,结束值是100,但是这0到100具体是怎么变化的呢,这就是插值器产生的结果,线性的,就是匀速增长,加速的,就是按加速度增长。这些增加的算法公式,已经不需要我们来自己设计了,Android内置了7种插值器,基本可以满足需求,当然你也可以自定新的插值器。
AccelerateInterpolator 加速
Decelerate 减速
AccelerateDecelerateInterpolator 开始,和结尾都很慢,但是,中间加速
AnticipateInterpolator 开始向后一点,然后,往前抛
OvershootInterpolator 往前抛超过一点,然后返回来
AnticipateOvershootInterpolator 开始向后一点,往前抛过点,然后返回来 
BounceInterpolator 结束的时候弹一下
LinearInterpolator 默认 匀速

TypeEvalutors (估值器)

        根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值,android提供了以下几个evalutor:
IntEvaluator:属性的值类型为int;
FloatEvaluator:属性的值类型为float;
ArgbEvaluator:属性的值类型为十六进制颜色值;
TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。
  自定义TypeEvalutor很简单,只需要实现一个方法,如FloatEvalutor的定义:
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);
		}
	}

        根据动画执行的时间跟应用的Interplator,会计算出一个0~1之间的因子,即evalute函数中的fraction参数。

KeyFrame

        keyFrame是一个 时间/值 对,通过它可以定义一个在特定时间的特定状态,即关键帧,而且在两个keyFrame之间可以定义不同的Interpolator,就好像多个动画的拼接,第一个动画的结束点是第二个动画的开始点。KeyFrame是抽象类,要通过ofInt(),ofFloat(),ofObject()获得适当的KeyFrame,然后通过PropertyValuesHolder.ofKeyframe获得PropertyValuesHolder对象,如以下例子:
Keyframe kf0 = Keyframe.ofInt(0, 400);
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
Keyframe kf3 = Keyframe.ofInt(1f, 500);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation);
rotationAnim.setDuration(2000);

        上述代码的意思为:设置btn对象的width属性值使其:
开始时 Width=400
动画开始1/4时 Width=200
动画开始1/2时 Width=400
动画开始3/4时 Width=100
动画结束时 Width=500
        第一个参数为时间百分比,第二个参数是在第一个参数的时间时的属性值。
定义了一些Keyframe后,通过PropertyValuesHolder类的方法ofKeyframe一个PropertyValuesHolder对象,然后通过ObjectAnimator.ofPropertyValuesHolder获得一个Animator对象。
用下面的代码可以实现同样的效果(上述代码时间值是线性,变化均匀):


ObjectAnimator oa=ObjectAnimator.ofInt(btn2, "width", 400,200,400,100,500);
oa.setDuration(2000);
oa.start();


代码下载地址:http://download.csdn.net/detail/x359981514/7721651


目录
相关文章
|
22天前
|
PHP 项目管理 开发者
深入解析PHP的命名空间和自动加载机制
【4月更文挑战第4天】 在PHP的编程世界中,命名空间和自动加载机制是构建大型应用程序时不可或缺的工具。本文将深入探讨这两个概念,揭示它们如何简化代码结构、避免类名冲突以及提高代码维护性。通过对PHP命名空间的由来、作用域和使用方法的细致剖析,以及对自动加载机制工作原理和应用实践的全面讲解,读者将获得有效管理复杂项目中依赖关系的能力。
|
24天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
51 1
|
1月前
|
消息中间件 Unix Linux
Linux进程间通信(IPC)介绍:详细解析IPC的执行流程、状态和通信机制
Linux进程间通信(IPC)介绍:详细解析IPC的执行流程、状态和通信机制
55 1
|
1月前
|
存储 缓存 NoSQL
【Redis】Redis魔法:揭秘Key的自动消失术——过期删除机制解析
【Redis】Redis魔法:揭秘Key的自动消失术——过期删除机制解析
131 0
|
1月前
|
存储 Java
ArrayList的初始化容量与扩容机制解析
ArrayList的初始化容量与扩容机制解析
|
3月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
1月前
|
资源调度 算法 Linux
Linux进程/线程的调度机制介绍:详细解析Linux系统中进程/线程的调度优先级规则
Linux进程/线程的调度机制介绍:详细解析Linux系统中进程/线程的调度优先级规则
92 0
|
14天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
21天前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
23 1
Android开发之使用OpenGL实现翻书动画
|
1月前
|
监控 算法 Unix
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
61 0

推荐镜像

更多