Android 属性动画使用解析 - 属性动画高级用法

7,435 阅读11分钟

上一篇文章Android 属性动画使用解析-属性动画基本用法介绍了属性动画的基本用法,这一篇文章继续介绍一些高级的用法,在上一篇文章中我们知道了通过Animator.start方法启动一个动画,当动画执行到我们设置的时间duration末就会自动结束一个动画,那么如果我们想手动结束一个动画该如何操作呢?动画执行过程中可以暂停吗?这是我们今天介绍的一个部分。

我们知道动画执行时需要一个时间段的,那么动画执行过程中如果我们想根据动画的执行进度处理一些业务逻辑,又或者当动画开始或者结束时处理业务,这种情况如何处理呢?上面所说的业务逻辑的处理是可以实现的,通过属性动画的监听器,Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener实例,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。

补间动画我们知道是针对View进行处理的,属性动画不仅仅对View进行处理了,它可以对任意对象进行操作。上篇文章我们说过,如果自定义一个View,View在运动过程中更改了背景色,这一篇文章中就介绍如何来对画笔Paint进行动画过渡处理。

动画流

动画流事实上就是动画执行过程中的各种状态,跟Activity的生命周期方法差不多但是也有差别,生命周期所有方法在一个周期中一定会执行完全,但是动画流中的各个方法在动画运行过程中不一定会被执行,下面我们来看一下一个动画包含了哪些状态。

Animator.start()   // 启动动画
Animator.end()     // 结束动画
Animator.cancel()  // 取消动画 
Animator.pause()   // API 19新增的方法,暂停动画
Animator.resume()  // API 19新增的方法; 重新启动暂停的动画

上面有两种方式可以停止一个动画,你可以使用end或者cancel来停止动画,这两种方式动画停止后都只能调用start方法才可以重启动画。当然了end方法和cancel方法去停止一个动画也是有区别的,cancel方法动画会停止在它当时的动画轨迹状态上面,然而end方法会立刻让动画过渡到完成状态。

在Android API 19上面又增加了两个方法,这两个方法的使用跟Activity生命周期中的pause和resume方法功能很类似。在以前,如果一个动画调用了cancel方法停止后,想要重新启动画必须调用start方法重新从初始状态开启,那么现在我们可以调用pause方法了,动画会停止在它原有的状态轨迹上,当我们想重新让动画从它轨迹上面运行时,此时我们可以调用resume方法让动画从暂停状态轨迹处继续运行。

查询动画所处的状态

有时候我们想知道动画此时处于动画流的哪个状态,为此Android系统为我们提供了下面三个方法。

boolean isStarted()  // API 14
boolean isRunning()
boolean isPaused()   // API 19

isStarted将会返回true当我们调用start方法之后,如果调用了end或者cancel方法,此时isStarted返回false,记住一点,当我们调用delay方法将动画延时一定时间之后再执行,但是此时isStarted仍然返回true。isRunning在延时播放动画上面跟isStarted有区别,如果延时播放动画,在延时时段isRunning将会返回false,其余情况跟isStarted返回值相同。
isPaused方法除非在调用pause方法是返回true,其余情况都返回false。

下面我们写一个简单的示例来对动画流中的各个状态进行测试,代码很简单,三个TextView文本用来标记动画执行过程中的状态,然后是五个按钮分别调用上面所讲的动画流的五种状态,当我们点击按钮时记录下动画当前的状态即可。

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.anim06);
	imageView = (ImageView) findViewById(R.id.imageView);
	tv_isStarted = (TextView) findViewById(R.id.tv_isStarted);
	tv_isRunning = (TextView) findViewById(R.id.tv_isRunning);
	tv_isPaused = (TextView) findViewById(R.id.tv_isPaused);
 
	anim = ObjectAnimator.ofFloat(imageView, "rotation", 0, 360);
	anim.setDuration(1000);
	anim.setRepeatCount(5);
	anim.setRepeatMode(ObjectAnimator.RESTART);
}
 
public void start(View v) {
	anim.start();
	setStatus();
}
 
public void end(View v) {
	anim.end();
	setStatus();
}
 
public void cancel(View v) {
	anim.cancel();
	setStatus();
}
 
// API level 19
public void pause(View v) {
	anim.pause();
	setStatus();
}
 
// API level 19
public void resume(View v) {
	anim.resume();
	setStatus();
}
 
public void setStatus() {
	tv_isStarted.setText("isStarted = " + anim.isStarted());//API 14
	tv_isRunning.setText("isRunning = " + anim.isRunning());
	tv_isPaused.setText("isPaused = " + anim.isPaused());// API 19
}

示例运行对比图如下:

动画监听器

属性动画才有监听器,补间动画并没有设置监听器,属性动画监听是通过Animator的addListener方法对动画状态进行监听。在上一篇文章中已经通过类图介绍过ObjectAnimator继承自ValueAnimator,而ValueAnimator继承自Animator,所以Animator能用到的监听方式子类也可以使用,AnimatorSet也是继承自Animator,因此同样可以使用监听器。

Animator为我们提供了两个监听接口,方便我们针对不同方式监听,其中有一个是针对API 19以上的才适用,接口如下:


public static interface AnimatorListener
public static interface AnimatorPauseListener //API 19

事实上还有一个监听器,它位于ValueAnimator类中,在介绍属性动画第一个示例中已经使用过了,它是通过addUpdateListener方法添加监听事件的,接口如下:

public static interface AnimatorUpdateListener

AnimatorListener

为动画添加监听器也很简单,下面是一个动画监听器的示例代码:

Java

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) {  
	}  
});   

上面可以看到,动画监听器中的方法也很容易明白,就不做文字解释了。但是由于监听器是一个接口,所以我们要实现其中所有的方法,这样实现是不是有点繁琐了。不用担心,系统为我们提供了一个AnimatorListenerAdapter适配器,这是一个抽象类,平常我们开发中也遇到过这种情况,如果我们自定义一个监听器,如果里面定义了许多方法,一般我们会重新定义一个抽象类来实现这个接口,在开发的时候如果我们只需要实现某些方法而不是全部方法,该抽象类就是我们最好的选择。AnimatorListenerAdapter就是上面所讲述的实现方式,它仅仅是实现了Animator.AnimatorListener接口,没有做任何方法的实现。


anim.addListener(new AnimatorListenerAdapter() {  
	@Override  
	public void onAnimationEnd(Animator animation) {  
	}  
});  

AnimatorPauseListener

AnimatorPauseListener是Android API 19中新增的一个监听器,它只有两个抽象方法需要实现,事实上就是监听API 19新增加的动画流中的两个方法,pause和resume方法。

Java

Animator.AnimatorPauseListener pauseListener=new AnimatorPauseListener() {	
	@Override
	public void onAnimationResume(Animator animation) {
	}
	
	@Override
	public void onAnimationPause(Animator animation) {
	}
};

AnimatorUpdateListener

AnimatorUpdateListener类只有一个方法onAnimationUpdate,在该方法中我们可以监听动画值大小的改变,通过ObjectValue可以很简单的改变一个图片透明度,事实上通过AnimatorUpdateListener类我们也可以实现图片透明度的变化,示例代码如下:

ValueAnimator anim = ValueAnimator.ofFloat(1, 0);
anim.setDuration(100);
anim.addUpdateListener(new AnimatorUpdateListener() {
	@Override
	public void onAnimationUpdate(ValueAnimator animation) {
		float currentValue = (Float) animation.getAnimatedValue();
		imageView.setAlpha(currentValue);
	}
});
anim.start();

TypeEvaluator

TypeEvaluator模式计算器,什么是模式计算器呢?在属性动画中,给出了某个属性的初始值和结束值,初始值通过一定的算法平滑过度到结束值的这一过程就是通过TypeEvaluator来实现的。当然了一般情况下我们都不会自己写一个算法来实现TypeEvaluator,但是了解一下它的核心机制,以防开发过程中遇到系统中模式计算器解决不了的问题。

系统为我们提供了下列类型的Evaluator:

  • ArgbEvaluator
  • FloatArrayEvaluator
  • FloatEvaluator
  • IntArrayEvaluator
  • IntEvaluator
  • PointFEvaluator
  • RectEvaluator

先分析一下TypeEvaluator的源码:

public interface TypeEvaluator {
    public T evaluate(float fraction, T startValue, T endValue);
}

记住一点无论我们实现一个多么复杂的TypeEvaluator,都是一个实现一个从初始值到结束值线性的均匀变化逻辑,那么有些人可能问了,为什么有些动画变换有快有慢呢?快慢的变化不是由TypeEvaluator来确定的,它是由插值器Interpolator来计算得来的,插值器决定了动画执行的快慢,插值器Interpolator下篇文章再继续讲解。事实上插值器最终的返回结果就是TypeEvaluator接口中抽象方法的参数fraction,抽象方法evaluate核心计算方程式就是y = x0 + t * (x1 – x0),其中y就是方法的返回值,t是fraction,x0是startValue,x1是endValue。

FloatEvaluator

用文字描述TypeEvaluator感觉还是很抽象,接下来通过一个示例就很容易明白了。前面我们使用过一个ValueAnimator.ofFloat()方法实现了初始值到结束值的平滑过渡,事实上这个过程就是通过FloatEvalutor来实现的,我们来看一下FloatEvaluator的代码实现:


public class FloatEvaluator implements TypeEvaluator {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

实现TypeEvaluator接口,重写evaluate方法,然后套用公式y = x0 + t * (x1 – x0)实现方法,逻辑清晰了吧,这就是核心机制。用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。

PointFEvalutor

简单的TypeEvaluator实现了,接下来我们看一个相对复杂的实现,Android系统内置了一个坐标类PointF,如果我们让一个坐标点从屏幕的左上角平滑移动到右下角,这时候就需要实现一个PoinF类型的TypeEvalutor。复杂类型的实现机制跟简单类型的实现机制是一样的,我们知道坐标点PointF的表示方式是(float x,float y),将它的两个属性x和y分别平滑过渡到有右下角的坐标点x1和y1,然后就可以构建一个右下角的坐标点PointF1,这样就实现了平滑过渡,算法思想就是这样的,下面是代码实现:


public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
	float x = startValue.x + (fraction * (endValue.x - startValue.x));
	float y = startValue.y + (fraction * (endValue.y - startValue.y));
 
	if (mPoint != null) {
		mPoint.set(x, y);
		return mPoint;
	} else {
		return new PointF(x, y);
	}
}

ArgbEvalutor

前面说过了,补间动画可以做出很多动画效果,但是背景色更改却无法实现,属性动画可以更改背景色,接下来我们就来看看更改背景色动画的核心的一个类ArgbEvalutor,ArgbEvalutor就是一个色值过渡计算器,给定一个初始颜色和一个结束颜色,它的内部实现了一个颜色平滑过渡的算法。Argb就是色彩的表示类型,A(alpha)R(red)G(green)B(blue)分别表示不透明度、红、绿、蓝,下面就是ArgbEvaluator的代码实现:

Java

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

将表示色值的四个类型分别使用公式y = x0 + t * (x1 – x0)计算过渡值,最后在按位与合并就可以了。

示例

接下来我们通过上面所讲的内容做一个简单的demo,让一个小球从屏幕左上角平滑移动到右下角,在移动过程中将小球的背景色从蓝色平滑过渡到红色。

private static final float RADIUS = 50F;
 
	private Paint mPaint;
	private PointF mPointF;
 
	public PointFView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}
 
	//初始化画笔和坐标
	private void init() {
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mPaint.setColor(Color.BLUE);
 
		mPointF = new PointF(RADIUS, RADIUS);
	}
 
	@Override
	protected void onDraw(Canvas canvas) {
		drawCircle(canvas);
	}
	
	//绘制小球
	private void drawCircle(Canvas canvas) {
		float x = mPointF.x;
		float y = mPointF.y;
		canvas.drawCircle(x, y, RADIUS, mPaint);
	}
 
	private void startAnimation() {
		PointF startPoint = new PointF(RADIUS, RADIUS);
		PointF endPoint = new PointF(getWidth() - RADIUS, getHeight() - RADIUS);
		ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(), startPoint, endPoint);
		anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
			//坐标改变 进行图像重绘
			public void onAnimationUpdate(ValueAnimator animation) {
				mPointF = (PointF) animation.getAnimatedValue();
				invalidate();
			}
		});
		//背景色渐变动画
		ObjectAnimator anim2 = ObjectAnimator.ofObject(mPaint, "color", new ArgbEvaluator(), Color.BLUE, Color.RED);
		AnimatorSet animSet = new AnimatorSet();
		animSet.play(anim).with(anim2);
		animSet.setDuration(5000);
		animSet.start();
 
	}
 
	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		super.onWindowFocusChanged(hasWindowFocus);
		startAnimation();
	}
 
}

在平移的同时更改背景色,所以是两个动画,这是采用了组合动画的实现方式,ObjectAnimator的ofObject方法传入的是一个Paint示例,组合动画在5秒内从左上角移动到右下角,效果图如下:

查看图片

结束语

本篇文章就讲解这里,限于能力有限以及对于动画理解还不是很深入,文章中如有错误之处还请及时之处以求共同进步。我们在开发过程中对动画的处理最常见的还是对View的处理,属性动画操作相对来说比补间动画已经简单了许多,那么面对最常见的View的动画处理是不是仍然有更简单的处理方式呢?这个答案是肯定的,就在 ViewPropertyAnimator类中。前面我们已经提过插值器Interpolator,ViewPropertyAnimator和插值器Interpolator两个知识点在下篇文章中再另作介绍。

本文地址www.sunnyang.com/401.html

参考资料

Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

Android Property Animations: Controlling Animation Flow

Android Property Animations: Animator Listeners