随笔博文

Android:插值器(Interpolator)和估值器(TypeEvaluator)的理解和使用

2023-04-11 17:18:05 michael007js 1140

插值器和估值器的理解和使用

在这里插入图片描述

1、前言

Android中 补间动画 & 属性动画实现动画的原理是: 在这里插入图片描述 步骤2中的 插值器(Interpolator)和估值器(TypeEvaluator)是实现 复杂动画效果的关键

2、插值器简介

2.1、定义

什么是Interpolator?

  • 一个接口

  • 通俗易懂的说,Interpolator负责控制动画变化的速率,即确定了 动画效果变化的模式,使得基本的动画效果能够以匀速、加速、减速、抛物线速率等各种速率变化。

动画是开发者给定开始和结束的“关键帧”,其变化的“中间帧”是有系统计算决定然后播放出来。因此,动画的每一帧都将在开始和结束之间的特定时间显示。此时动画时间被转换为时间索引,则动画时间轴上的每个点都可以转换成0.0到1.0之间的一个浮点数。然后再将该值用于计算该对象的属性变换。在变换的情况下,y轴上,0.0对应于起始位置,1.0对应于结束位置,0.5对应于起始和结束之间的中间,对于一些插值器其值还可以是0~1之外的数值。

比如:对于LinearInterpolator线性插值器的平移动画来讲,在0.3这个时间点视图则刚好移动了整个动画的30%。

Interpolator 本质上是一个数学函数,其取数字在0.0和1.0之间,并将其转换为另一个数字。

2.2、分类

在这里插入图片描述

系统默认的插值器是AccelerateDecelerateInterpolator,即先加速后减速

系统内置9种插值器的效果图: 在这里插入图片描述

2.2.1、Linear Interpolator 线性插值

公式: y=t

构造函数: public LinearInterpolator()

参数: 无 在这里插入图片描述在这里插入图片描述

2.2.2、Accelerate Interpolator / 加速度插值器

公式: y=t^(2f)

构造函数: public AccelerateInterpolator(float factor)

参数:

名称: f

XML属性: android:factor

描述: 加速度参数. f越大,起始速度越慢,但是速度越来越快

在这里插入图片描述 在这里插入图片描述

2.2.3、Decelerate Interpolator / 减速插值器

公式: y=1-(1-t)^(2f)

构造函数: public DecelerateInterpolator(float factor)

参数:

名称: f

XML属性: android:factor

描述: 加速度参数.f越大,起始速度越快,但是速度越来越慢

在这里插入图片描述 在这里插入图片描述

2.2.4 、Accelerate Decelerate Interpolator / 先加速后减速插值器

公式: y=cos((t+1)π)/2+0.5

构造函数: public AccelerateDecelerateInterpolator()

参数: 无

在这里插入图片描述 在这里插入图片描述

2.2.5、Anticipate Interpolator

公式: y=(T+1)×t3–T×t2

构造函数: public AnticipateInterpolator(float tension)

参数:

名称: T

XML属性: android:tension

描述: 张力值, 默认为2,T越大,初始的偏移越大,而且速度越快

在这里插入图片描述 在这里插入图片描述

2.2.6、Overshoot Interpolator

公式: y=(T+1)x(t1)3+T×(t1)2 +1

构造函数: public OvershootInterpolator (float tension)

参数:

名称: T

XML属性: android:tension

描述: 张力值,默认为2,T越大,结束时的偏移越大,而且速度越快

该插值器的y值就是先会超过1,然后又回到1。 在这里插入图片描述 在这里插入图片描述

2.2.1、Anticipate Overshoot Interpolator

公式: y={0.5((T+1)×(2t)3–T×(2t)2)0.5((T+1)×(2t−2)3+T×(2t−2)2)+1for t<0.5for t≥0.5

构造函数:

public AnticipateOvershootInterpolator(float tension)

public AnticipateOvershootInterpolator(float tension, float extraTension)

参数:

XML属性: android:tension

描述: 张力值,默认为2,张力越大,起始和结束时的偏移越大,而且速度越快

XML属性: android:extraTension

描述: 额外张力值,默认为1.5。

公式中T的值为tension*extraTension

在这里插入图片描述 在这里插入图片描述

2.2.8、Bounce Interpolator / 弹跳插值器

在这里插入图片描述 在这里插入图片描述

2.2.9、Cycle Interpolator / 周期插值器

公式: y=sin(2π×C×t)

构造函数: public CycleInterpolator(float cycles)

参数:

名称: C

XML属性: android:cycles

描述: 周期值,默认为1;2表示动画会执行两次

这里的动画是放大效果,那么就会先放大->缩小 -> 放大 ->恢复原来

在这里插入图片描述 在这里插入图片描述

2.2.10、插值器使用方式

插值器在动画的使用有两种方式:在XML / Java代码中设置:

设置方法1:在 动画效果的XML代码中设置插值器属性android:interpolator

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"

 android:interpolator="@android:anim/overshoot_interpolator"
 // 通过资源ID设置插值器
 android:duration="3000"
 android:fromXScale="0.0"
 android:fromYScale="0.0"
 android:pivotX="50%"
 android:pivotY="50%"
 android:toXScale="2"
 android:toYScale="2" />
123456789101112

设置方法2:在 Java 代码中设置

Button mButton = (Button) findViewById(R.id.Button);
     // 步骤1:创建 需要设置动画的 视图View

Animation alphaAnimation = new AlphaAnimation(1,0);
     // 步骤2:创建透明度动画的对象 & 设置动画效果

     alphaAnimation.setDuration(3000);
     Interpolator overshootInterpolator = new OvershootInterpolator();
     // 步骤3:创建对应的插值器类对象

     alphaAnimation.setInterpolator(overshootInterpolator);
     // 步骤4:给动画设置插值器

     mButton.startAnimation(alphaAnimation);
     // 步骤5:播放动画


2.2.10.1、使用方式demo

xml中使用

translate.xml

在<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
     android:fromXDelta="0"
     android:fromYDelta="0"
     android:toXDelta="400"
     android:toYDelta="400"
     android:duration="2000"
     android:interpolator="@android:anim/bounce_interpolator" />

MainActivity

package com.example.buttontes;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.BounceInterpolator;
import android.widget.Button;
import android.widget.ImageView;



public class MainActivity extends AppCompatActivity {

 private static finalString TAG = "jmw";

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

     Button button = findViewById(R.id.button);
     button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             Animation animation = AnimationUtils.loadAnimation(MainActivity.this
                     ,R.anim.translate);
             //animation.setInterpolator(new BounceInterpolator());
             button.startAnimation(animation);
         }
     });

 }


}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/rl"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:gravity="center">

 <Button
     android:id="@+id/button"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="我是动画" />

</LinearLayout>


Java代码中使用

translate.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
     android:fromXDelta="0"
     android:fromYDelta="0"
     android:toXDelta="400"
     android:toYDelta="400"
     android:duration="2000" />
1234567

MainActivity

package com.example.buttontes;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.BounceInterpolator;
import android.widget.Button;
import android.widget.ImageView;



public class MainActivity extends AppCompatActivity {

 private static finalString TAG = "jmw";

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

     Button button = findViewById(R.id.button);
     button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             Animation animation = AnimationUtils.loadAnimation(MainActivity.this
                     ,R.anim.scale);
             animation.setInterpolator(new BounceInterpolator());
             button.startAnimation(animation);
         }
     });

 }
}

2.2.11、自定义插值器

  • 本质:根据动画的进度(0%-100%)计算出当前属性值改变的百分比

  • 具体使用:自定义插值器需要实现 Interpolator / TimeInterpolator接口 &复写getInterpolation()

补间动画 实现 Interpolator接口;属性动画实现TimeInterpolator接口 TimeInterpolator接口是属性动画中新增的,用于兼容Interpolator接口,这使得所有过去的Interpolator实现类都可以直接在属性动画使用

// Interpolator接口
public interface Interpolator {  

 // 内部只有一个方法
    float getInterpolation(float input) {  
        // 参数说明
        // input值值变化范围是0-1,且随着动画进度(0% - 100% )均匀变化
     // 即动画开始时,input值 = 0;动画结束时input = 1
     // 而中间的值则是随着动画的进度(0% - 100%)在0到1之间均匀增加
       
   ...// 插值器的计算逻辑

   return xxx;
   // 返回的值就是用于估值器继续计算的fraction值,下面会详细说明
 }  

// TimeInterpolator接口
// 同上
public interface TimeInterpolator {  
 
 float getInterpolation(float input);  
 
}  

在学习自定义插值器前,我们先来看两个已经实现好的系统内置差值器:

  • 匀速插值器:LinearInterpolator

  • 先加速再减速 插值器:AccelerateDecelerateInterpolator

// 匀速差值器:LinearInterpolator
@HasNativeInterpolator  
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {  
  // 仅贴出关键代码
...
 public float getInterpolation(float input) {  
     return input;  
     // 没有对input值进行任何逻辑处理,直接返回
     // 即input值 = fraction值
     // 因为input值是匀速增加的,因此fraction值也是匀速增加的,所以动画的运动情况也是匀速的,所以是匀速插值器
 }  


// 先加速再减速 差值器:AccelerateDecelerateInterpolator
@HasNativeInterpolator  
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {  
   // 仅贴出关键代码
...
 public float getInterpolation(float input) {  
     return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
     // input的运算逻辑如下:
     // 使用了余弦函数,因input的取值范围是0到1,那么cos函数中的取值范围就是π到2π。
     // 而cos(π)的结果是-1,cos(2π)的结果是1
     // 所以该值除以2加上0.5后,getInterpolation()方法最终返回的结果值还是在0到1之间。只不过经过了余弦运算之后,最终的结果不再是匀速增加的了,而是经历了一个先加速后减速的过程
     // 所以最终,fraction值 = 运算后的值 = 先加速后减速
     // 所以该差值器是先加速再减速的
 }  
 

 }

从上面看出,自定义插值器的关键在于:对input值 根据动画的进度(0%-100%)通过逻辑计算 计算出当前属性值改变的百分比

下面我将用一个实例来说明该如何自定义插值器

步骤1:根据需求实现Interpolator接口 DecelerateAccelerateInterpolator.java

package com.example.buttontes;

import android.animation.TimeInterpolator;

public class DecelerateAccelerateInterpolator implements TimeInterpolator {

 @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;
     // 返回的result值 = 随着动画进度呈先减速后加速的变化趋势
 }
}

MainActivity

package com.example.buttontes;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;




public class MainActivity extends AppCompatActivity {

 private static finalString TAG = "jmw";

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

     Button button = findViewById(R.id.button);
     float curTranslationX = button.getTranslationX();
     // 获得当前按钮的位置
     button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             ObjectAnimator animator = ObjectAnimator.ofFloat(button, "translationX"
                     , curTranslationX, 300,curTranslationX);
             // 创建动画对象 & 设置动画
             // 表示的是:
             // 动画作用对象是mButton
             // 动画作用的对象的属性是X轴平移
             // 动画效果是:从当前位置平移到 x=1500 再平移到初始位置
             animator.setDuration(5000);
             animator.setInterpolator(new DecelerateAccelerateInterpolator());
             // 设置插值器
             animator.start();
             // 启动动画
         }
     });

 }
}

在这里插入图片描述

3、估值器(TypeEvaluator)

3.1、为什么要有估值器

插值器其实是变化快慢的一个衡量标准,根据时间流逝的比例,来得出属性值变化的比例,这是一个接口,只有一个方法,input就是时间的流逝,返回的就是属性值变化的程度其直接继承接口是Interpolator,这仍然是一个接口,Interpolator就有很多实现类了:匀速、加速、减速等等插值器。

public class LinearInterpolator implements Interpolator {
 public LinearInterpolator() {
 }
 public LinearInterpolator(Context context, AttributeSet attrs) {
 }
 public float getInterpolation(float input) {
     return input;
 }
}


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


//DecelerateInterpolator
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;
 }

从上面可以抽象出三个函数:

LinearInterpolator:result = input
AccelerateInterpolator:result = input^2
DecelerateInterpolator: result = 1-(1-input)^2

那么上面的result是如何反应变化速度的呢?就是对input求导,也就是对时间求导,求导之后:

LinearInterpolator:result导数 = 1
AccelerateInterpolator:result导数 = 2 * input
DecelerateInterpolator: result导数 = 2*(1-input)

可以看出当input由0变成1时,LinearInterpolator导数不变,也就是匀速;AccelerateInterpolator导数变大,也就是速度越来越快;DecelerateInterpolator导数越来越小,也就是速度越来越慢。

有了插值器得到的属性值的变化程度之后,就要算出具体的属性值`了,比如从start变化到最终的end,某一时刻,插值器得到变化程度为result,那么`属性值=start+(end-start)*result

让我们看一下源码中估值器都是如何计算属性值的:

public class IntEvaluator implements TypeEvaluator<Integer> {
 public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
     int startInt = startValue;
     return (int)(startInt + fraction * (endValue - startInt));
 }
}
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);
 }
}

属性估值器唯一要实现的方法是evaluate方法,如上,源码中的估值器“估值”过程都是使用我们所讲的方法,那既然方法都一样,为什么不使用同一个估值器呢?

原因是有些属性并不能直接按照上面的方法进行计算,比如:将View的背景色由红色变成蓝色,这种属性就不能简单的使用上面的方法,因为这个属性并不是使用一个数值就能简单描述的,还好google工程师专门为颜色变化写了一个估值器

public class ArgbEvaluator implements TypeEvaluator {
 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))));
 }
   
}

仔细观察其实也是我们上面所说的方法,但是将argb各个数值进行计算,然后组合出来一个值。

总结

插值器就是反应属性变化的快慢,具体快慢的标准可以由插值器函数求导得到;估值器具体计算属性值,一般计算的方法就是

start+(end-start)*fraction

3.2、定义

什么是估值器:根据当前属性改变的百分比来计算改变后的属性值。

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

  • 定义:一个接口

  • 作用:设置 属性值 从初始值过渡到结束值 的变化具体数值

  • 属性动画特有的属性

  • 协助插值器 实现非线性运动的动画效果

如果你还是对估值器的概念一脸懵逼的话,不妨以例子作为切入点,我们从例子慢慢分析。

在上一章中,我们用代码初始化ObjectAnimation的时候,有利用到系统默认提供的FloatEvaluator:

val objectAnimator = 
ObjectAnimator.ofObject(viewHolder,"PropertyName",FloatEvaluator(),0f,1f)

这时候,我们定义的值的类型时Float。我们直接看看FloatEvaluator的源码,只有一个方法:

public Float evaluate(float fraction, Number startValue, Number endValue) {
 float startFloat = startValue.floatValue();
 return startFloat + fraction * (endValue.floatValue() - startFloat);
}
  • startValue 是指初始值

  • endValue 是指最终值

  • fraction是指从startValue到endValue的动画的进度,这个值是从0到1的float值。其变化的速率和插值器是有关联的。假设插值器使用的是LinearInterpolator线性插值器,那么fraction的变化也是线性的!

  • return 生成的具体值!在上个Demo中,就是直接操控View的透明度的值。

我们知道,一个动画设置value的时候是可以设置多个的,比如说设置了三个值:(0f,1f,2f)

那么估值器在这段时间内会一值被调用 evaluate() 方法,fraction始终是从0到1的变化,而startValue和endValue会以下面的方式变化:

在这里插入图片描述

需要注意的是:因为估值器返回的值会直接影响显示的效果,如果把fraction看作是x,startValue和endValue是常量,其返回值y必须满足与x*(endValue - startValue) 构成的等式为一元一次方程。

3.3、使用方式

ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "height", new Evaluator(),1,3);
// 在第4个参数中传入对应估值器类的对象
// 系统内置的估值器有3个:
// IntEvaluator:以整型的形式从初始值 - 结束值 进行过渡
// FloatEvaluator:以浮点型的形式从初始值 - 结束值 进行过渡
// ArgbEvaluator:以Argb类型的形式从初始值 - 结束值 进行过渡
123456

如果要为 Android 系统无法识别的类型添加动画效果,则可以通过实现 TypeEvaluator 接口来创建您自己的评估程序。Android 系统可以识别的类型为 int、float 或颜色,分别由 IntEvaluator、FloatEvaluator 和 ArgbEvaluator 类型评估程序提供支持。

IntEvaluator :Int类型估值器,返回int类型的属性改变 FloatEvaluator : Float类型估值器,返回Float类型属性改变 ArgbEvaluator :颜色类型估值器,返回16进制颜色值

3.4、自定义估值器

本质:根据 插值器计算出当前属性值改变的百分比 & 初始值 & 结束值 来计算 当前属性具体的数值

如:动画进行了50%(初始值=100,结束值=200 ),那么匀速插值器计算出了当前属性值改变的百分比是50%,那么估值器则负责计算当前属性值 = 100 + (200-100)x50% = 150.

  • 具体使用:自定义估值器需要实现 TypeEvaluator接口 & 复写evaluate()

public interface TypeEvaluator {  

 public Object evaluate(float fraction, Object startValue, Object endValue) {  
// 参数说明
// fraction:插值器getInterpolation()的返回值
// startValue:动画的初始值
// endValue:动画的结束值

     ....// 估值器的计算逻辑

     return xxx;
     // 赋给动画属性的具体数值
     // 使用反射机制改变属性变化

 }  
}  

那么插值器的input值 和 估值器fraction有什么关系呢?

  • 答:input的值决定了fraction的值:input值经过计算后传入到插值器的getInterpolation(),然后通过实现getInterpolation()中的逻辑算法,根据input值来计算出一个返回值,而这个返回值就是fraction了

这里随便整段代码,大家感受一下就好了。如果需求上需要自定义估值器,方法实现需要自己根据业务去调整。

// 实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator<Point> {
 // 复写evaluate()
 // 在evaluate()里写入对象动画过渡的逻辑
 @Override
 public Point evaluate(float fraction, Point startValue, Point endValue) {
     // 根据fraction来计算当前动画的x和y的值
     int x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
     int y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
     // 将计算后的坐标封装到一个新的Point对象中并返回
     return new Point(x, y);
 }
}

自定义估值器实现具体过程参考

3.5、插值器和估值器关系

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

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

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

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


首页
关于博主
我的博客
搜索