Android动画-Animation原理解析

Android动画-Animation原理解析

一、概述

在android中动画分为3类,帧动画、补间动画、属性动画

今天要说的就是“补间动画”,补间动画的基类是Animation,具体的实现都在TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation中实现的

这个动画的原理是将控件View在时间上连续的绘制,就形成了动画,但是这个动画有个2个主要缺点

1、控件的本身没有移动或者旋转,位置信息没有改变,只是在绘制View的时候进行了矩阵Matrix变换

,所以当你要点击控件的时候,必须要点击原来位置的,

2、这个方式实现动画扩展性不高,只能针对View的处理,

google意识到了这点,于是在android3.0推出了属性动画,具体可以在我的对应文章有所分析

今天通过分析Animation来学习借鉴下谷歌的思路,也是不错的

二、分析

首先我们来分析下这个类,这个是一个补间动画的基类,这个类是个抽象类

这个类主要作用:实现了补间动画的基本逻辑,已经提供了子类要实现的接口,形成了基本框架

我们来看下重要的api

//设置动画时间
animation.setDuration(2000);
//设置重复模式,这里有两种RESTART:这个是从头开始,Reverse:这个是反向回到起点
animation.setRepeatMode(Animation.REVERSE);
//这个是动画重复次数,如果设置-1就是无线次数,可以是Animation.INFINITE
animation.setRepeatCount(2);
//设置为true,动画结束时停留在动画的最后一帧
animation.setFillAfter(true);
//设置为false,动画结束时候停留在动画的第一帧
animation.setFillBefore(true);
//重置当前动画,主要是做了清空资源,初始化变量,当我们调用了cancel后,如果要重新开始
//就要先调用下reset哦
animation.reset();
//动画取消,这个取消不是暂停的意思,补间动画是没有暂停功能的,animation没有提供
//这个方法调用后动画会setFillAfter 或者setFillBefore 的设置回到起点还是终点帧
animation.cancel();
//开始动画
animation.start();
//设置一个handler,来处理里面的各种监听事件
animation.setListenerHandler(Handler)
//设置动画监听
animation.setAnimationListener();
//设置插值器,这个可以改变动画的运动节奏,
animation.setInterpolator(interpolator);
//设置动画开始的延迟时间,默认是0 意思 立即开始动画
animation.setStartOffset(2000);
//按照scale 对animation和startOffset 进行比例缩放或者扩大
animation.scaleCurrentDuration(scale);
    

以上就是Animation 经常用的api了

/**
 * Helper for getTransformation. Subclasses should implement this to apply
 * their transforms given an interpolation value.  Implementations of this
 * method should always replace the specified Transformation or document
 * they are doing otherwise.
 *
 * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
 *        after it has been run through the interpolation function.
 * @param t The Transformation object to fill in with the current
 *        transforms.
 */
protected void applyTransformation(float interpolatedTime, Transformation t) {
    
    
}

这个是Animation中需要子类重写的方法,需要根据自己的需求实现对应逻辑的

那么我们就来看下TranslateAnimation如何实现的

主要下面这三个方法

//这个方法就是初始化起始点和重点,还有对应的类型,这个类型包含三种
//public static final int ABSOLUTE = 0; 绝对的类型,传入的是像素点
//public static final int RELATIVE_TO_SELF = 1; 这个是相对于自己的长度,toXValue=2,2倍的自己width长度
//public static final int RELATIVE_TO_PARENT = 2; 这个是相对于父视图的尺寸对比
//
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
        int fromYType, float fromYValue, int toYType, float toYValue) {
    
    

    mFromXValue = fromXValue;
    mToXValue = toXValue;
    mFromYValue = fromYValue;
    mToYValue = toYValue;

    mFromXType = fromXType;
    mToXType = toXType;
    mFromYType = fromYType;
    mToYType = toYType;
}

/*
interpolatedTime:这个是经过插值器计算完了之后的返回值
Transformation:这个是一个转换器,是Animation和View的绘制的桥梁,Transformation里面有个Matrix变换
矩阵,Animation的子类对这个转换器操作后,View拿到后,取出matrix来进行实际的转换
*/
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    
    
    float dx = mFromXDelta;
    float dy = mFromYDelta;
    if (mFromXDelta != mToXDelta) {
    
    
        //根据插值器的值,在什么比例进度上 做不同的距离移动
        dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
    }
    if (mFromYDelta != mToYDelta) {
    
    
        dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
    }
    //将这个值设置到matrix
    t.getMatrix().setTranslate(dx, dy);
}


//将不同的type类型 转换成像素
//比如类型是RELATIVE_TO_SELF,相对于自己,自己控件本身width=200 那么这个最终的值是:mFromXValue*自己控件本身width
/*
protected float resolveSize(int type, float value, int size, int parentSize) {
        switch (type) {
            case ABSOLUTE:
                return value;
            case RELATIVE_TO_SELF:
                return size * value;
            case RELATIVE_TO_PARENT:
                return parentSize * value;
            default:
                return value;
        }
    }
*/
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    
    
    super.initialize(width, height, parentWidth, parentHeight);
    mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
    mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
    mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
    mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
}

applyTransformation 这个方法是子类需要实现的方法,那问题来了,这个方法在什么地方调用的呢,很显然这个应该是父类Animation里调用的,我们能可以找到是在父类中的getTransformation中调用的

getTransformation方法是Animation的核心方法实现了插值器的标准值计算(0-1)还有对重复执行的控制逻辑

这个方法子类一般不用重写

public boolean getTransformation(long currentTime, Transformation outTransformation) {
    
    
    if (mStartTime == -1) {
    
    
        mStartTime = currentTime;
    }

    final long startOffset = getStartOffset();
    final long duration = mDuration;
    //这个参数是插值器Interpolation的标准值时间范围是0-1,这个是个进度的值
    float normalizedTime;
    if (duration != 0) {
    
    
        //这里是这个值的计算核心,很明显 进度值=(外部时间的插值)/持续的时间
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
    
    
        // time is a step-change with a zero duration
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }

    final boolean expired = normalizedTime >= 1.0f || isCanceled();
    mMore = !expired;

    if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
    
    
        if (!mStarted) {
    
    
            fireAnimationStart();
            mStarted = true;
            if (NoImagePreloadHolder.USE_CLOSEGUARD) {
    
    
                guard.open("cancel or detach or getTransformation");
            }
        }

        if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

        if (mCycleFlip) {
    
    
            normalizedTime = 1.0f - normalizedTime;
        }
		
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
        //这里调用了applyTransformation,这个方法是子类需要实现的,
        applyTransformation(interpolatedTime, outTransformation);
    }
	//这里面的逻辑主要是实现重复执行的控制
    //只有一圈执行完了 才能进入这里判断
    
    if (expired) {
    
    
        if (mRepeatCount == mRepeated || isCanceled()) {
    
    
            if (!mEnded) {
    
    
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
    
    
            //如果mRepeatCount>0 那么mRepeated,到时候mRepeatCount == mRepeated的时候就停止动画了
            //如果mRepeatCount==-1 那么就无线循环,一直动画,因为mRepeatCount == mRepeated 永远			//不可能为true,
            //后面就一直返回mMore=true
            if (mRepeatCount > 0) {
    
    
                mRepeated++;
            }

            if (mRepeatMode == REVERSE) {
    
    
                mCycleFlip = !mCycleFlip;
            }

            mStartTime = -1;
            //这里设置为true,这最终会传入到View中的draw方法,作为返回值的一部分
            //如果为true 就是动画正在进行,没有完,还要继续画
            mMore = true;
			//回调AnimationLister接口
            fireAnimationRepeat();
        }
    }

    if (!mMore && mOneMoreTime) {
    
    
        mOneMoreTime = false;
        return true;
    }

    return mMore;
}

Animation在View的执行逻辑

我们先前说过这个动画的绘制是在View的draw中执行的,那我们就来找一下,有这么段代码

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    
    
    /*
    省去部分代码
    */

    if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
    
    
        parent.getChildTransformation().clear();
        parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
    }

    Transformation transformToApply = null;
    boolean concatMatrix = false;
    final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
    final Animation a = getAnimation();
    //判断这个Animation
    if (a != null) {
    
    
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
    
    
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        transformToApply = parent.getChildTransformation();
    }

继续看,我们在View#applyLegacyAnimation方法中找到,那我们来看下吧

private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
        Animation a, boolean scalingRequired) {
    
    
    Transformation invalidationTransform;
    final int flags = parent.mGroupFlags;
    final boolean initialized = a.isInitialized();
    if (!initialized) {
    
    
        a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
        a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
        if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
        onAnimationStart();
    }
	
    final Transformation t = parent.getChildTransformation();
    /*
	这个就是调用前面说的getTransformation方法的调用地方
	如果返回true,就标识动画要继续执行
	*/
    boolean more = a.getTransformation(drawingTime, t, 1f);
    if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
    
    
        if (parent.mInvalidationTransformation == null) {
    
    
            parent.mInvalidationTransformation = new Transformation();
        }
        invalidationTransform = parent.mInvalidationTransformation;
        a.getTransformation(drawingTime, invalidationTransform, 1f);
    } else {
    
    
        invalidationTransform = t;
    }
	//如果true,就继续调用parent的invalidate方法 继续循环draw
    //上层的ViewGroup会执行dispathchDraw 最后调用的子View的draw中 往复执行
    if (more) {
    
    
        if (!a.willChangeBounds()) {
    
    
            if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                    ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
    
    
                parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
            } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
    
    
                // The child need to draw an animation, potentially offscreen, so
                // make sure we do not cancel invalidate requests
                parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                //继续执行动画
                parent.invalidate(mLeft, mTop, mRight, mBottom);
            }
        } else {
    
    
            if (parent.mInvalidateRegion == null) {
    
    
                parent.mInvalidateRegion = new RectF();
            }
            final RectF region = parent.mInvalidateRegion;
            a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                    invalidationTransform);

            // The child need to draw an animation, potentially offscreen, so
            // make sure we do not cancel invalidate requests
            parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;

            final int left = mLeft + (int) region.left;
            final int top = mTop + (int) region.top;
            //继续执行动画
            parent.invalidate(left, top, left + (int) (region.width() + .5f),
                    top + (int) (region.height() + .5f));
        }
    }
    return more;
}

三、总结

通过上面的分析,Animation核心还是通过view的重绘来实现动画效果,重绘的过程需要设置矩阵Matrix来实现不同动画,控件的实际位置没有该改变

Animation补间动画用法没那么灵活,扩展性也不好,取而代之的是属性动画

但是通过对android-Animation的分析和理解来拓展我们的思路

Guess you like

Origin blog.csdn.net/fagawee/article/details/121036019