美文网首页
属性动画完全解析 - 2

属性动画完全解析 - 2

作者: 彩虹_直至黑白 | 来源:发表于2022-02-16 09:29 被阅读0次

插值器Interpolator

插值器(Interpolator)用于定义动画随时间流逝的变化规律。这句话说起来比较抽象,但其实在我们实际使用属性动画的时候,我们能明显感觉到插值器的作用。
例如默认采用的加减速插值器,它会在动画开始执行逐渐加速,然后又逐渐减速直至动画结束;而线性插值器则是按着一个均匀的速度执行完整个动画的。它们随着动画执行比例的变化规律如下图所示:


加减速插值器.png
线性插值器.png

图中线上某点所在的斜率即为在某时刻的速率。从图上也可以明显看到加减速插值器先逐渐加速、再逐渐减速,而线性插值器一直保持恒定的速率执行完成。在知道了插值器的作用之后,接下来我们就来看Android中有哪些内置的插值器。

系统内置插值器

Android系统中共内置了9种插值器,如下表所示:


插值器种类.png

那么我们来看下插值器的相关接口以及如何自定义一个插值器。

自定义插值器我们需要实现 Interpolator / TimeInterpolator接口并实现接口方法getInterpolation。接下来看到TimeInterpolator接口的定义:

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

可以看到这个接口只有一个接口方法getInterpolation方法需要实现,它是用于定义动画随时间流逝的变化规律的,它的参数input表示的是当前动画执行比例,如0.5表示动画现在执行了50%。返回值表示动画的完成度,在属性动画中称之为fraction
我们通过系统内置的几个插值器来看看如何使用这个方法,先看到LinearInterpolator

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }
   ......
}

可以看到,它就是简单地将输入参数input返回了。虽然简单的不可思议,但是也很容易理解,结合前面的图,LinearInterpolator是随着时间流逝匀速变化的,所以它的变化是线性的,我们只需要直接返回input即可。接下来看一个稍微复杂些的AccelerateInterpolator,也就是加速度插值器:

public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    ......

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

    ......
}
  //Math.pow(底数,几次方)
  //int c=(int)Math.pow(3,3);    
  //c=27;

在默认情况下,mFactor的值为1.0f,也就是说它的返回值是input*input,结合一定的数学知识和上面的相关图像我们很容易得知,在0~1这个区间上斜率是逐渐增加的,符合加速度插值器的特点。

估值器TypeEvaluator

估值器(TypeEvaluator)的作用是定义从初始值过渡到结束值的计算规则。当我们采用如下的方式创建属性动画时:

ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 360.0f);

ValueAnimator.ofFloat方法其实就实现了初始值到结束值过渡的规则定义,我们在使用过程中没有感受到估值器 TypeEvaluator 的存在是因为这个方法内置了FloatEvaluator来对浮点值从初始值到结束值进行了定义,在看FloatEvaluator的源码之前,我们先来查看每个估值器都要实现的接口:TypeEvaluator

TypeEvaluator的接口定义如下:

public interface TypeEvaluator<T> {

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

以看到这个接口接收一个泛型参数T,用于其唯一的方法evaluate的参数类型以及返回值类型。该方法的参数含义以及返回值的含义如下表所示:

接下来我们就来看系统内置的实现类 FloatEvaluator 的源码:

public class FloatEvaluator implements TypeEvaluator<Number> {

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

可以看到,FloatEvaluator所做的事就是简单地将结束值减去初始值乘以fraction后再加上初始值所得的结果进行返回,这跟我们平常计算比例值的方式是一样的。而IntEvaluator的实现也是差不多的,这两个类已经能够涵盖大部分动画所遇到的情况了,但是当我们遇到一些更加复杂的操作时,这两个类可能并不够用,接下来我们就来看如何自定义一个估值器。

自定义估值器

在这里我会举两个例子来介绍自定义估值器的使用,首先我们先来看到第一个:自定义字符变化的估值器,将字符按照字母表的顺序进行过渡。这个需求比较简单,代码如下所示:

public class CharEvaluator implements TypeEvaluator<Character> {
    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        return (char) (startValue + (endValue - startValue) * fraction);
    }
}

然后在Activity中,我们需要借助 ValueAnimator.ofObject方法来使用自定义的估值器:

ValueAnimator anim = ValueAnimator.ofObject(new CharEvaluator(), 'A', 'Z');
anim.setDuration(500);
anim.setInterpolator(new LinearInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.d(TAG, "current character is " + animation.getAnimatedValue());
    }
});
anim.start();

接下来我们来看一个在自定义View中应用的估值器。

  1. 首先先自定义一个Point类表示坐标:
public class Point {

    private float x;
    private float 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;
    }
}
  1. 接着我们为Point定义一个估值器PointEvaluator:
public class PointEvaluator implements TypeEvaluator<Point> {
    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        float x = startValue.getX() + (endValue.getX() - startValue.getX()) * fraction;
        float y = startValue.getY() + (endValue.getY() - startValue.getY()) * fraction;
        return new Point(x, y);
    }
}
  1. 接着我们自定义一个View,如下所示:
public class CircleView extends View {

    private static final float RADIUS = 100.0f;

    private Point point;

    private Paint mPaint;

    public CircleView(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 (point == null){
            point = new Point(RADIUS, RADIUS);
            drawCircle(canvas);
        } else {
            drawCircle(canvas);
        }
        invalidate();
    }

    private void drawCircle(Canvas canvas) {
        float x = point.getX();
        float y = point.getY();
        canvas.drawCircle(x, y, RADIUS, mPaint);
    }

    public Point getPoint(){
        return point;
    }

    public void setPoint(Point point){
        this.point = point;
    }
}

在这里需要特别说明2点:

  1. 本例子中我们需要修改的是Point的属性值,所以在自定义的类中我们必须要提供相应的setPoint和getPoint方法,因为属性动画是通过反射的原理来修改相应的属性值的。
  2. 在重写onDraw方法的时候要特别注意调用invalidate方法。

接着在Activity中,代码如下所示:

ObjectAnimator anim = ObjectAnimator.ofObject(circleView, "point",
        new PointEvaluator(),
        new Point(100.0f, 100.0f), new Point(500.0f, 500.0f));
anim.setDuration(2000);
anim.start();

插值器&估值器

插值器:
概念: 根据 时间流失的百分比 计算 当前属性改变的百分比
场景: 实现非线性运动的动画效果

估值器:
概念: 根据 当前属性改变的百分比 计算 改变后的属性值。

插值器决定属性值随时间变化的规律;而具体变化属性数值则交给估值器去计算。

二者关系:

属性动画是对属性做动画,属性要实现动画。

  1. 首先由插值器根据时间流逝的百分比计算出当前属性值改变的百分比,然后由插值器将这个百分比返回。这个时候插值器的工作就完成了。

比如 插值器 返回的值是0.5,很显然我们要的不是0.5

  1. 插值器计算好属性变化百分比后,由估值器根据当前属性改变的百分比来计算改变后的属性值,根据这个属性值,我们就可以对View设置当前的属性值了。

相关文章

  • Android属性动画完全解析

    Android属性动画完全解析(上)Android属性动画完全解析(中)Android属性动画完全解析(下)

  • Android属性动画完全解析

    Android属性动画完全解析(上),初识属性动画的基本用法Android属性动画完全解析(中),ValueAni...

  • 属性动画

    参考-Android属性动画完全解析(上) 参考-Android属性动画完全解析(中) 参考-Android属性动...

  • 属性动画完全解析 - 2

    插值器Interpolator 插值器(Interpolator)用于定义动画随时间流逝的变化规律。这句话说起来比...

  • Android属性动画

    版权声明:本文出自郭霖的博客Android属性动画完全解析(上),初识属性动画的基本用法Android属性动画完全...

  • 属性动画完全解析

    写的非常好,强烈推荐给大家 转载请注明出处:http://blog.csdn.net/guolin_blog/ar...

  • Android属性动画

    还未开始写,目前先占位,详情可参考 1.Android属性动画完全解析(上),初识属性动画的基本用法 2.Andr...

  • 初尝自定义View和属性动画:实现一个转动的进度条

    最近看到了郭霖大神写的博客,关于属性动画的使用的。Android属性动画完全解析(上),初识属性动画的基本用法 觉...

  • Android动画总结

    本文总结常用属性方法等,详细学习可使用如下郭霖大神文章: Android属性动画完全解析(上),初识属性动画的基本...

  • Android属性动画完全解析

    初识属性动画的基本用法[https://guolin.blog.csdn.net/article/details/...

网友评论

      本文标题:属性动画完全解析 - 2

      本文链接:https://www.haomeiwen.com/subject/typakrtx.html