前言
现在如果要实现复杂,绚丽的动画效果,离不开两个接口的实现--插值器和估值器
插值器决定动画的变化模式(线性运动,非线性运动)
估值器决定变化中某一时刻的具体数值
本文主要介绍插值器的相关使用
定义
一个接口
根据初始值和结束值以及动画完成度计算当前变化的数值。
应用场景
协助插值器完成动画效果,计算属性变化过程中的具体数值。
默认插值器
ValueAnimator和ObjectAnimator均有ofInt,ofFloat,ofArgb三个方法用来实现动画效果,这三个方法对应着三个默认的估值器
IntEvaluator,FloatEvaluator以及ArgbEvaluator,源码如下:
IntEvaluator:以整形的形式从初始值到结束值的平稳过渡。
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
FloatEvaluator:以Float的形式从初始值到结束值的平稳过渡。
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);
}
}
ArgbEvaluator:以Argb类型的形式从初始值结束值 进行过渡
public class ArgbEvaluator implements TypeEvaluator {
private static final ArgbEvaluator sInstance = new ArgbEvaluator();
public static ArgbEvaluator getInstance() {
return sInstance;
}
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;
// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);
endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);
// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
}
ofObject由于数据类型不定,系统无法了解其数值的变化,需要自己实现TypeEvaluator接口实现某时刻具体数值的计算。代码如下:
public class MyTypeEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
//fraction 当前动画的完成度
//startValue 初始值
//endValue 结束值
//return 返回计算的数值
// 那么插值器的input值 和 估值器fraction有什么关系呢?
// 答:input的值决定了fraction的值:input值经过计算后传入到插值器的getInterpolation(),然后通过实现getInterpolation()中的逻辑算法,根据input值来计算出一个返回值,而这个返回值就是fraction了
//简单的说,插值器决定动画进度,估值器决定具体数值
return null;
}
}
这就是说ofInt,ofFloat,ofArgb的估值器已经有系统默认实现好了,当然你也可以通过 ValueAnimator.setEvaluator(TypeEvaluator evaluator)去设置你自定义的估值器。
那么现在我们就简单实现一个自定义的估值器实现圆形控件的移动。
实际应用
实现圆形控件的移动需要注意一下几点:
(1)原点如何表示--创建坐标类Point来标记原点坐标
(2)圆形控件的实现--自定义控件CircleView
(3)圆形控件如何随着动画的进度实现移动--通过ObjectAnimator的ofObject以及自定义估值器MyTypeEvaluator实现动画,圆形控件的移动通过setPoint方法实现。
CircleView.java:
package com.zhqy.evaluatordemo;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by jackal on 2019/8/22.
*/
public class CircleView extends View {
private static final float RADIUS=70f;
Paint paint;
Point currentPoint;
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(getResources().getColor(R.color.red));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果当前没有坐标,则绘制控件
if (currentPoint==null){
canvas.drawCircle(RADIUS,RADIUS,RADIUS,paint);
//起始坐标
Point startPoint=new Point(70,70);
//结束坐标
Point endPoint=new Point(700,700);
//属性动画
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(this, "CurrentPoint", new MyTypeEvaluator(), startPoint, endPoint);
//设置开始时延
objectAnimator.setStartDelay(1000);
//设置动画时长
objectAnimator.setDuration(4000);
//开始动画
objectAnimator.start();
}else{
//重新在新的坐标上绘制控件
canvas.drawCircle(currentPoint.getX(),currentPoint.getY(),RADIUS,paint);
}
}
public Point getCurrentPoint() {
return currentPoint;
}
public void setCurrentPoint(Point currentPoint) {
this.currentPoint = currentPoint;
// 引起控件重新绘制,如果不引起绘制,则动画没有效果
invalidate();
}
}
MyTypeEvaluator.java:
package com.zhqy.evaluatordemo;
import android.animation.TypeEvaluator;
/**
* Created by jackal on 2019/8/22.
*/
public class MyTypeEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
float deltaX=endValue.getX()-startValue.getX();
float deltaY=endValue.getY()-startValue.getY();
//计算动画执行过程中坐标的位置
Point point=new Point(deltaX*fraction+startValue.getX(),deltaY*fraction+startValue.getY());
return point;
}
}
activiy_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zhqy.evaluatordemo.MainActivity">
<com.zhqy.evaluatordemo.CircleView
android:id="@+id/cv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
</android.support.constraint.ConstraintLayout>
MainActivity.java:
package com.zhqy.evaluatordemo;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
CircleView cv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cv=findViewById(R.id.cv);
}
}
测试结果:
测试结果.gif
ObjectAnimator会默认调用属性的setXXX方法,而在setCurrentPoint方法中调用了invalidate方法引起了控件的重绘,而在动画的执行过程中会不断地将TypeEvaluator计算的数值设置给CurrentPoint并引起控件的绘制,从而实现了动画效果,而TypeEvaluator在动画执行的整个过程的作用是计算动画执行过程中的某一个时间点上对象属性的确定值。
这里需要注意的是:在自定义View中setXXX方法中要调用invalidate方法来重绘整个view布局,否则执行动画没有效果。
网友评论