Tween动画原理

1,330 阅读3分钟

Android提供了几种基本的动画:

  1. 帧动画
  2. 补间动画
  3. 属性动画

其中属性动画和补间动画比较常用,它们最大的区别在于补间动画并不会真正的改变View的属性,什么意思呢?比如通过补间动画将页面中的Button从左边移动到右边一段距离,如果此时点击Button,它不会对点击事件做出响应,这是因为button的作用区域(点击该区域依然可以触发click事件)依然在原来的位置,这是补间动画将其绘制在其原来的右边罢了。

本篇将首先从源码的角度对补间动画进行分析,属性动画会在下一篇中做介绍,至于补间动画的基本用法,这里就不多做介绍了。 在我们分析之前我们先提两个问题:

  1. 动画是如何进行绘制的?
  2. 动画是怎么计算每一帧的画面的?即某一个时刻View的位置的?

带着这两个问题,我们开始分析补间动画,这里首先从startAnimation开始分析,这是动画开始执行的方法。

//frameworks\base\core\java\android\view\View.java
public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}

startAnimation中主要做了两件事,第一个就是通过setAnimation设置当前View的动画,然后通过invalidate重绘制View。

 public void setAnimation(Animation animation) {
    mCurrentAnimation = animation;

    if (animation != null) {
        // If the screen is off assume the animation start time is now instead of
        // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
        // would cause the animation to start when the screen turns back on
        if (mAttachInfo != null && !mAttachInfo.mScreenOn &&
                animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
            animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
        }
        animation.reset();
    }
}

setAnimation将animation保存在View的mCurrentAnimation以备使用。 接下来就是通过invalidate来重绘制View,这个我在专门的一篇中有所介绍,这里我简单说一下, invalidate实际上会从当前发起绘制的view开始向上寻找父View(ViewParent),然后和其父View计算总的脏区域,父View再计算根据得到的脏区域计算和其父View的脏区域,这个操作一直到ViewRootImpl,即View层级树的顶部管理者,它会根据计算的脏区域触发Draw操作。也就是会走到performTraversals中,这个流程想必大家都清楚了。

View在绘制完自身的内容后,会通过dispatchDrawl来绘制其子View,动画也就是在此时进行绘制的,

// frameworks/base/core/java/android/view/ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
    final int count = mChildrenCount;//子view的个数
    final View[] children = mChildren;//子view节点
    int flags = mGroupFlags;

    ……
    boolean more = false;
    final long drawingTime = getDrawingTime();

    if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);//绘制子view
            }
        }
    } else {
        for (int i = 0; i < count; i++) {
            final View child = children[getChildDrawingOrder(count, i)];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }
    
   ……
}

 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

dispatchDraw中会通过遍历孩子节点并未其调用drawChild来绘制子View

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ……
    Transformation transformToApply = null;

    final Animation a = getAnimation();
    if (a != null) {
        more = drawAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        transformToApply = parent.getChildTransformation();
    } 
    ……
    if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() ||
        (mPrivateFlags3& PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) {
        if (transformToApply != null || !childHasIdentityMatrix) {
            int transX = 0;
            int transY = 0;

            if (offsetForScroll) {
                transX = -sx;
                transY = -sy;
            }
            //动画的Transformation不为null
            if (transformToApply != null) {
                if (concatMatrix) {
                    if (useDisplayListProperties) {
                        displayList.setAnimationMatrix(transformToApply.getMatrix());
                    } else {
                        // Undo the scroll translation, apply the
                        // transformation matrix,
                        // then redo the scroll translate to get the correct
                        // result.
                        canvas.translate(-transX, -transY);
                        canvas.concat(transformToApply.getMatrix());
                        canvas.translate(transX, transY);
                    }
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                }
                //获取Transformation的alpha值
                float transformAlpha = transformToApply.getAlpha();
                if (transformAlpha < 1) {
                    alpha *= transformAlpha;
                    parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                }
            }

            if (!childHasIdentityMatrix && !useDisplayListProperties) {
                canvas.translate(-transX, -transY);
                canvas.concat(getMatrix());
                canvas.translate(transX, transY);
            }
        }
    }
    return more;
}

View的这个draw方法同draw(Canvas)的那个方法不同,它只是被ViewGroupd的drawChild调用,在这个方法里,我们可以看到绘制动画的方法,首先通过getAnimation获取到我们在startAnimation中设置的动画,然后交给drawAnimation计算动画的Transformation,它被存放在transformToApply中,如果有动画执行那么transformToApply肯定不为null,这里concatMatrix未true表示动画会改变变换矩阵,比如ScaleAnimation会改变变换的Matrix,而AlpahAnimation不会改变,为true这种情况会对canvas做一些列变换。

///frameworks/base/core/java/android/view/View.java
private boolean drawAnimation(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();
    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;
    }

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

这个方法是补间动画的核心处理方法,它首先对动画进行初始化initialize,接下来通过getTransformation计算动画的相关信息,并返回more,代表动画是否完成。如果是true表示还有动画需要执行,那么在最后会通过parent的invalidate重新发起绘制进行下一帧的绘制。

 public boolean getTransformation(long currentTime, Transformation outTransformation,
        float scale) {
    mScaleFactor = scale;
    return getTransformation(currentTime, outTransformation);
}

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

    final long startOffset = getStartOffset();
    final long duration = mDuration;
    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;
    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 (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(interpolatedTime, outTransformation);
    }

    if (expired) {
        if (mRepeatCount == mRepeated) {
            if (!mEnded) {
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
            if (mRepeatCount > 0) {
                mRepeated++;
            }

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

            mStartTime = -1;
            mMore = true;

            fireAnimationRepeat();
        }
    }

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

    return mMore;
}

getTransformation会根据当前时间和动画的持续时间执行时间流逝的进度,该进度随时间的流逝均匀的增加,如果该进度在0和1之间那么说明动画还在执行,如果动画此时还未开始会通过fireAnimationStart回调动画开始的回调,随后通过getInterpolation根据进度和插值器计算插值,插值和时间流逝的进度区别在于,插值可以根据数学模型生成任意的值,而时间流逝的进度值是客观不可变的。补间动画正是基于计算的插值来计算下一帧的动画的。计算完插值后通过applyTransformation来计算动画。我们可以看看TranslateAnimation是如何基于此计算动画的。

@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);
    }
    t.getMatrix().setTranslate(dx, dy);
}

TranslateAnimation都是基于interpolatedTime计算动画偏移。如水平方向的偏移 dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime) 这里的interpolatedTime可以看作是插值器计算的动画进度,它从0f-1.0f。

完成这一步,就可以通过view的父view发起绘制动画的请求,这是通过parent.invalidate方法来发起的。