Android属性动画详解(二),插值器和估值器

在上篇博客中主要和大家讨论了属性动画的用法,相信通过上篇博客大家对属性动画的用法已经不陌生了,那么今天就来大家一起讨论下属性动画的执行流程。

谈到属性动画的执行流程,其实离不开插值器(Interpolator)和估值器(TypeEvaluator)的协同工作,本篇博客就来探究这两者是如何协同工作的。通过本篇博客你讲学到以下知识点: ①理解Interpolator和TypeEvaluator各自的作用 ②理解Interpolator和TypeEvaluator是如何协同的工作的 ③如何自定义TypeEvaluator和Interpolator 好的废话不多说进入正题,之前也谈到过学习这些基础的内容要参考官方文档,那么本篇也是一样,官方文档首先举了一个如下的例子: 如下图所示,描绘了一个假设的对象,它以x属性为动画,表示其在屏幕上的水平位置。 动画的持续时间设置为40 ms,行驶距离为40像素。 每10 ms,这是默认的帧刷新率,对象水平移动10像素。 在40ms结束时,动画停止,物体在水平位置40处结束。这是具有线性插值的动画的示例,意味着物体以恒定的速度移动。

什么意思呢?简单来说就是上图采用了一个匀速动画,在40ms内,我们假设的对象的位置从0匀速变为40。 我们可以分析一下中间的某一个时刻,比如(x=20,t=20ms)当t=20ms的时候,时间流逝的百分比是0.5(20/40=0.5),但是这个百分比不是我们所想要的,我们关心的是x的变化,那么怎么知道x变化了多少呢?这时候就要用到插值器和估值器了,上述我们说到是匀速运动,我们就来看看线性插值器的源码:

/**
 * An interpolator where the rate of change is constant
 */
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

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

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
复制代码

从getInterpolator可以看到线性插值器的返回值和输入值是相同的。另外getInterpolator()方法接收一个参数input,这个参数的值会随着动画的运动而不断变化,不过它的变化是有规律的,就是根据设定的动画时长匀速增加,变化范围是0到1。也就是说当动画一开始的时候input的值是0,到动画结束的时候input的值是1,而中间的值则是随着动画运行的时长在0到1之间变化的,它表示的是时间流逝的百分比,它的作用就是根据时间流逝的百分比计算出当前属性值改变的百分比,在例中当t=20ms时,因为运行的总时长是40ms所以时间流逝的百分比为20/40=0.5。我们刚才说过插值器的作用是拿到时间流逝的百分比,那么我们怎么根据这个百分比来计算属性值改变的百分比呢?这个时候就要用到估值器了,它的作用是根据当前属性改变的百分比来计算改变后的属性值。同样我们来看看系统提供的整形估算值算法的源码:

public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
复制代码

可以看到evaluate方法接收三个参数,第一个参数fraction这个参数非常重要它表示动画的完成度,第二和第三个参数分表表示动画的初始值和结束值。因此在上述代码中用结束值减去初始值可以得出它们之间的差,然后乘以fraction这个系数,再加上初始值,那么得到的就是动画当前的值了。 到这里大家应该可以想到上面刚刚提到的input,那么input和fraction有什么关系或者区别呢?答案很简单,input的值决定了fraction。而input的值是由系统经过计算后传入到getInterpolation()方法中的,然后我们可以自己实现getInterpolator()方法中算法,根据input的值计算出一个返回值,而这个返回值就是fraction。理清楚这两个值的关系特别重要。接着我们总体来分析一下刚开始的那个实例,当t=20ms的时候,可以计算出input的值是20/40=0.5,即动画已经完成了50%了,getInterpolator()方法的返回值就是fraction,拿到fraction后,我们就要根据它计算x属性的变化了,IntEvaluator这个估值器中的evaluate的三个分别为:0.5,0,40。代入后计算即:0+0.5*(40-0)=20。可以看出与上图是吻合的当t=20ms时,x=20。这就是属性动画执行的流程,当然我们说的这种情况是最简单的一种情况。

对于插值器和估值器来说,除了系统提供的外,我们还可以自定义。实现方式也很简单,因为插值器和估值器都是一个接口,且内部都只有一个方法,我们只要实现接口就可以了,就可以做出很多绚丽的动画了。其中,自定义插值器需要实现Interpolator或者TimeInterpolator,自定义估值器需要实现TypeEvaluator。但是一般来说,插值器一般使用系统的就足够了,估值器一般自定义会比较多,另外就是如果要对其他类型(非Int丶float丶color)做动画,必须自定义类型估值算法。接下来我们就来自定义一个TypeEvaluator来说下自定义TypeEvaluator的流程,所实现的效果如下:

从上述效果可以看到小球做的是抛物线运动,那么如何能让小球做抛物线运动呢?从屋里学的角度来讲如果满足x=400t,y=400t²,及x轴做匀速运动y轴做加速运动即可实现小球做抛物线运动。从表达式看只和时间有关系,但是根据时间的变化,横向和纵向的移动速率是不同的,我们应该如何实现呢?此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要返回给对象两个值,x当前位置,y当前位置,并且需要满足x=400t,y=400t²。我们可以创建一个PointView类来保存x,y这两个值。PointView的代码如下

public class PointView {

    float x;
    float y;

    public PointView(){

    }

    public PointView(float x, float y) {
        this.x=x;
        this.y=y;
    }

}
复制代码

有了这两个值之后我们就可以自定义一个TypeEvaluator

class PointViewEvaluator implements TypeEvaluator {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        PointView pointView=new PointView();
        //fraction为时间流逝的百分比
        pointView.x=400*(fraction*2);
        pointView.y=400*(fraction*2)*(fraction*2);
        return pointView;
    }
}
复制代码

从自定义的PointViewEvaluator 中可以看到PointView的x和y是满足x=400t,y=400t²这两个表达式的,另外我们将时间写死了为2s。自定义好之后我们就可以直接拿过来用了代码如下

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView ivBall;
    private Button btnClick;

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

        ivBall=findViewById(R.id.iv_ball);
        btnClick=findViewById(R.id.btn_click);
        btnClick.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {

        ValueAnimator valueAnimator=ValueAnimator.ofObject(new PointViewEvaluator(),new PointView(0,0));
        valueAnimator.setDuration(2000);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
            
                PointView pointView= (PointView) valueAnimator.getAnimatedValue();
                ivBall.setX(pointView.x);
                ivBall.setY(pointView.y);
            }
        });
        valueAnimator.start();

    }
}
复制代码

从上述代码中我们可以发现设置了LinearInterpolator线性的插值器,同时我们给动画添加了监听,然后获取当前PointView对象(至于为什么会返回PointView对象,可以去看上一篇文章讲解的很清晰Android属性动画详解(一),属性动画基本用法)获取的目的就是拿到PointView对象的x和y的值,然后将这两个值设置给小球,就会表现为抛物线运动。以上就是自定义TypeEvaluator的相关流程,理清楚关系逻辑其实很简单,接下来咱们一起看看自定义Interpolator虽然用的比较少,但是如果用到也要知道怎么去定义它。我们定义一个动画以最快的速度启动,然后减速运动至一半,最后加速运动至结束效果图如下

对插值器的定义其实就是一个数学表达式 y=0.5 × ((2t − 1)3+1)与其对应的图形如下
从图形中可以看到它是先加速然后加速再加速的一个过程,与其对应的代码如下:

public class PointViewInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float t) {

        float x=2.0f*t-1;
        return 0.5f*(x*x*x + 1.0f);
    }
}
复制代码

不要忘记添加这句话:

valueAnimator.setInterpolator(new PointViewInterpolator());
复制代码

然后再运行就会有上面的效果。 好了以上就是对Interpolator和TypeEvaluator的相关讨论。如有谬误欢迎批评指正。 通过本篇博客相信大家对于Interpolator和TypeEvaluator是如何协同工作的,以及对于这两者是如何自定义的相信大家都应该有个清晰的了解了。

转载于:https://juejin.im/post/5b2b96096fb9a00e90018510

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值