`
ch_kexin
  • 浏览: 898047 次
  • 性别: Icon_minigender_2
  • 来自: 青岛
社区版块
存档分类
最新评论

Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

 
阅读更多

ValueAnimator的高级用法

 

在上篇文章中介绍补间动画缺点的时候有提到过,补间动画是只能对View对象进行动画操作的。而属性动画就不再受这个限制,它可以对任意对象进行动画操作。那么大家应该还记得在上篇文章当中我举的一个例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。OK,下面我们就来学习一下如何实现这样的效果。

 

在开始动手之前,我们还需要掌握另外一个知识点,就是TypeEvaluator的用法。可能在大多数情况下我们使用属性动画的时候都不会用到TypeEvaluator,但是大家还是应该了解一下它的用法,以防止当我们遇到一些解决不掉的问题时能够想起来还有这样的一种解决方案。

 

那么TypeEvaluator的作用到底是什么呢?简单来说,就是告诉动画系统如何从初始值过度到结束值。我们在上一篇文章中学到的ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,那么这个平滑过度是怎么做到的呢?其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator的代码实现:

public class FloatEvaluator implements TypeEvaluator {  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        float startFloat = ((Number) startValue).floatValue();  
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
    }  
}  

 可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。

 

好的,那FloatEvaluator是系统内置好的功能,并不需要我们自己去编写,但介绍它的实现方法是要为我们后面的功能铺路的。前面我们使用过了ValueAnimator的ofFloat()和ofInt()方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。

 

下面来先定义一个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 float getY() {  
        return y;  
    }  
  
}  

 Point类非常简单,只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标。接下来定义PointEvaluator,如下所示:

public class PointEvaluator implements TypeEvaluator{  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }  
  
}  

 可以看到,PointEvaluator同样实现了TypeEvaluator接口并重写了evaluate()方法。其实evaluate()方法中的逻辑还是非常简单的,先是将startValue和endValue强转成Point对象,然后同样根据fraction来计算当前动画的x和y的值,最后组装到一个新的Point对象当中并返回。

 

这样我们就将PointEvaluator编写完成了,接下来我们就可以非常轻松地对Point对象进行动画操作了,比如说我们有两个Point对象,现在需要将Point1通过动画平滑过度到Point2,就可以这样写:

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();  

 代码很简单,这里我们先是new出了两个Point对象,并在构造函数中分别设置了它们的坐标点。然后调用ValueAnimator的ofObject()方法来构建ValueAnimator的实例,这里需要注意的是,ofObject()方法要求多传入一个TypeEvaluator参数,这里我们只需要传入刚才定义好的PointEvaluator的实例就可以了。

 

好的,这就是自定义TypeEvaluator的全部用法,掌握了这些知识之后,我们就可以来尝试一下如何通过对Point对象进行动画操作,从而实现整个自定义View的动画效果。

 

新建一个MyAnimView继承自View,代码如下所示:

public class MyAnimView extends View {  
  
    public static final float RADIUS = 50f;  
  
    private Point currentPoint;  
  
    private Paint mPaint;  
  
    public MyAnimView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.BLUE);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        if (currentPoint == null) {  
            currentPoint = new Point(RADIUS, RADIUS);  
            drawCircle(canvas);  
            startAnimation();  
        } else {  
            drawCircle(canvas);  
        }  
    }  
  
    private void drawCircle(Canvas canvas) {  
        float x = currentPoint.getX();  
        float y = currentPoint.getY();  
        canvas.drawCircle(x, y, RADIUS, mPaint);  
    }  
  
    private void startAnimation() {  
        Point startPoint = new Point(RADIUS, RADIUS);  
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);  
        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation) {  
                currentPoint = (Point) animation.getAnimatedValue();  
                invalidate();  
            }  
        });  
        anim.setDuration(5000);  
        anim.start();  
    }  
  
}  

 基本上还是很简单的,总共也没几行代码。首先在自定义View的构造方法当中初始化了一个Paint对象作为画笔,并将画笔颜色设置为蓝色,接着在onDraw()方法当中进行绘制。这里我们绘制的逻辑是由currentPoint这个对象控制的,如果currentPoint对象不等于空,那么就调用drawCircle()方法在currentPoint的坐标位置画出一个半径为50的圆,如果currentPoint对象是空,那么就调用startAnimation()方法来启动动画。

 

那么我们来观察一下startAnimation()方法中的代码,其实大家应该很熟悉了,就是对Point对象进行了一个动画操作而已。这里我们定义了一个startPoint和一个endPoint,坐标分别是View的左上角和右下角,并将动画的时长设为5秒。然后有一点需要大家注意的,就是我们通过监听器对动画的过程进行了监听,每当Point值有改变的时候都会回调onAnimationUpdate()方法。在这个方法当中,我们对currentPoint对象进行了重新赋值,并调用了invalidate()方法,这样的话onDraw()方法就会重新调用,并且由于currentPoint对象的坐标已经改变了,那么绘制的位置也会改变,于是一个平移的动画效果也就实现了。

 

下面我们只需要在布局文件当中引入这个自定义控件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    >  
  
    <com.example.tony.myapplication.MyAnimView  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" />  
  
</RelativeLayout>  

 最后运行一下程序,效果如下图所示:

 

 

OK!这样我们就成功实现了通过对对象进行值操作来实现动画效果的功能,这就是ValueAnimator的高级用法。

 

ObjectAnimator的高级用法

 

ObjectAnimator的基本用法和工作原理在上一篇文章当中都已经讲解过了,相信大家都已经掌握。那么大家应该都还记得,我们在吐槽补间动画的时候有提到过,补间动画是只能实现移动、缩放、旋转和淡入淡出这四种动画操作的,功能限定死就是这些,基本上没有任何扩展性可言。比如我们想要实现对View的颜色进行动态改变,补间动画是没有办法做到的。

 

但是属性动画就不会受这些条条框框的限制,它的扩展性非常强,对于动态改变View的颜色这种功能是完全可是胜任的,那么下面我们就来学习一下如何实现这样的效果。

 

大家应该都还记得,ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个color属性,并提供它的get和set方法。这里我们可以将color属性设置为字符串类型,使用#RRGGBB这种格式来表示颜色值,代码如下所示:

public class MyAnimView extends View {  
  
    ...  
  
    private String color;  
  
    public String getColor() {  
        return color;  
    }  
  
    public void setColor(String color) {  
        this.color = color;  
        mPaint.setColor(Color.parseColor(color));  
        invalidate();  
    }  
  
    ...  
  
}  

 注意在setColor()方法当中,我们编写了一个非常简单的逻辑,就是将画笔的颜色设置成方法参数传入的颜色,然后调用了invalidate()方法。这段代码虽然只有三行,但是却执行了一个非常核心的功能,就是在改变了画笔颜色之后立即刷新视图,然后onDraw()方法就会调用。在onDraw()方法当中会根据当前画笔的颜色来进行绘制,这样颜色也就会动态进行改变了。

 

那么接下来的问题就是怎样让setColor()方法得到调用了,毫无疑问,当然是要借助ObjectAnimator类,但是在使用ObjectAnimator之前我们还要完成一个非常重要的工作,就是编写一个用于告知系统如何进行颜色过度的TypeEvaluator。创建ColorEvaluator并实现TypeEvaluator接口,代码如下所示:

public class ColorEvaluator implements TypeEvaluator {  
  
    private int mCurrentRed = -1;  
  
    private int mCurrentGreen = -1;  
  
    private int mCurrentBlue = -1;  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        String startColor = (String) startValue;  
        String endColor = (String) endValue;  
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);  
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);  
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);  
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);  
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);  
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);  
        // 初始化颜色的值  
        if (mCurrentRed == -1) {  
            mCurrentRed = startRed;  
        }  
        if (mCurrentGreen == -1) {  
            mCurrentGreen = startGreen;  
        }  
        if (mCurrentBlue == -1) {  
            mCurrentBlue = startBlue;  
        }  
        // 计算初始颜色和结束颜色之间的差值  
        int redDiff = Math.abs(startRed - endRed);  
        int greenDiff = Math.abs(startGreen - endGreen);  
        int blueDiff = Math.abs(startBlue - endBlue);  
        int colorDiff = redDiff + greenDiff + blueDiff;  
        if (mCurrentRed != endRed) {  
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,  
                    fraction);  
        } else if (mCurrentGreen != endGreen) {  
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,  
                    redDiff, fraction);  
        } else if (mCurrentBlue != endBlue) {  
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,  
                    redDiff + greenDiff, fraction);  
        }  
        // 将计算出的当前颜色的值组装返回  
        String currentColor = "#" + getHexString(mCurrentRed)  
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);  
        return currentColor;  
    }  
  
    /** 
     * 根据fraction值来计算当前的颜色。 
     */  
    private int getCurrentColor(int startColor, int endColor, int colorDiff,  
            int offset, float fraction) {  
        int currentColor;  
        if (startColor > endColor) {  
            currentColor = (int) (startColor - (fraction * colorDiff - offset));  
            if (currentColor < endColor) {  
                currentColor = endColor;  
            }  
        } else {  
            currentColor = (int) (startColor + (fraction * colorDiff - offset));  
            if (currentColor > endColor) {  
                currentColor = endColor;  
            }  
        }  
        return currentColor;  
    }  
      
    /** 
     * 将10进制颜色值转换成16进制。 
     */  
    private String getHexString(int value) {  
        String hexString = Integer.toHexString(value);  
        if (hexString.length() == 1) {  
            hexString = "0" + hexString;  
        }  
        return hexString;  
    }  
  
}  

 这大概是我们整个动画操作当中最复杂的一个类了。没错,属性动画的高级用法中最有技术含量的也就是如何编写出一个合适的TypeEvaluator。好在刚才我们已经编写了一个PointEvaluator,对它的基本工作原理已经有了了解,那么这里我们主要学习一下ColorEvaluator的逻辑流程吧。

 

首先在evaluate()方法当中获取到颜色的初始值和结束值,并通过字符串截取的方式将颜色分为RGB三个部分,并将RGB的值转换成十进制数字,那么每个颜色的取值范围就是0-255。接下来计算一下初始颜色值到结束颜色值之间的差值,这个差值很重要,决定着颜色变化的快慢,如果初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢,而如果颜色值相差很大,比如说从黑到白,那么就要经历255*3这个幅度的颜色过度,变化就会非常快。

 

那么控制颜色变化的速度是通过getCurrentColor()这个方法来实现的,这个方法会根据当前的fraction值来计算目前应该过度到什么颜色,并且这里会根据初始和结束的颜色差值来控制变化速度,最终将计算出的颜色进行返回。

 

最后,由于我们计算出的颜色是十进制数字,这里还需要调用一下getHexString()方法把它们转换成十六进制字符串,再将RGB颜色拼装起来之后作为最终的结果返回。

 

好了,ColorEvaluator写完之后我们就把最复杂的工作完成了,剩下的就是一些简单调用的问题了,比如说我们想要实现从蓝色到红色的动画过度,历时5秒,就可以这样写:

ObjectAnimator anim = ObjectAnimator.ofObject(myAnimView, "color", new ColorEvaluator(),   
    "#0000FF", "#FF0000");  
anim.setDuration(5000);  
anim.start();  

 用法非常简单易懂,相信不需要我再进行解释了。

 

接下来我们需要将上面一段代码移到MyAnimView类当中,让它和刚才的Point移动动画可以结合到一起播放,这就要借助我们在上篇文章当中学到的组合动画的技术了。修改MyAnimView中的代码,如下所示:

public class MyAnimView extends View {  
  
    ...  
  
    private void startAnimation() {  
        Point startPoint = new Point(RADIUS, RADIUS);  
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);  
        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation) {  
                currentPoint = (Point) animation.getAnimatedValue();  
                invalidate();  
            }  
        });  
        ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),   
                "#0000FF", "#FF0000");  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.play(anim).with(anim2);  
        animSet.setDuration(5000);  
        animSet.start();  
    }  
  
}  

 可以看到,我们并没有改动太多的代码,重点只是修改了startAnimation()方法中的部分内容。这里先是将颜色过度的代码逻辑移动到了startAnimation()方法当中,注意由于这段代码本身就是在MyAnimView当中执行的,因此ObjectAnimator.ofObject()的第一个参数直接传this就可以了。接着我们又创建了一个AnimatorSet,并把两个动画设置成同时播放,动画时长为五秒,最后启动动画。现在重新运行一下代码,效果如下图所示:

 

 

OK,位置动画和颜色动画非常融洽的结合到一起了,看上去效果还是相当不错的,这样我们就把ObjectAnimator的高级用法也掌握了。

 

好的,通过本篇文章的学习,我们对属性动画已经有了颇为深刻的认识,那么本篇文章的内容到此为止,下篇文章当中将会介绍更多关于属性动画的其它技巧,感兴趣的朋友请继续阅读

 

分享到:
评论

相关推荐

    Android属性动画的基本用法解析

    接下来,我们将深入解析Android属性动画的基本用法。 ### 1. 动画系统概述 Android的属性动画系统主要包括三个核心类:`ValueAnimator`, `ObjectAnimator` 和 `AnimatorSet`。 - **ValueAnimator**:是最基础的...

    Android 属性动画(Property Animation) 完全解析 源码程序

    **Android属性动画(Property Animation)完全解析** 属性动画是Android 3.0(API Level 11)引入的一种新动画机制,与视图动画(View Animation)不同,它不依赖于视图的绘制过程,而是直接改变对象的属性并实时...

    Android源码解析之属性动画详解

    属性动画在Android 3.0及以上版本引入,作为补间动画的一种强大补充,它允许开发者在没有实际改变视图属性的情况下模拟属性的变化,提供了更为灵活的动画机制。本篇文章将深入探讨属性动画的工作原理,以帮助开发者...

    Android 动画实例(包含帧动画、补间动画、属性动画)

    本文将深入探讨Android中的帧动画、补间动画以及属性动画这三种主要的动画类型,并通过具体实例进行详细解析。 一、帧动画(Frame Animation) 帧动画是通过显示一系列连续的静态图像来创建动态效果的方法,类似于...

    Android属性动画

    Android属性动画是Android系统中的一种高级动画机制,它在API Level 11(Android 3.0 Honeycomb)被引入,极大地扩展了Android动画的能力。相比于传统的 Tweened Animation(帧动画和补间动画),属性动画提供了更为...

    android视图动画属性动画详解

    本文将详细解析Android视图动画和属性动画的概念、使用方法及其差异。 **视图动画**(View Animation)是Android早期版本中引入的动画系统,它基于`Animation`类和`AnimationSet`类。视图动画并不改变对象的实际状态...

    Android 动画 Animation Demo

    Android动画主要分为两种类型:属性动画(Property Animation)和视图动画(View Animation)。本篇将深入探讨这两种动画机制,以及如何在实际项目中应用它们。 1. **视图动画(View Animation)**:视图动画是...

    Android动画机制全解析source

    本文将全面解析Android的动画机制,包括帧动画、属性动画以及过渡动画,深入理解这些概念将有助于开发者创建更加流畅、交互性强的应用。 一、帧动画(Frame Animation) 帧动画是通过连续播放一系列静态图像来创建...

    android基本动画大盘点

    本文将全面解析Android的基本动画类型,包括Activity动画、Fragment动画、Layout动画、Tween Animation、Frame Animation以及属性动画。 首先,我们来了解Activity动画。Activity动画主要用于场景切换,比如启动、...

    Android动画高级用法演示

    本案例“Android动画高级用法演示”深入探讨了Android动画系统的一些高级特性,包括属性动画、帧动画以及视图动画。 1. **属性动画**(Property Animation): - 属性动画系统是Android 3.0(API 11)引入的,它...

    Android SVG动画PathView源码解析与使用教程(API 14)

    3. **动画实现**:PathView通过监听属性动画库(Property Animation)的变化,动态改变Path对象中的节点坐标,从而实现动画效果。这通常涉及到`ValueAnimator`和`AnimatorUpdateListener`,在每次动画更新时,计算新...

    android动画原理demo

    - **值的改变与监听**:在属性动画中,我们可能使用`Animator.AnimatorListener`监听动画的开始、结束、取消和重复事件,以及`ValueAnimator.AnimatorUpdateListener`来实时更新对象状态。 五、动画性能优化 - ...

    Android Gif动画解析

    一、Android中的GIF动画解析库 1. NineOldAndroids:这是一个兼容Android 2.1及更高版本的库,它允许开发者使用Android 3.0引入的View动画API。虽然NineOldAndroids并不直接支持GIF,但可以结合其他库如GIFDrawable...

    android实用动画整理

    在Android应用中,动画可以分为两大类:视图动画(View Animation)和属性动画(Property Animation)。下面我们将详细探讨这两个方面,并结合描述中提到的几种特定动画效果进行深入解析。 1. 视图动画(View ...

    TestApp-master_zoo3s3_android_分组图表_属性动画_android音频动画_

    在Android开发领域,属性动画(Property Animation)和音频动画组件是增强用户体验的重要工具,而分组图表组件则有助于数据的可视化展示。以下是对这些技术的详细解析: **属性动画(Property Animation)** 属性...

    Android-Android上的Triangulation动画

    Android提供了多种动画机制,包括属性动画(Property Animation)、视图动画(View Animation)和过渡动画(Transition Animation)。属性动画是最新且功能最强大的,它允许开发者直接操作对象的属性并随着时间推移...

    Android中使用SVG实现炫酷动画效果

    - **使用AndroidSVG库**:首先在项目中添加依赖,然后通过`SVG.parse()`方法解析SVG文件,再用`SVGDrawable`转换为可绘制对象,最后将其设置到ImageView或其他视图。 - **使用VectorDrawable**:从Android 5.0...

    Android动画实例

    属性动画包括ValueAnimator、ObjectAnimator和AnimatorSet三个核心类: 1. ValueAnimator:是最基础的动画类,负责计算动画的中间值。可以通过设置时长、插值器、监听器等来定制动画效果。 2. ObjectAnimator:基于...

    Android界面切换动画效果源代码

    属性动画通过ObjectAnimator、ValueAnimator和AnimatorSet来实现。ObjectAnimator可以直接操作对象的属性,ValueAnimator提供了一个连续变化的值流,而AnimatorSet可以用来组合多个动画。对于界面切换,可能使用了...

    解析json进行动画展示

    4. **播放和控制动画**:一旦动画加载完成,你可以使用`play()`方法开始播放,`pause()`暂停,`resume()`恢复播放,`stop()`停止,并使用`progress`属性设置动画进度: ```java animationView.play(); ...

Global site tag (gtag.js) - Google Analytics