Android 属性动画浅谈(一)插值器和估值器
属性动画是API11新加入的特性,和View动画不同,它对作用对象进行了扩展,属性动画可以对任何对象动画,甚至还可以没有对象。除了作用对象进行了扩展以外,属性动画的效果也得到了假期,不再像View动画那样只能支持4中简单的变换。属性动画中有ValueAnimator、ObjectAnimator和AnimatorSet等概念。通过它们可以实现炫彩的的效果!
属性动画中插值器(Interpolator)和估值器(TypeEvaluator)很重要,它们是实现非匀速动画的重要手段。
一、插值器
首先我们先讲一下插值器:
TimeInterpolator:时间插值器,它主要是根据时间的流逝的百分比来计算出当前属性值改变的百分比。我们可以看一下它的源码。
package android.animation;
/**
* 时间插值器定义了一个动画的变化率。
* 这让动画让非线性的移动轨迹,例如加速和减速。
* <hr/>
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* 将动画已经消耗的时间的分数映射到一个表示插值的分数。
* 然后将插值与动画的变化值相乘来推导出当前已经过去的动画时间的动画变化量。
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input 一个0到1.0表示动画当前点的值,0表示开头。1表示结尾<br/> A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return 插值。它的值可以大于1来超出目标值,也小于0来空破底线。<br/>The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
通过它的源码,我们可以看到。该类中主要有一个方法 float getInterpolation(float input);这个方法就是控制动画变化的!
TimeInterpolator是在Android API11时加入的之前类就叫Interpolator。现在Interpolatro继承了TimeInterpolator。
我们看一下源码:
package android.view.animation;
import android.animation.TimeInterpolator;
/**
*
* 一个定义动画变化率的插值器。
* 它允许对基本的(如透明,缩放,平移,旋转)进行加速,减速,重复等动画效果
* <hr/>
* An interpolator defines the rate of change of an animation. This allows
* the basic animation effects (alpha, scale, translate, rotate) to be
* accelerated, decelerated, repeated, etc.
*/
public interface Interpolator extends TimeInterpolator {
// A new interface, TimeInterpolator, was introduced for the new android.animation
// package. This older Interpolator interface extends TimeInterpolator so that users of
// the new Animator-based animations can use either the old Interpolator implementations or
// new classes that implement TimeInterpolator directly.
}
通过Interpolator 源码,我们了解到,这是一个定义动画变化率的插值器。可以对动画进行加速,减速,重复等动画效果!那么接下来我们可以很好的操作了。但在自定义之前,我们先看一下系统给我们提供的这几种插值器。
常用继承类
AccelerateDecelerateInterpolator============动画开始与结束的地方速率改变比较慢,在中间的时候加速。
AccelerateInterpolator===================动画开始的地方速率改变比较慢,然后开始加速。
AnticipateInterpolator ==================开始的时候向后然后向前甩。
AnticipateOvershootInterpolator=============开始的时候向后然后向前甩一定值后返回最后的值。
BounceInterpolator=====================动画结束的时候弹起。
CycleInterpolator======================动画循环播放特定的次数,速率改变沿着正弦曲线。
DecelerateInterpolator===================在动画开始的地方快然后慢。
LinearInterpolator======================以常量速率改变。
OvershootInterpolator====================向前甩一定值后再回到原来位置。
PathInterpolator========================新增的,就是可以定义路径坐标,然后可以按照路径坐标来跑动;
注意其坐标并不是 XY,而是单方向,也就是我可以从0~1,然后弹回0.5 然后又弹到0.7 有到0.3,直到最后时间结束。
接下来我们来看DecelerateInterpolator的源码:
/**
* An interpolator where the rate of change starts out quickly and
* and then decelerates.
*
*/
@HasNativeInterpolator
public class DecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
public DecelerateInterpolator() {
}
/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
* an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
* ease-out effect (i.e., it starts even faster and ends evens slower)
*/
public DecelerateInterpolator(float factor) {
mFactor = factor;
}
public DecelerateInterpolator(Context context, AttributeSet attrs) {
this(context.getResources(), context.getTheme(), attrs);
}
/** @hide */
public DecelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.DecelerateInterpolator, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.DecelerateInterpolator);
}
mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f);
a.recycle();
}
public float getInterpolation(float input) {
float result;
if (mFactor == 1.0f) {
result = (float)(1.0f - (1.0f - input) * (1.0f - input));
} else {
result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
}
return result;
}
private float mFactor = 1.0f;
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createDecelerateInterpolator(mFactor);
}
}
接下来我们再来看一下AccelerateInterpolator的源码:
package android.view.animation;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
/**
*
* 一个开始很慢然后不断加速的插值器。
* <hr/>
* An interpolator where the rate of change starts out slowly and
* and then accelerates.
*
*/
public class AccelerateInterpolator implements Interpolator {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
/**
* Constructor
*
* @param factor
* 动画的快慢度。将factor设置为1.0f会产生一条y=x^2的抛物线。
增加factor到1.0f之后为加大这种渐入效果(也就是说它开头更加慢,结尾更加快)
* <br/>Degree to which the animation should be eased. Seting
* factor to 1.0f produces a y=x^2 parabola(抛物线). Increasing factor above
* 1.0f exaggerates the ease-in effect (i.e., it starts even
* slower and ends evens faster)
*/
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
public AccelerateInterpolator(Context context, AttributeSet attrs) {
TypedArray a =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);
mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
a.recycle();
}
@Override
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
}
看完减速、加速的插值器。我们可以看出了。实现这样的效果。主要是靠这个getInterpolation(float input)这个方法
那接下来我们自己就可以实现一个时间插值器-先减速在加速。
/**
* 自定义插值器-Interpolator---先减速后加速(使用sin)
*
*/
public class DecelerateAccelerateInterpolator implements Interpolator{
@Override
public float getInterpolation(float input) {
float result;
if(input <= 0.5){
result=(float)(Math.sin(Math.PI*input))/2;
}else{
result=(float)(2-Math.sin(Math.PI*input))/2;
}
return result;
}
}
自定义插值器需要实现Interpolator这个接口。而实现先减速在加速的效果。我们可以参考函数 sin()函数。具体为什么,大家私下里去了解!我这里就不详细说了。
二 估值器
估值器(TypeEvaluator) 中文名:类型估值器。也叫做估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值。
系统给出的估值器:IntEvaluator(整型属性) FloatEvaluator(浮点型属性)ArgbEvaluator(针对Color属性)。其中动画的默认刷新率为10ms/帧。
想自定义一个估值器,就必须实现TypeEvaluator接口。这里所有的估值器都要实现TypeEvaluator接口,TypeEvaluator接口具体代码如下。
public interface TypeEvaluator<T> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value.
* @param endValue The end value.
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public T evaluate(float fraction, T startValue, T endValue);
}
从源码中,我们可以看到evaluatede 三个参数分别表示估值小数,开始值和结束值。
接下来使我们自定义的估值器:位置和颜色改变
import android.animation.TypeEvaluator;
/**
* 自定义估值器--位置
*
*/
public class PointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint=(Point)startValue;
Point endPoint=(Point)endValue;
float x=startPoint.getPointX()+fraction*(endPoint.getPointX()-startPoint.getPointX());
float y=startPoint.getPointY()+fraction*(endPoint.getPointY()-startPoint.getPointY());
Point point=new Point(x,y);
return point;
}
}
自定义颜色估值器
import android.animation.TypeEvaluator;
/**
* 颜色估值器(RGB--顺序不可以写倒)
* Created by mjm on 2016/9/20.
*/
public class ColorEvaluator implements TypeEvaluator {
private int mCurrentRed = -1;
private int mCurrentGreen = -1;
private int mCurrentBule = -1;
private String mCurrentColor ;//真正的颜色值
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
String startColor = (String) startValue;
String endColor = (String) endValue;
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBule = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBule = Integer.parseInt(endColor.substring(5, 7), 16);
if (mCurrentRed == -1) {
mCurrentRed = startRed;
}
if (mCurrentGreen == -1) {
mCurrentGreen = startGreen;
}
if (mCurrentBule == -1) {
mCurrentBule = startBule;
}
//计算差值
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int buleDiff = Math.abs(startBule - endBule);
int colorDiff = redDiff + greenDiff + buleDiff;
if (mCurrentRed!=endRed) {
mCurrentRed = getCurrentColor(startRed,endRed,colorDiff,0,fraction);
}else if(mCurrentGreen!=endGreen){
mCurrentGreen=getCurrentColor(startGreen,endGreen,colorDiff,redDiff,fraction);
} else if(mCurrentBule!=endBule){
mCurrentBule=getCurrentColor(startBule,endBule,colorDiff,redDiff+greenDiff,fraction);
}
//计算真正的值
mCurrentColor="#"+getHexString(mCurrentRed)+getHexString(mCurrentGreen)+getHexString(mCurrentBule);
return mCurrentColor;
}
//根据frcation 计算当前颜色值
public int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int)(startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
//10进制转换成16进制
public String getHexString(int color){
String hexString=Integer.toHexString(color);
if(hexString.length()==1){
hexString="0"+hexString;
}
return hexString;
}
}
那么 TimeInterpolator和 TypeEvaluator是怎么协同工作的呢?
三 插值器和估值器结合
它们是实现非匀速动画 的重要手段。属性动画是对属性做动画,属性要实现动画,首先由
TimeInterpolator(插值器)根据时间流逝的百分比计算出当前属性值改变的百分比,并且 插值器 将这个百分比返回,这个时候 插值器 的工作就完成了。
比如
插值器 返回的值是0.5,很显然我们要的不是0.5,而是当前属性的值,即当前属性变成了什么值,这就需要
估值器 根据当前属性改变的百分比来计算改变后的属性值,根据这个属性值,我们就可以设置当前属性的值了。
上面的理论知识可能比较抽象,下面根据一个实例结合系统源码来分析一下:
下图表示一个匀速动画,采用了线性插值器和整型估值算法,在40ms内,View的x属性实现从0到40的变换。
由于动画的默认刷新率为10ms/帧,所以该动画将分为5帧进行。
当动画进行到第三帧的时候,(x=20,t=20ms)当时间t=20ms 的时候,时间流逝的百分比是0.5(20/40=0.5),即时间过了一半。这个百分比不是我们最终想要的,我们关心的是x的变化,那么x应该变化多少呢?插值器和估值算法就起作用了。拿线性插值器来说,线性插值器是实现
匀速动画 的,先看一下线性插值器的源码:
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.BaseInterpolator;
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
很显然,线性插值器的返回值和输入值一样(看getInterpolation方法),因此t=20ms时 插值器 返回的值是 0.5,这意味着 x属性的改变是0.5。这个时候 插值器 的工作已经完成了,插值器的工作就是根据时间流逝的百分比计算出当前属性值改变的百分比
我们得到了当前属性改变的百分比是0.5,即50%。下一步就是要算出x具体变为了什么值,这个时候 估值算法 就起作用了,它的作用就是根据当前属性改变的百分比来计算改变后的属性值。我们先看看系统提供的 整型估值算法 的源码:
package android.animation;
import android.animation.TypeEvaluator;
public class IntEvaluator implements TypeEvaluator<Integer> {
public IntEvaluator() {
}
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return (Int)(startValue+fraction*(endValue-startValue));
}
}
上述代码中的 evaluate方法 的三个参数分别表示 估值小数(fraction),开始值(startValue)和 结束值(endValue)。对于我们的这个例子就是0.5,0和40。我们将这三个值代入求值,即:0+0.5*(40- 0)=20。没错,这就是当t=20ms时 x=20 的由来。
那接下来就让我们运用刚才咱们写的3个自定义吧!
public void startAnimation() {
Point startPoint=new Point(radius,radius);
Point endPoint=new Point(getWidth()-radius,getHeight()-radius);
//位置估值器
ValueAnimator animPoint=ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
animPoint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentPoint = (Point) valueAnimator.getAnimatedValue();
// Log.e("MA","value:---X:---"+currentPoint.getPointX()+"---Y:--"+currentPoint.getPointY());
invalidate();//刷新UI
}
});
//颜色估值器
ObjectAnimatoranimColor=ObjectAnimator.ofObject(this,"color",
new ColorEvaluator(),"#0000FF","#FF0000");
animColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
currentColor=(String )valueAnimator.getAnimatedValue();
Log.e("MA","value:------"+currentColor);
mPaint.setColor(Color.parseColor(currentColor));
}
});
//组合动画
AnimatorSet anim=new AnimatorSet();
anim.play(animPoint).with(animColor);
anim.setDuration(5000);
//加入自定义插值器(先减速在加速
anim.setInterpolator(new DecelerateAccelerateInterpolator());
anim.setInterpolator(new LinearInterpolator());
anim.start();
}