Android属性动画完全解析(二)

来自郭神:http://blog.csdn.net/guolin_blog/article/details/43536355

(一)TypeEvaluator

在前面一篇文章中,我们已经了解到了Android属性动画的一些基本用法,这些说起来够用吗,可能是够用的。但是如果我们想自定义属性绘制的过程,那该怎么破?这时就需要用到我们的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这个系数,再加上初始值,那么就得到当前动画的值了。

系统提供了ofFloat和ofInt,实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。

(二)实例

之前在郭神的博客中,也提到过一点,如何对View上的一个点进行操作,好的,现在我们就来构造这样一个点。
先说一下实例的思路:先构造一个点,由这个点去绘制圆,完成的动画是圆从屏幕的左上角直线移动到右下角。

首先我们构造一个point对象,标记坐标x,y

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

然后利用TypeEvaluator来构造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;
    }
}

然后为了实现圆的变色过程,我们还加入了ColorEvaluator,注释写得挺详细的,就不细说了:

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;

        // 首先解释一下这一段,我们传进来的Color是String类型的,
        // 格式如同"#ff00ff",然后进行substring(1,3)就是切割字符串,#为位置0
        // substring(1,3)就是把切割1到3-1位,那1、2位的ff,依此类推,切成ff,00,ff
        // parseInt(string,16)就是以基数为16去分析string进行转换
        // 如parseInt("FF", 16) 返回 255
        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 greenDif=Math.abs(startGreen-endGreen);
        int blueDiff=Math.abs(startBlue-endBlue);
        int colorDiff=redDiff+greenDif+blueDiff;
        // 整体的变化是colorDiff来控制,小的变化为offset来控制,实现变化方式的不同
        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+greenDif,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;
        // 在startColor和endColor之间进行变化,分两种情况讨论
        // 计算当前颜色,并设定currentColor的边界为endColor
        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;
    }

然面将以上这两个写进自定义View中:

public class MyAnimView extends View {

    public static final float RADIUS=50f;

    private Point currentPoint;

    private Paint mPaint;

    private String color;

    public MyAnimView(Context context,AttributeSet attrs) {
        super(context,attrs);
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
    }

    @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();
            }
        });
        ObjectAnimator anim2=ObjectAnimator.ofObject(this,"color",new ColorEvaluator(),"#0000ff","#ff0000");
        AnimatorSet animatorSet=new AnimatorSet();
        animatorSet.play(anim).with(anim2);
        animatorSet.setDuration(3000);
        animatorSet.start();
    }

    public String getColor(){
        return color;
    }

    public void setColor(String color){
        this.color=color;
        mPaint.setColor(Color.parseColor(color));
        invalidate();//刷新
    }
}

就是一些drawCircle,用play with来控制动画等等。
然后在布局文件中引入自定义View,在Activity中调用即可。

到此我们也感受到属性动画的魅力了,可以对点操作,可以对View进行变色。这一些在之前的补间动画中是完全没办法做到的。

(三)Interpolator

Interpolator内插值, 被用来修饰动画效果,定义动画的变化率,可以使存在的动画效果accelerated(加速),decelerated(减速),repeated(重复),bounced(弹跳)等。

Android属性动画完全解析(二)_第1张图片

AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速

AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速

AnticipateInterpolator 开始的时候向后然后向前甩

AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值

BounceInterpolator 动画结束的时候弹起

CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线

DecelerateInterpolator 在动画开始的地方快然后慢

LinearInterpolator 以常量速率改变

OvershootInterpolator 向前甩一定值后再回到原来位置

那它是怎么使用的,只要在anim.start( )之前调用就可以了:

 anim.setInterpolator(new AccelerateInterpolator(2f));
 anim.start();

那么这个动画的控制速率的过程是怎么实现的呢:
来看下它的源码:

/** * An interpolator where the rate of change starts and ends slowly but * accelerates through the middle. * */  
@HasNativeInterpolator  
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {  
    public AccelerateDecelerateInterpolator() {  
    }  

    @SuppressWarnings({"UnusedDeclaration"})  
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {  
    }  

    public float getInterpolation(float input) {  
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  
    }  

    /** @hide */  
    @Override  
    public long createNativeInterpolator() {  
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();  
    }  
}  

最重要的是 getInterpolation(float input) 这个方法,
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f是一个余弦函数,如果改成正弦还是其它的就可以实现不同的加强效果了。那如果要实现自定义过程了,就去继承然后改写这个方法就可以了。

(四)ViewPropertyAnimator的用法

这个用法是Android团队为大家提供的一个便利,方便使用。
View.animate().alpha(0f); 就是对它的一个调用。
animate()方法就是在Android 3.1系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,这里我们调用了alpha()方法并转入0,表示将当前的textview变成透明状态。

它还支持链式使用:
textview.animate().x(500).y(500).setDuration(5000)
.setInterpolator(new BounceInterpolator());
就是动画移动到横坐标500纵坐标500,动画时间5000毫秒,内插值为弹跳插值。默认是不需要start方法,如果想要显示地start,也可以自己添加。

(五)布局动画 (Layout Animations)

主要使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时存在过渡的动画效果。

LayoutTransition transition = new LayoutTransition(); 
    transition.setAnimator(LayoutTransition.CHANGE_APPEARING,  
            transition.getAnimator(LayoutTransition.CHANGE_APPEARING)); 
    transition.setAnimator(LayoutTransition.APPEARING,  
            null); 
    transition.setAnimator(LayoutTransition.DISAPPEARING,  
            null); 
    transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,  
            null); 
    mGridLayout.setLayoutTransition(transition); 

过渡的类型一共有四种:

LayoutTransition.APPEARING 当一个View在ViewGroup中出现时,对此View设置的动画

LayoutTransition.CHANGE_APPEARING 当一个View在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画

LayoutTransition.DISAPPEARING 当一个View在ViewGroup中消失时,对此View设置的动画

LayoutTransition.CHANGE_DISAPPEARING 当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画

要注意作用对象是此View还是其它View
综合的例子就是在ViewGroup中添加一个GridLayout而已,进行布局动画演示。

       // 创建一个GridLayout 
        mGridLayout = new GridLayout(this);  
        // 设置每列5个按钮 
        mGridLayout.setColumnCount(5);  
        // 添加到布局中 
        viewGroup.addView(mGridLayout);  
        //默认动画全部开启 
        mTransition = new LayoutTransition();  
        mGridLayout.setLayoutTransition(mTransition);  

还有进行LayoutTransition

 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)  
    {  
        mTransition = new LayoutTransition(); 
        mTransition.setAnimator(  
                LayoutTransition.APPEARING,  
                (mAppear.isChecked() ? mTransition  
                        .getAnimator(LayoutTransition.APPEARING) : null)); 
        mTransition  
                .setAnimator(  
                        LayoutTransition.CHANGE_APPEARING,  
                        (mChangeAppear.isChecked() ? mTransition  
                                .getAnimator(LayoutTransition.CHANGE_APPEARING)  
                                : null)); 
        mTransition.setAnimator(  
                LayoutTransition.DISAPPEARING,  
                (mDisAppear.isChecked() ? mTransition  
                        .getAnimator(LayoutTransition.DISAPPEARING) : null)); 
        mTransition.setAnimator(  
                LayoutTransition.CHANGE_DISAPPEARING,  
                (mChangeDisAppear.isChecked() ? mTransition  
                        .getAnimator(LayoutTransition.CHANGE_DISAPPEARING)  
                        : null)); 
        mGridLayout.setLayoutTransition(mTransition); 
    }  
}  

先设置mTransition触发操作的情况,再把其作为参数传mGridLayout的setLayoutTransition方法中。

到此,关于属性动画的学习就完成了。
感谢郭神。
感谢郭神。
感谢郭神。
重要的事情说三遍。

代码主要来自己郭神博客的整理,能够运行,看郭神博客可以运行下这个动画过程。
整体的源码如下:
http://download.csdn.net/detail/linshijun33/9212155

你可能感兴趣的:(动画,android,属性动画)