自定义View学习笔记

自定义View(四)-动画- Interpolator与Eval

2018-01-02  本文已影响60人  g小志

介绍

Interpolator插值器之前我们已经接触过了,而Evaluator好像我们还没有将,这是属性动画中俩个比较中的两个知识点,弄清楚它们有助于我们更好的使用与理解属性动画。


Interpolator插值器

之前我们已经明白了它的作用了,他就是一个控制动画如何运动的一个工具。比如有匀速效果插值器,回弹效果的插值器等等。现在我们就来从源码的角度分析下看看他是如何实现的。我们以LinearInterpolator来分析插值器(在api22以后继承BaseInterpolator,但是逻辑是一样的)

public class LinearInterpolator extends Interpolator   {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

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

LinearInterpolator实现了Interpolator接口;而Interpolator接口则直接继承自TimeInterpolator,而且并没有添加任何其它的方法。
那我们来看看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);  
    }  

这里是TimeInterpolator的代码,它里面只有一个函数float getInterpolation(float input);我们来讲讲这个函数是干什么的。
参数input: input参数是一个float类型,它取值范围是0到1,表示当前动画的进度,取0时表示动画刚开始,取1时表示动画结束,取0.5时表示动画中间的位置,其它类推。
返回值: 表示当前实际想要显示的进度。取值可以超过1也可以小于0,超过1表示已经超过目标值,小于0表示小于开始位置。正是因为每种插值器返回值不同才形成了不同运动效果的动画
对于input参数,它表示的是当前动画的进度,匀速增加的。什么叫动画的进度,动画的进度就是动画在时间上的进度,与我们的任何设置无关,随着时间的增长,动画的进度自然的增加,从0到1;input参数相当于时间的概念,我们通过setDuration()指定了动画的时长,在这个时间范围内,动画进度肯定是一点点增加的;就相当于我们播放一首歌,这首歌的进度是从0到1是一样的。
而返回值则表示动画的数值进度,它的对应的数值范围是我们通过ofInt(),ofFloat()来指定的,这个返回值就表示当前时间所对应的数值的进度。
我们而我们上篇中在AnimatorUpdateListener()监听的值是如何等到的呢?这里我们得到的都是关于浮点型的小数,但是我们在打印的时候得到的却是具体变化的数值,那么又是如何得到具体的数值的呢?
其实我们在AnimatorUpdateListener()监听中得到的数值值通过一个公式:

当前的值= 开始的值 + 当前的进度 * (结束的进度 - 开始的进度)

例如我们设置ValueAnimator.ofInt(100,400);动画时间为2s。当使用LinearInterpolator加速器(或是默认情况下)时间在1s时,显示的进度为0.5(这里的显示进度就是<font color=#006400>public float getInterpolation(float input)</font>方法返回的参数)。那么计算的公式就为:

当前的值 = 100 + (400 - 100)* 0.5  

公式相当于我们做一个应用题:
小明从100的位置开始出发向400的位置开始跑去,在走到全程距离20%位置时,请问小明在哪个数字点上?

    public class MyInterploator implements TimeInterpolator {  
        @Override  
        public float getInterpolation(float input) {  
            return 1-input;  
        }  
    }  

自定义插值器注意2点:1.继承TimeInterpolator 2.返回当前的显示进度。这里我们将进度反转过来,当传0的时候,我们让它数值进度在完成的位置,当完成的时候,我们让它在开始的位置。跟家复杂的可以参考自带的其他插值器。
效果:


Evaluator估值器

TypeEvaluator估值器,他的作用是根据当期属性的百分比来计算改变后的属性值。Evaluator其实就是一个转换器,他能把小数进度转换成对应的数值位置。
先来看张图(此图来自Android自定义控件三部曲文章 )

image
这幅图讲述了从定义动画的数字区间到通过AnimatorUpdateListener中得到当前动画所对应数值的整个过程。下面我们对这四个步骤具体讲解一下:
(1)、ofInt(0,400)表示指定动画的数字区间,是从0运动到400;
(2)、加速器:上面我们讲了,在动画开始后,通过加速器会返回当前动画进度所对应的数字进度,但这个数字进度是百分制的,以小数表示,如0.2
(3)、Evaluator:我们知道我们通过监听器拿到的是当前动画所对应的具体数值,而不是百分制的进度。那么就必须有一个地方会根据当前的数字进度,将其转化为对应的数值,这个地方就是Evaluator;Evaluator就是将从加速器返回的数字进度转成对应的数字值。所以上部分中,我们讲到的公式:
    当前的值 = 100 + (400 - 100)* 显示进度  

(4)、监听器:我们通过在AnimatorUpdateListener监听器使用animation.getAnimatedValue()函数拿到Evaluator中返回的数字值。

在计算后就是得到ValueAnimator.addAnimatorUpdateListener()中变化的值。Android中已经为我们提供了IntEvaluator(针对Int类型估值器),floatEvaluator(针对floatt类型估值器)和ArgbEvaluator(针对Color估值器)先不管ArgbEvaluator,IntEvaluator与floatEvaluator正好对应ValueAnimator.ofInt()与ValueAnimator.offloat(),如果你用.ofInt()那么在计算时就用IntEvaluator计算。.offloat()就用floatEvaluator,之前我们没有设置适因为ofInt和ofFloat都是系统直接提供的函数,所以在使用时都会有默认的加速器和Evaluator来使用的,不指定则使用默认的。

以IntEvaluator我们分析下它的源码,看看他是如何实现的:

·
    /** 
     * This evaluator can be used to perform type interpolation between <code>int</code> values. 
     */  
    public class IntEvaluator implements TypeEvaluator<Integer> {  
      
        /** 
         * This function returns the result of linearly interpolating the start and end values, with 
         * <code>fraction</code> representing the proportion between the start and end values. The 
         * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>, 
         * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>, 
         * and <code>t</code> is <code>fraction</code>. 
         * 
         * @param fraction   The fraction from the starting to the ending values 
         * @param startValue The start value; should be of type <code>int</code> or 
         *                   <code>Integer</code> 
         * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code> 
         * @return A linear interpolation between the start and end values, given the 
         *         <code>fraction</code> parameter. 
         */  
        public Integer evaluate(float fraction, Integer startValue, Integer endValue) {  
            int startInt = startValue;  
            return (int)(startInt + fraction * (endValue - startInt));  
        }  
    }  

首先IntEvaluator继承了我们的TypeEvaluator估值器。因为是IntEvaluator所以指定的泛型类型为Integer,同时里面实现了一个方法,这个方法的参数的含义

当前的值= 开始的值 + 当前的进度 * (结束的进度 - 开始的进度)

我们用图片中的例子如果加载器为匀速加速器,动画时长为2s,在1s时(也就是动画运动到一半的时候),这三个参数依次为0.5,100,400,此时在监听中得到的结果为:

    当前的值 = 100 + (400 - 100)* 0.5  

结果为250。

  1. 简单实现MyEvalutor
    2gif.gif 3gif.gif
·
       public class MyEvaluator implements TypeEvaluator<Integer> {  
        @Override  
        public Integer evaluate(float fraction, Integer startValue, Integer endValue) {  
            int startInt = startValue;  
            return (int)(200+startInt + fraction * (endValue - startInt));  
        }  
    }  

这个插值器在原来的运动的点的值基础上加200。效果对比如下:

  1. 实现倒序输出实例
    1gif.gif
·
    public class ReverseEvaluator implements TypeEvaluator<Integer> {  
    @Override  
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {  
        int startInt = startValue;  
        return (int) (endValue - fraction * (endValue - startInt));  
    }  
}

其中 fraction * (endValue - startInt)表示动画实际运动的距离,我们用endValue减去实际运动的距离就表示随着运动距离的增加,离终点越来越远,这也就实现了从终点出发,最终运动到起点的效果了。
总结: 在加速器中,我们可以通过自定义加速器的返回的数值进度来改变返回数值的位置。比如上面我们实现的倒序动画
在Evaluator中,我们又可以通过改变进度值所对应的具体数字来改变数值的位置。 所以<font color=#D02090 size=3>可以通过重写加速器改变数值进度来改变数值位置,也可以通过改变Evaluator中进度所对应的数值来改变数值位置</font>。虽然2者都可以改变位置。但是插值器(加速器)是根据运动是时间来决定当前进度的百分比。估值器是根据加速器的百分比来计算具体的数值。作用相同,职责不同。
我个人理解加速器侧重运动的状态(弹动,忽快忽慢等)估值器就是计算的作用(可以改变运动开始结束的位置)。


ArgbEvalutor颜色估值器

从一开始我们就说过属性动画可以改变动画的颜色。其实他就是通过ArgbEvalutor来做到的。为什么单独把ArgbEvalutor拿出来呢?因为与前两者的使用稍微有些不同。使用如下:

`
                valueAnimator = ValueAnimator.ofInt(0xffff0000, 0xff0000ff);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int curValue = (int) animation.getAnimatedValue();
                        mTextView.setBackgroundColor(curValue);
                    }
                });
                 valueAnimator.setDuration(2000);
                 valueAnimator.setEvaluator(new ArgbEvaluator());
                 valueAnimator.start();

这里我们使用8位16进制数值表示A,R,G,B的颜色值。使用ofInt()来创建对象。颜色变化为由红色(RED)变成蓝色(BULE)效果图如下:

4gif.gif
插值器基本原理,与自定义插值器差不多也就这样了。 下面我们继续往下看。

ValueAnimator.ofObject

之前在讲到ValueAnimator我们提到了创建对象有.0fInt(),offloat()。但是他们只能传入int与float类型的值。其实ValueAnimator还有一个方法就是ofObject()他可以传入任意类型或是对象。他有什么用呢?我们测试一下,看小面这个例子。

GIF.gif
在这里我们动态的将字母A-Z。实现了动态变换的效果。那么他是如何实现的呢?那我就先来了解下ofObject().
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values);  

第一个参数是我们上面讲到的估值器。第二个参数是我们要变换的值,和.0fInt(),offloat()一样传入的值越多,动画效果变换越复杂。这里第二个参数好理解。但是为什么要传入估值器呢?其实这里的估值器不是系统为我们预设的之前将的三种估值器的任意一种,而是自定义估值器。因为估值器的作用就是根据加速器返回的当前的进度来计算具体的值的。如果我们不自己定义,那系统是没有办法计算具体的值的,比如上面的例子计算A-Z变化某一进度具体的值,显然我们讲到的三种估值器都无法做到。所以我们需要自己定义自己的估值器。在明白了这一点使用起来就简单多了。我们来看下代码:

·  
        btn_object.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyEvaluator(), 'A', 'Z');
                valueAnimator.setDuration(3000);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        char text = (char) animation.getAnimatedValue();
                        mTextView.setText(String.valueOf(text));
                    }
                });
                valueAnimator.start();
            }
        });

    /**
     * 计算A-Z 具体的值的自定义估值器 
     */
    public class MyEvaluator implements TypeEvaluator<Character> {
        @Override
        public Character evaluate(float fraction, Character startValue, Character endValue) {
            int startInt = (int) startValue;
            int endInt = (int) endValue;
            int curInt = (int) (startInt + fraction * (endInt - startInt));
            char result = (char) curInt;
            return result;
        }
    }

java基础好的同学都应该知道 A-Z对应ASCII码为65-90 a-z为97-122。所以我们在自定义估值器的时候就可以像上面那么些。核心公式也是我们上面提到的,就不讲解了。这段代码也不难。这样我们就可以实现文字变换的动画。其实ofObject方法非常强大,不仅可以针对控件的某一属性(如文字)。甚至可以作用与对象本身。下面我们在举一个例子:

1GIF.gif
具体实现如下:

·
//Activity调用
btn_point.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                point.doPointAnim();
            }
        });
        
//自定义View
public class MyPointView extends View {
    private Point mCurPoint;
    public MyPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mCurPoint!=null){
            Paint mPaint=new Paint();//画笔 用来画圆
            mPaint.setColor(Color.RED);//画笔颜色
            mPaint.setStyle(Paint.Style.FILL);//画笔的格式 这里用FILL画出的圆就会填满
            canvas.drawCircle(400f,600f,mCurPoint.getRadius(),mPaint);//400f,600f 表示基于屏幕圆心的X,Y轴坐标 mCurPoint.getRadius()表示圆的半径
        }

    }
    public void doPointAnim(){
        ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),new Point(20),new Point(200));
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurPoint = (Point)animation.getAnimatedValue();
                invalidate();//强制刷新 从而执行onDraw()方法
            }
        });
        animator.setDuration(1000);
        animator.setInterpolator(new BounceInterpolator());//插值器 弹起效果
        animator.start();
    }  
    
//圆的实体类
public class Point {
    private int radius;//圆的半径

    public Point(int radius){
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }

    public void setRadius(int radius) {
        this.radius = radius;
    }
}

这样我们就实现了传入具体的对象。根据对象的变化做动画。虽然我们没有讲到自定义View用到的也不难,里面也没有什么难度就不细讲了。


结语

这里属性动画中的ValueAnimatot基本也就差不多了。其实学起来回头看看也不是很难,在自定义View中重要是思路,平时还是已改多看看自定义View控件的文章,多学习多积累多练习才能熟练掌握,大家和我一起加油吧!

感谢

站在巨人的肩膀上可以让我们看的更远。
Android自定义控件三部曲文章

上一篇下一篇

猜你喜欢

热点阅读