Interpolator定义了一个动画中的特定值作为时间的函数的计算(根据时间流逝的百分比计算出当前属性值改变的百分比)。例如,您可以指定在整个动画过程中线性的动画,使动画在整个时间内均匀地移动,或者你可以指定要使用的非线性的动画,加速开始,减速结束动画
Interpolator | xml资源id | 作用 |
---|---|---|
AccelerateDecelerateInterpolator | @android:anim/accelerate_decelerate_interpolator | 变化速度开始和结束缓慢,但在中间加速。 |
AccelerateInterpolator | @android:anim/accelerate_interpolator | 变化速度开始缓慢,然后加速 |
AnticipateInterpolator | @android:anim/anticipate_interpolator | 其变化从后向开始,然后向前抛出 |
AnticipateOvershootInterpolator | @android:anim/anticipate_overshoot_interpolator | 其变化从后向开始,向前抛出并超调目标值,然后返回到最后的值 |
BounceInterpolator | @android:anim/bounce_interpolator | 其变化在末端反弹 |
CycleInterpolator | @android:anim/cycle_interpolator | 动画在一定数目的循环中重复 |
DecelerateInterpolator | @android:anim/decelerate_interpolator | 变化速度迅速开始,然后减速 |
LinearInterpolator | @android:anim/linear_interpolator | 变化率为常数 |
OvershootInterpolator | @android:anim/overshoot_interpolator | 其变化会向前抛出,并超过最后一个值,然后返回 |
TimeInterpolator | 一个接口,可以自定义插值器 |
系统默认的插值器是AccelerateDecelerateInterpolator
当在XML文件设置插值器时,只需传入对应的插值器资源ID即可
"@android:anim/accelerate_interpolator">
...
当在Java代码设置插值器时,只需创建对应的插值器对象即可
Animation.setInterpolator();
Animator.setInterpolator();
步骤1、在res/anim/目录下创建filename.xml文件;
步骤2、修改自定义的插值器xml文件
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="2">
accelerateInterpolator>
通过xml定义的插值器,用于xml定义的动画
有些插值器的一些属性,但是有些插值器却不具备修改属性:
<accelerateDecelerateInterpolator>
无可自定义的attribute。
<accelerateInterpolator>
android:factor 浮点值,加速速率(默认值为1)。
<anticipateInterploator>
android:tension 浮点值,起始点后拉的张力数(默认值为2)。
<anticipateOvershootInterpolator>
android:tension 浮点值,起始点后拉的张力数(默认值为2)。
android:extraTension 浮点值,拉力的倍数(默认值为1.5)。
<bounceInterpolator>
无可自定义的attribute。
<cycleInterplolator>
android:cycles 整形,循环的个数(默认为1)。
<decelerateInterpolator>
android:factor 浮点值,减速的速率(默认为1)。
<linearInterpolator>
无可自定义的attribute。
<overshootInterpolator>
android:tension 浮点值,超出终点后的张力(默认为2)
这些属性都可以通过方法来设置。
来看一下AccelerateInterpolator的源码:
@HasNativeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
/**
* Constructor
*
* @param factor 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) {
this(context.getResources(), context.getTheme(), attrs);
}
/** @hide */
public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
}
mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
}
}
插值器的本质就是根据动画的进度(0%-100%)计算出当前属性值改变的百分比。
其中实现的地方就在getInterpolation(float input)方法中。
自定义插值器需要实现 Interpolator / TimeInterpolator接口和复写getInterpolation();
补间动画 实现 Interpolator接口;属性动画实现TimeInterpolator接口。
TimeInterpolator接口是属性动画中新增的,用于兼容Interpolator接口,这使得所有过去的Interpolator实现类都可以直接在属性动画使用。
下面自定义一个先减速后加速的插值器:
public class DecelerateAccelerateInterpolator implements TimeInterpolator{
public DecelerateAccelerateInterpolator() {
}
@Override
public float getInterpolation(float input) {
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
// 使用正弦函数来实现先减速后加速的功能,逻辑如下:
// 因为正弦函数初始弧度变化值非常大,刚好和余弦函数是相反的
// 随着弧度的增加,正弦函数的变化值也会逐渐变小,这样也就实现了减速的效果。
// 当弧度大于π/2之后,整个过程相反了过来,现在正弦函数的弧度变化值非常小,渐渐随着弧度继续增加,变化值越来越大,弧度到π时结束,这样从0过度到π,也就实现了先减速后加速的效果
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
估值器是用来根据当前属性改变的百分比来计算改变后的属性值。
系统默认的有IntEvaluator,FloatEvaluator,ArgbEvaluator。
属性动画中的ValueAnimator.ofInt() & ValueAnimator.ofFloat()& ValueAnimator.ofArgb()都具备系统内置的估值器,即FloatEvaluator & IntEvaluator & ArgbEvaluator.
但是ValueAnimator.ofObject()没有默认实现估值器,需要我们自己自定义估值器。
自定义估值器需要实现 TypeEvaluator接口 & 复写evaluate()方法。
先来分析一下FloatEvaluator源码:
public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* @param fraction 表示动画完成度(根据它来计算当前动画的值)
* @param startValue 动画的初始值
* @param endValue 动画的结束值
* @return A linear interpolation between the start and end values, given the
* fraction
parameter.
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
// 初始值 过渡 到结束值 的算法是:
// 1. 用结束值减去初始值,算出它们之间的差值
// 2. 用上述差值乘以fraction系数
// 3. 再加上初始值,就得到当前动画的值
}
}
我们以一个圆从一个点移动到另外一个点为例自定义一个TypeEvaluator:
public class Point {
private float x;//x轴坐标值
private float y;//y轴坐标值
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
}
public class PointEvaluator implements TypeEvaluator<Point> {
public PointEvaluator() {
}
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
// 根据fraction来计算当前动画的x和y的值
float x = startValue.getX() + fraction * (endValue.getX() - startValue.getX());
float y = startValue.getY() + fraction * (endValue.getY() - startValue.getY());
// 将计算后的坐标封装到一个新的Point对象中并返回
Point point = new Point(x, y);
return point;
}
}
public class TypeEvaluatorView extends View {
public static final float RADIUS = 70f;// 圆的半径 = 70
private Point currentPoint;// 当前点坐标
private Paint mPaint;// 绘图画笔
public TypeEvaluatorView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
// 如果当前点坐标为空(即第一次)
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
// 创建一个点对象(坐标是(70,70))
// 在该点画一个圆:圆心 = (70,70),半径 = 70
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
// (重点关注)将属性动画作用到View中
// 步骤1:创建初始动画时的对象点 & 结束动画时的对象点
Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70)
Point endPoint = new Point(700, 1000);// 结束点为(700,1000)
// 步骤2:创建动画对象 & 设置初始值 和 结束值
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
// 步骤3:设置动画参数
anim.setDuration(5000);
// 设置动画时长
// 步骤3:通过 值 的更新监听器,将改变的对象手动赋值给当前对象
// 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象
// 设置 值的更新监听器
// 即每当坐标值(Point对象)更新一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
// 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint)
// 从而更新当前坐标值(currentPoint)
// 步骤4:每次赋值后就重新绘制,从而实现动画效果
invalidate();
// 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
// 所以坐标值每改变一次,就会调用onDraw()一次
}
});
anim.start();
// 启动动画
} else {
// 如果坐标值不为0,则画圆
// 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果
// 在该点画一个圆:圆心 = (30,30),半径 = 30
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
}
}
参考文章:https://www.jianshu.com/p/2f19fe1e3ca1
Github示例代码