Android动画总结系列(3)——补间动画源码分析

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013478336/article/details/52207314
上文总结了Android补间动画的基本用法,Android补间动画的原理是通过给定初始帧和结束帧的值,再通过在给定时间间隔内计算插值并刷新界面,来形成渐变动画效果。Android补间动画包括四个基本效果:平移/旋转/缩放/透明度。上文由于篇幅原因没有解释其源码,本文专门分析补间动画的相关源码。

一、整体结构

Animation是四个基本动画效果的基类,其定义了动画的基础属性和获取变换(Transformation)的基本操作模板;
AnimationUtils是动画的执行帮助类,具体见第三章描述;
平移/缩放/旋转/透明度动画都是Animation的子类,AnimationSet动画集合是Animation动画的组合;

二、基类Animation

2.1 Animation支持的XML属性
duration:一次动画的执行耗时;
startOffset:动画从startTime到真正执行的等待事件,也就是开始偏移时间量;
fillEnabled:表示动画开始前和结束后是否还继续应用动画的转化效果,默认false;
fillBefore:动画开始执行前是否应用动画的转化效果,默认true;但受fillEnabled影响:fillEnabled=false,此方法值的false无效而true有效;fillEnabled=true,此方法的true/false都有效;
fillAfter:动画结束执行后是否应用动画的转化效果,默认false;也受fillEnabled影响:fillEnabled=false,此方法的false无效而true有效,fillEnabled=true,此方法的true/false都有效;
repeatCount:动画重复执行次数
repeatMode:动画重复执行时的模式,可以指定重复执行是从头执行或者逆序执行到头
zAdjustment:动画执行过程中待执行动画的View的展示层次
background:动画执行过程中的背景
detachWallpaper:如果动画是Window动画,而Window设置了背景,则此值true表示Window展示动画,而背景保持不动,不执行动画;false表示背景和Window一起执行动画;
interpolator:动画的插值器

2.2 构造器
2.2.1 Animation() 构造一个空动画(执行时间0,默认插值器,fillBefore为true而fillAfter为false)
2.2.2 Animation (Context context, AttributeSet attrs) 通过资源初始化一个动画,初始化上面列出的xml属性

2.3 关键方法与内部类
与set方法相对的get方法略去。

2.3.1 void reset():重置动画状态,回到初始初始状态;

2.3.2 void cancel():取消动画执行,此操作会调用动画监听器告知动画结束,如果手动调用cancel,则想要再次执行动画需要调用reset;

2.3.3 boolean isInitialized():判断动画是否已经初始化(initialized);

2.3.4 void initialize( int width, int height, int parentWidth, int parentHeight):传入需待执行动画的对象(一般是View)和其父元素的宽高,来初始化动画,这是为了支持上文中提到的RELATIVE_TO_SELF和RELATIVE_TO_PARENT属性;当待执行动画的对象和其父元素的宽高确定时,就应该调用此方法,同时,此方法应在getTransformation前调用。width/height代表待执行动画对象的宽/高,parentWidth/parentHeight代表待执行动画对象父元素的宽/高;
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    reset();
    mInitialized = true;
}
需要注意的是,这段代码并没有真的对宽高进行操作,而是在子类中覆写此方法来使用宽高的。

2.3.5 void setListenerHandler(Handler handler):设置回调动画事件的Handler,注意,这个方法不是对外可见的,我们一般也不用上,除非说强烈需要动画回调到某个进程内去;

2.3.6 void setInterpolator(Context context, @InterpolatorRes int resID):设置动画插值器,传入resId;

2.3.7 void setInterpolator(Interpolator i):设置动画插值器,常用;

2.3.8 void setStartOffset( long startOffset):设置动画开始执行的时间偏移量(动画开始执行相对其开始执行时间的偏差),单位为毫秒,动画从时间startTime + startOffset开始执行,在AnimationSet中,某些子动画需要延迟执行,则可调用此方法;

2.3.9 void setDuration( long durationMillis):设置动画执行时间,不能为负数;

2.3.10 void restrictDuration( long durationMillis):指定动画最长可以执行的时间;此方法实现在预期执行动画时间超出durationMillis时,通过减少duration和repeatCount的值来保证动画总执行时间不超过durationMillis;动画的一次执行时间等于 startOffset + duration;

2.3.11 void scaleCurrentDuration( float scale):将duration和startOffset缩放scale倍;

2.3.12 void setStartTime( long startTimeMillis):设置动画开始执行的时间;如果值被设为START_ON_FIRST_FRAME,动画将在第一次调用getTransformation时开始执行;此方法传入的startTimeMillis应使用;AnimationUtils.currentAnimationTimeMillis而非System.currentTimeMillis()获取;

2.3.13 void start():第一次调用getTransformation时开始执行动画;

2.3.14 void startNow():在当前时间开始执行动画;

2.3.15 void setRepeatMode( int repeatMode):设置动画重复模式,标识动画执行到结束帧时该如何执行;RESTART表示从初始帧执行动画,REVERSE表示从结束帧到初始帧执行动画;仅在repeatCount>0或者动画无限执行时有效果;

2.3.16 void setRepeatCount( int repeatCount):设置动画重复执行次数;

2.3.17 boolean isFillEnabled():fillEnabled为true,表示动画会应用fillBefore的设置;

2.3.18 void setFillEnabled( boolean fillEnabled):设置fillEnabled值,表示是否需要考虑fillBefore属性,true表示会应用fillBefore属性,false表示fillBefore属性会被忽略,动画转化效果只能在动画结束时才会被应用到对象展示上;

2.3.19 void setFillBefore( boolean fillBefore):fillBefore为true表示在动画开始执行前,就将其属性应用到View展示上;注意一点动画集合也适用此逻辑,在动画集合AnimationSet未开始执行时,动画效果也不会应用在界面展示上;

2.3.20 void setFillAfter( boolean fillAfter):true表示在动画执行结束后,动画效果还持续存在,false表示执行结束后View展示回到原始位置,也就是View的可视区域的大小;

2.3.21 void setZAdjustment( int zAdjustment):设置动画运行时View的Z轴层次展示模式。取值有三种:
Animation.ZORDER_NORMAL:待执行动画View保持在当前Z轴层次上;
Animation. ZORDER_TOP:待执行动画View在执行动画过程中的层次强制在所有其他View的上方;
Animation.ZORDER_BOTTOM:与ZORDER_TOP相反,待执行动画View在执行动画过程中层次强制在所有其他View的下方;

2.3.22 void setBackgroundColor(@ColorInt int bg):设置动画的背景

2.3.23 float getScaleFactor():缩放因子在动画执行过程中影响与View坐标相关的值如中心点等;在getTransformation(long, Transformation, float)方法中设置该值,其实就是第三个参数;一般在applyTransformation调用此方法获取缩放值;

2.3.24 void setDetachWallpaper( boolean detachWallpaper):此值为true且动画应用于有壁纸的Window,则Window将与壁纸脱离执行动画,也就是说,在动画执行过程中,壁纸保持不动。false则壁纸跟随window的动画变动;

2.3.25 boolean willChangeTransformationMatrix():标识动画是否会影响transformation matrix(转化矩阵),透明度动画不影响此矩阵,而缩放动画会影响此矩阵;

2.3.26 boolean willChangeBounds():标识动画是否会影响View的边界,透明度动画不会影响,而 >100%的缩放动画会影响边界;注意,这里影响了边界也并不会导致View重新布局(layout);不管View缩放到多大,都只会影响View的可视宽高,不影响其布局宽高,具体见上一篇文章。

2.3.27 void setAnimationListener(AnimationListener listener):设置动画事件监听,能监听的事件有三个:动画开始、动画结束和动画重复

2.3.28 long computeDurationHint():估算动画应该会执行多长时间,正常情况下只要重写此类的动画没特意调整,此值都是精确的;

2.3.29 boolean getTransformation( long currentTime, Transformation outTransformation, float scale):获取在指定时间点的transformation,此方法会修改outTransformation的值,并返回动画是否还在执行的标志
currentTime:当前的动画执行位置,值是真实世界时间(wall clock time);
outTransformation:调用方传入的transformation,由动画填充后供调用方使用;
scale:缩放因子,应用于所有的转化动作(transform operation);
返回值:true表示动画还在执行,false表示动画不再执行;

2.3.30 boolean hasStarted():动画是否已经开始执行;

2.3.31 boolean hasEnded():动画是否已经结束执行;

2.3.32 void applyTransformation( float interpolatedTime, Transformation t):子类实现此方法根据插值时间来应用其转化效果,比如说透明度动画,在时间0.5时的透明度等;interpolatedTime是0~1之间的规整化时间;

2.3.33 通过type解析值,这就是上文中说的三个参数的值解析方法
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;
    }
}

2.3.34 Description内部类:完成xml布局中动画尺寸的解析工作,xml布局中绝对值以dp结尾,相对于自己以%结尾,相对于父元素以%p结尾。

2.4 核心方法
我们在2.3中基本列出了所有Animation类的属性设置与其意义。但Animation最核心的一个方法boolean getTransformation( long currentTime, Transformation outTransformation)我们并未讨论。此处专开一节讨论此方法。
我们直接在源码内讲解此方法:
/**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
* 此方法的作用是计算指定时间应该对View应用的transformation效果。此方法的实现会改变外部传入的outTranformation的内容从而形成动画效果。
*
* @param currentTime Where we are in the animation. This is wall clock time.当前我们在动画的位置,此时间是真实时间,也就是System.currentTimeMillis时间
* @param outTransformation A transformation object that is provided by the
*        caller and will be filled in by the animation.外部传入的Transformation对象,动画会根据当前动画执行时间时间来调整tranformation对象的内容
* @return True if the animation is still running;返回true表示动画还在执行,false表示动画执行结束了
*/
public boolean getTransformation(long currentTime, Transformation outTransformation) {
    if (mStartTime == -1) {
        //前文描述了一种设置startTime为START_ON_FIRST_FRAME动作,表示动画在界面绘制的第一帧开始执行,这里就是其StartTime的真正赋值
        //后文描述了当动画需要多次执行时,每次执行结束后startTime会置为-1,重新经此方法决定动画startTime
        mStartTime = currentTime;
    }

    //获取动画的执行偏移时间,动画真正的执行耗时是startTime + startOffset。startOffset表示动画在startTime后还要等待startOffset才执行
    final long startOffset = getStartOffset();
    //获取动画执行一次的耗时
    final long duration = mDuration;
    float normalizedTime;
    if (duration != 0) {
        //计算当前时间在动画一次执行过程中的位置,此值如果是在startOffset范围内,也就是动画还没执行,则结果为<0;
        //开始执行时为0,如果是执行过程中肯定<1,执行结束后则一定>=1
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        // time is a step-change with a zero duration
        //duration为0,则是初始帧和结束帧来回切换的动画,时间只有0 1两种取值
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f ;
    }

    //动画一次执行结束了
    final boolean expired = normalizedTime >= 1.0f;
    //mMore表示动画是否还有帧可以执行,默认是只执行一次,所以一次后mMore=false;后面会根据repeatCount重新决定此值
    mMore = !expired;

    //mFillEnabled为false,直接将时间规整到0~1之间(FillEnabled默认值是false)
    if (!mFillEnabled) normalizedTime = Math.max(Math. min(normalizedTime, 1.0f ), 0.0f );

    //fillBefore支持时间<0,fillAfter支持时间>1
    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        if (!mStarted) {
            //通知动画开始
            fireAnimationStart();
            mStarted = true;
            if (USE_CLOSEGUARD) {
                guard.open( "cancel or detach or getTransformation");
            }
        }

        //mFillBefore或mFillEnd导致规整时间不在0~1之间,再规整一遍
        if (mFillEnabled) normalizedTime = Math.max(Math. min(normalizedTime, 1.0f ), 0.0f );

        //动画的重复模式reverse时,往回执行
        if (mCycleFlip) {
            normalizedTime = 1.0f - normalizedTime;
        }

        //获取插值时间,因为均匀时间被插值器打乱成插值时间,形成了动画的速率变化效果
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
        //子类实现此方法,来真正的应用动画效果
        applyTransformation(interpolatedTime, outTransformation);
    }

    if (expired) {
        if (mRepeatCount == mRepeated) {
            //动画执行次数已经全部执行完成,则通知动画结束
            if (!mEnded) {
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
            if (mRepeatCount > 0) {
                //已执行次数+1
                mRepeated++;
            }

            if (mRepeatMode == REVERSE) {
                //动画执行结束了一轮后,动画执行是否需要反转的标志位要切换过来
                mCycleFlip = ! mCycleFlip;
            }

            //startTime设为-1,下次调用此方法时重新赋值
            mStartTime = -1;
            //因为动画轮数没有执行完成,所以mMore置为true
            mMore = true;

            //告知动画重复执行事件
            fireAnimationRepeat();
        }
    }

    //动画已经执行结束了,但是OneMoreTime为true,返回标识告诉外面动画仍在执行
    //mOneMoreTime默认值为true,仅此处和cancel接口会置此值为false,所以,外部还会再调一次getTransfrom,这时候就是界面会考虑fillAfter来决定
    if (!mMore && mOneMoreTime) {
        mOneMoreTime = false;
        return true;
    }

    //返回动画是否还在运行
    return mMore;
}
此处实现的是通用的动画改变transformation前后的通用处理,由子类实现applyTransformation来真正的填充转化效果。

三、AnimationUtils类
AnimationUtils内所有方法都是静态的,因为此类是帮助类,不需要初始化,此节讲解一些此类的接口。

3.1相关接口
3.1.1 long currentAnimationTimeMillis():返回的是SystemClock. uptimeMillis(),此值不等同于System.currentTimeMillis;
3.1.2 Animation loadAnimation(Context context, @AnimRes int id):从布局文件中解析出动画,此方法包含的是从xml到动画的XML解析过程;
3.1.3 支持解析set/translate/alpha/rotate/scale五种标签;
3.1.4 LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id):解析LayoutAnimation,本文不涉及此知识,LayoutAnimation支持layoutAnimation、gridLayoutAnimation标签;
3.1.5 Animation makeInAnimation(Context c, boolean fromLeft):返回一个左右平移和透明度效果的View显示动画;
3.1.6 Animation makeOutAnimation(Context c, boolean toRight):返回一个左右平移和透明度效果的View消失动画;
3.1.7 Animation makeInChildBottomAnimation(Context c):返回一个子元素显示动画,动画支持从下到上平移和透明度效果;
3.1.8 Interpolator loadInterpolator(Context context, @InterpolatorRes int id):加载一个插值器;支持的标签有:
linearInterpolator:线性插值,匀速
accelerateInterpolator:加速插值
decelerateInterpolator:减速插值
accelerateDecelerateInterpolator:先加速再减速插值
cycleInterpolator:循环插值,没研究过
anticipateInterpolator:没研究
overshootInterpolator:没研究
anticipateOvershootInterpolator:没研究
bounceInterpolator:没研究
pathInterpolator:没研究
详细可见文章http://blog.csdn.net/lgaojiantong/article/details/39451243,有图文效果;

四、基本效果相关类
4.1 TranslateAnimation
4.1.1 支持的XML属性
fromXDelta/toXDelta/fromYDelta/toYDelta:具体见上一篇文章(补间动画使用),由上文中的Description类解析。绝对偏移量用dp/px标识,相对于自己用%标识,相对于父元素用%p标识。代码见TranslateAnimation(Context context, AttributeSet attrs);

4.1.2 其他构造方法:
TranslateAnimation( float fromXDelta, float toXDelta, float fromYDelta, float toYDelta):调用此构造方法传入的都认为是绝对偏移值
TranslateAnimation( int fromXType, float fromXValue, int toXType, float toXValue,
  int fromYType, float fromYValue, int toYType, float toYValue):调用此构造方法可以传入相对值

4.1.3 初始化动画宽高:
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);
}
上文中提到的mFromXValue/mToXValue/mFromYValue/mToYValue可能是绝对值,也可能是相对View自己或相对View父元素的百分比,此处就是要根据各值的Type和当前元素宽高及父元素宽高,来算出最终的绝对偏移量。计算结果存在mFromXDelta/mToXDelta/mFromYDelta/mToYDelta四个成员变量内。

4.1.4 填充转化过程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    float dx = mFromXDelta;
    float dy = mFromYDelta;
    //简单的插值计算,计算出X轴在规整化的时间interpolatedTime时的偏移量
    if ( mFromXDelta != mToXDelta) {
        dx = mFromXDelta + (( mToXDelta - mFromXDelta ) * interpolatedTime);
    }

    //计算出y轴在规整化的时间interpolatedTime时的偏移量
    if ( mFromYDelta != mToYDelta) {
        dy = mFromYDelta + (( mToYDelta - mFromYDelta ) * interpolatedTime);
    }
    //对转化矩阵设置平移效果
    t.getMatrix().setTranslate(dx, dy);
}
核心的代码就这么多,一个平移动画就出来了~~~

4.2 ScaleAnimation
4.2.1 支持的XML属性
fromXScale/toXScale/fromYScale/toYScale/pivotX/pivotY,具体用法见前一篇文章(补间动画使用),pivotX和pivotY是由Description解析的,其他的四个缩放比例是ScaleAnimation解析的。

4.2.2 其他构造方法:
ScaleAnimation( float fromX, float toX, float fromY, float toY):传入各缩放比例
ScaleAnimation( float fromX, float toX, float fromY, float toY, float pivotX, float pivotY):传入各缩放比例和绝对的中心点
ScaleAnimation( float fromX, float toX, float fromY, float toY,
  int pivotXType, float pivotXValue, int pivotYType, float pivotYValue):传入各缩放比例,和各种类型的中心点定义

4.2.3 初始化动画宽高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);

    mFromX = resolveScale( mFromX, mFromXType , mFromXData , width, parentWidth);
    mToX = resolveScale( mToX, mToXType , mToXData , width, parentWidth);
    mFromY = resolveScale( mFromY, mFromYType , mFromYData , height, parentHeight);
    mToY = resolveScale( mToY, mToYType , mToYData , height, parentHeight);

    mPivotX = resolveSize( mPivotXType, mPivotXValue , width, parentWidth);
    mPivotY = resolveSize( mPivotYType, mPivotYValue , height, parentHeight);
}
//从value中解析出绝对宽高,然后与View的宽高相比,返回缩放比例
float resolveScale( float scale, int type, int data, int size, int psize) {
    float targetSize;
    //这两种type的值是绝对宽高值,解析出来,放到后面与View宽高做比率
    if (type == TypedValue. TYPE_FRACTION) { 
        targetSize = TypedValue.complexToFraction(data, size, psize);
    } else if (type == TypedValue.TYPE_DIMENSION ) {
        targetSize = TypedValue.complexToDimension(data, mResources .getDisplayMetrics());
    } else {
        //scale本身就是比例
        return scale;
    }

    if (size == 0) {
        return 1 ;
    }

    return targetSize/( float)size;
}

4.2.4 填充转化过程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    float sx = 1.0f ;
    float sy = 1.0f ;
    float scale = getScaleFactor();

    //计算出当前X轴的缩放比率
    if ( mFromX != 1.0f || mToX != 1.0f) {
        sx = mFromX + (( mToX - mFromX ) * interpolatedTime);
    }
    //计算出当前Y轴的缩放比例
    if ( mFromY != 1.0f || mToY != 1.0f) { 
        sy = mFromY + (( mToY - mFromY ) * interpolatedTime);
    }

    if ( mPivotX == 0 && mPivotY == 0 ) {
        //如果中心点就是View左上角(0,0)
        t.getMatrix().setScale(sx, sy);
    } else {
        //如果设置了指定的中心点(中心点受外部的缩放因子影响)
        t.getMatrix().setScale(sx, sy, scale * mPivotX , scale * mPivotY);
    }
}

4.3 RotateAnimation
4.3.1 支持的XML属性
fromDegrees/toDegrees:动画开始的旋转角度和动画结束的旋转角度,值是360度制的。

4.3.2 其他构造方法:
RotateAnimation( float fromDegrees, float toDegrees):传入动画开始结束的旋转角度,旋转中心点是View左上角;
RotateAnimation( float fromDegrees, float toDegrees, float pivotX, float pivotY):传入动画开始结束的旋转角度和绝对的中心点坐标,动画中心点(X+pivotX, Y+pivotY);
RotateAnimation (float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,
  int pivotYType, float pivotYValue):传入旋转角度和各种类型的中心点坐标

4.3.3 初始化动画宽高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);
    //使用Animation中的计算方法计算中心点,传入的当前元素宽高和父元素宽高
    mPivotX = resolveSize( mPivotXType, mPivotXValue , width, parentWidth);
    mPivotY = resolveSize( mPivotYType, mPivotYValue , height, parentHeight);
}

4.3.4 填充转化过程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    //计算插值的旋转角度
    float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
    float scale = getScaleFactor();
   
    if ( mPivotX == 0.0f && mPivotY == 0.0f ) {
        //绕(0,0)旋转
        t.getMatrix().setRotate(degrees);
    } else {
        //绕中心点旋转
        t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
    }
}

4.4 AlphaAnimation
4.4.1 支持的XML属性
fromAlpha/toAlpha设置动画开始时的透明度和结束时透明度,值在0~1之间

4.4.2 其他构造方法:
AlphaAnimation( float fromAlpha, float toAlpha):传入动画开始结束的Alpha值

4.4.3 初始化动画宽高:不需要

4.4.4 填充转化过程(applyTransformation)
protected void applyTransformation( float interpolatedTime, Transformation t) {
    final float alpha = mFromAlpha;
    //对变换设置透明度
    t.setAlpha(alpha + (( mToAlpha - alpha) * interpolatedTime));
}

4.4.5 其他方法
public boolean willChangeTransformationMatrix() {
    //告知外层调用方(也就是View),此动画不改变变换矩阵,
    //上面代码也看到了,Alpha改变的是Transformation本身,并不影响Matrix,这与其他三种变换不同
    return false;
}

public boolean willChangeBounds() {
    //告知调用方此动画不改变View边界
    return false ;
}

public boolean hasAlpha() {
    //告知调用方此动画需要透明度支持
    return true ;
}

4.5 AnimationSet
动画集合使用列表来存储子动画:
private ArrayList<Animation> mAnimations = new ArrayList<Animation>(); //存储的是所有的子动画
private long [] mStoredOffsets ; //存放的是每个子动画自己定义的StartOffset属性

4.5.1 支持的XML属性
shareInterpolator:true表示所有子元素都使用AnimationSet的插值,如果xml不设,则代码中默认使用加减速插值器
Android 4.0之后增加了duration/fillBefore/fillAfter/repeatMode/startOffset属性是否设置的存储;
注意:以上所有属性在AnimationSet内都存Boolean值(其实是mFlags内存放多个Int MASK),真实值在Animation类中存储。

4.5.2 其他构造方法:
AnimationSet( boolean shareInterpolator):传入是否共享插值器

4.5.3 初始化动画宽高:
public void initialize( int width, int height, int parentWidth, int parentHeight) {
    super .initialize(width, height, parentWidth, parentHeight);

    //是否设置了动画总时长
    boolean durationSet = ( mFlags & PROPERTY_DURATION_MASK ) == PROPERTY_DURATION_MASK ;
    //是否设置了fillAfter属性,后面几条类似
    boolean fillAfterSet = ( mFlags & PROPERTY_FILL_AFTER_MASK ) == PROPERTY_FILL_AFTER_MASK ;
    boolean fillBeforeSet = ( mFlags & PROPERTY_FILL_BEFORE_MASK ) == PROPERTY_FILL_BEFORE_MASK ;
    boolean repeatModeSet = ( mFlags & PROPERTY_REPEAT_MODE_MASK ) == PROPERTY_REPEAT_MODE_MASK ;
    boolean shareInterpolator = ( mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK )
            == PROPERTY_SHARE_INTERPOLATOR_MASK; 
    boolean startOffsetSet = ( mFlags & PROPERTY_START_OFFSET_MASK )
            == PROPERTY_START_OFFSET_MASK;

    //如果shareInterpolator设置了,而外部没有插值器,则使用加速减速插值器
    if (shareInterpolator) {
        ensureInterpolator();
    }

    final ArrayList<Animation> children = mAnimations ;
    final int count = children.size();

    final long duration = mDuration;
    final boolean fillAfter = mFillAfter;
    final boolean fillBefore = mFillBefore;
    final int repeatMode = mRepeatMode;
    final Interpolator interpolator = mInterpolator;
    final long startOffset = mStartOffset;


    long [] storedOffsets = mStoredOffsets;
    if (startOffsetSet) {
        //设置了startOffset,则新建一个子动画个数的storedOffsets,存储各动画的开始执行延迟
        if (storedOffsets == null || storedOffsets.length != count) {
            storedOffsets = mStoredOffsets = new long[count];
        }
    } else if (storedOffsets != null) {
        //如果没设置startOffset,则此处不用保存子动画的延迟
        storedOffsets = mStoredOffsets = null;
    }

    for ( int i = 0; i < count; i++) {
        Animation a = children.get(i);
        if (durationSet) {
            //所有子元素都使用AnimationSet的执行时间
            a.setDuration(duration);
        }
        if (fillAfterSet) {
            //所有子元素都使用AnimationSet的fillAfter属性
            a.setFillAfter(fillAfter);
        }
        if (fillBeforeSet) {
            //所有子元素都使用AnimationSet的fillBefore属性
            a.setFillBefore(fillBefore);
        }
        if (repeatModeSet) {
            ////所有子元素都使用AnimationSet的repeatMode属性
            a.setRepeatMode(repeatMode);
        }
        if (shareInterpolator) {
            //所有子元素都使用AnimationSet的插值器
            a.setInterpolator(interpolator);
        }
        if (startOffsetSet) {
            //所有子元素的延迟时间都加上集合的延迟时间,而子元素的延迟时间存在数组内,留待后面reset时回复子元素的startOffset属性
            long offset = a.getStartOffset();
            a.setStartOffset(offset + startOffset);
            storedOffsets[i] = offset;
        }
        //子动画执行初始化
        a.initialize(width, height, parentWidth, parentHeight);
    }
}

4.5.4 填充转化过程(applyTransformation)
public boolean getTransformation( long currentTime, Transformation t) {
    final int count = mAnimations.size();
    final ArrayList<Animation> animations = mAnimations ;
    final Transformation temp = mTempTransformation ;

    //动画是否还需要执行
    boolean more = false;
    //动画是否已开始
    boolean started = false;
    //动画是否已执行完成
    boolean ended = true;

    t.clear();

    for ( int i = count - 1; i >= 0; --i) {
        final Animation a = animations.get(i);

        temp.clear();
        //应用每个子动画的效果
        more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
        //组成最终的转换
        t.compose(temp);

        //某个子动画start后此值就更新为true
        started = started || a.hasStarted();
        //所有子动画都end后此值才为true
        ended = a.hasEnded() && ended;
    }

    //对外通知动画开始
    if (started && !mStarted) {
        if (mListener != null) {
            mListener.onAnimationStart( this);
        }
        mStarted = true;
    }

    //对外通知动画结束,此方法不通知动画重复,因为动画集合不支持重复,只有子动画通知
    if (ended != mEnded) {
        if (mListener != null) {
            mListener.onAnimationEnd( this);
        }
        mEnded = ended;
    }

    //返回动画是否还在执行
    return more;
}

4.5.5 其他接口
//克隆时需要将动画列表深拷贝
protected AnimationSet clone() throws CloneNotSupportedException {
    final AnimationSet animation = (AnimationSet) super .clone();
    animation.mTempTransformation = new Transformation();
    animation.mAnimations = new ArrayList<Animation>();

    final int count = mAnimations.size();
    final ArrayList<Animation> animations = mAnimations ;

    for ( int i = 0; i < count; i++) {
        animation.mAnimations.add(animations.get(i).clone());
    }

    return animation;
}

public boolean hasAlpha() {
    if ( mDirty) {
        //mDirty本身意义是标识集合中加入了一个Animation,由于加入的Animation可能hasAlpha,所以用mDirty标识我们需要重新遍历列表
        mDirty = mHasAlpha = false ;

        final int count = mAnimations.size();
        final ArrayList<Animation> animations = mAnimations ;
        //遍历子元素来判断是否有Alpha变化
        for ( int i = 0; i < count; i++) { 
            if (animations.get(i).hasAlpha()) {
                mHasAlpha = true ;
                break;
            }
        }
    }

       //如果mDirty为false,则标识列表内动画集合没有发生变化,所以直接返回已经计算好的mHasAlpha,减少性能性能消耗
    return mHasAlpha ;
}

//通常而言,此方法发生在调用start之前
public void addAnimation(Animation a) {
    mAnimations .add(a);

    //新增的动画可能会引起Matrix变化,此处重新计算
    boolean noMatrix = ( mFlags & PROPERTY_MORPH_MATRIX_MASK ) == 0 ;
    if (noMatrix && a.willChangeTransformationMatrix()) {
        mFlags |= PROPERTY_MORPH_MATRIX_MASK;
    }
    //新增的动画可能会改变边界,此处重新计算;changeBounds为true表示现有的动画不引起边界变化
    boolean changeBounds = ( mFlags & PROPERTY_CHANGE_BOUNDS_MASK ) == 0 ;
    if (changeBounds && a.willChangeBounds()) {
        mFlags |= PROPERTY_CHANGE_BOUNDS_MASK; 
    }

    if (( mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK ) {
        //动画设置过总时长,如果子元素也设置了startOffset,此值应当是mStartOffset+mDuration与子元素startOffset的和,但此处忽略了子元素的startOffset
        mLastEnd = mStartOffset + mDuration;
    } else {
        //动画没设置总时长
        if ( mAnimations.size() == 1) {
            //计算动画时长和最终结束时间
            mDuration = a.getStartOffset() + a.getDuration();
            mLastEnd = mStartOffset + mDuration;
        } else {
            //动画最终结束时间是现有最终结束时间与新增动画的执行时间中取较大值
            //这里我认为mLastEnd应当与mStartOffset + a.getStartOffset() + a.getDuration()做比较
            //如果此方法发生在初始化宽高之后,a.getStartOffset就是包含mStartOffset的,如果发生在其之前,此处mLastEnd计算可能不精确
            mLastEnd = Math. max( mLastEnd, a.getStartOffset() + a.getDuration());
            mDuration = mLastEnd - mStartOffset;
        }
    }

    //下次计算hasAlpha时表示新增了动画,重新计算
    mDirty = true;
}

五、总结
本文分析了补间动画的源码,总结了补间动画的实现思路,总体来讲,补间动画就是外部调用方(View)不断的传入真实执行时间,动画根据真实时间计算插值时间,再根据插值时间计算当前位置的转化效果,并应用在外部传入的Transformation对象上,而外部调用方使用此Transformation对View展示进行转化,最终形成插值动画效果。
展开阅读全文

没有更多推荐了,返回首页