源码分析视图动画是如何实现的

我们知道视图动画的启动方式如下:

ImageView image = (ImageView) findViewById(R.id.image);
Animation hyperspaceJump = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
image.startAnimation(hyperspaceJump);

也即从调用view.java类的startAnimation()函数开始,我们先分析如下的函数,drawAnimation()函数是真正通过不断的刷新视图内容绘制一个动态的视图的地方,怎么从startAnimation()到drawAnimation()我们后面再讲,我们的重点是放在视图动画的绘制原理上。


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);//初始化了一个矩形区域,mRight,mLeft,mBottom,mTop是根据该View得到的区域参数
            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
            onAnimationStart();
        }

        boolean more = a.getTransformation(drawingTime, parent.mChildTransformation, 1f);//第一个重要函数,后面源码分析,这里我们关注parent.mChildTransformation这个参数
        if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
            if (parent.mInvalidationTransformation == null) {
                parent.mInvalidationTransformation = new Transformation();
            }
            invalidationTransform = parent.mInvalidationTransformation;
            a.getTransformation(drawingTime, invalidationTransform, 1f);//比较这个if-else,不管走if还是else,我们可以看到它都走了a.getTransformation这个函数。
        } else {
            invalidationTransform = parent.mChildTransformation;//不管是if,还是else,invalidationTransform的值都来自于对getTransformation的调用
        }

        if (more) {
            if (!a.willChangeBounds()) {//除了alphaAnimation,其它的该函数都return true
                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);//对于AlphaAnimation,视图大小没有任何变化
                }
            } else {
                if (parent.mInvalidateRegion == null) {
                    parent.mInvalidateRegion = new RectF();
                }
                final RectF region = parent.mInvalidateRegion;
                a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                        invalidationTransform);//第二个重要函数,举行参数region动态的得到了下一桢的区域大小

                // 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值不断的更新得到新的区域值
                final int top = mTop + (int) region.top;
                parent.invalidate(left, top, left + (int) (region.width() + .5f),//绘制,并刷新下一桢,得到下一个区域大小。
                        top + (int) (region.height() + .5f));
            }
        }
        return more;
    }

通过上面这个函数可以了解到,视图通过不断的刷新并得到Animation提供的视图区域的大小的参数而绘制出动态视图。下面来看看上面提到的两个重要函数,通过这两个函数,可以看出Animation是如何提供动态的区域参数的。

   /**
     * 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.
     *
     * @param currentTime Where we are in the animation. This is wall clock time.
     * @param outTransformation A transformation object that is provided by the
     *        caller and will be filled in by the animation.
     * @return True if the animation is still running
     */

//上面的这段注释清晰的说明了这个函数的功能————在某个指定的时间点获取一个Transformation对象

 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);
        }
...
    }
所有继承自Animation类的函数都需要重写applyTransformation()函数,正是在这个函数中,指定的不同的动画效果所需要的变化。我们以TranslateAnimation的该函数作为示例:

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

Transformation.java类中有两个属性:Matrix和Alpha(float),以及一些改变这两个属性值的方法。上面这里还有一个疑问,就是插值器是如何工作的,留到后面再做解释,我们先看看第二个重要函数,即动态的得到了上面的Transformation对象后是如何得到动态绘制区域的。


    public void getInvalidateRegion(int left, int top, int right, int bottom,
            RectF invalidate, Transformation transformation) {

        final RectF tempRegion = mRegion;
        final RectF previousRegion = mPreviousRegion;

        invalidate.set(left, top, right, bottom);
        transformation.getMatrix().mapRect(invalidate);
        // Enlarge the invalidate region to account for rounding errors
        invalidate.inset(-1.0f, -1.0f);
        tempRegion.set(invalidate);
        invalidate.union(previousRegion);

        previousRegion.set(tempRegion);

        final Transformation tempTransformation = mTransformation;
        final Transformation previousTransformation = mPreviousTransformation;

        tempTransformation.set(transformation);
        transformation.set(previousTransformation);
        previousTransformation.set(tempTransformation);
    }

根据上面这些代码,可以窥见到Animation.java是如何影响到view不停刷新绘制,从而得到动态效果的。下面解释前面的一些疑问:

先来看看插值器的工作原理

public class AccelerateInterpolator implements Interpolator {
    private final float mFactor;
    private final double mDoubleFactor;
    
    public AccelerateInterpolator(Context context, AttributeSet attrs) {
        TypedArray a =
            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);
        
        mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);
        mDoubleFactor = 2 * mFactor;

        a.recycle();
    }
    
    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);//Math.pow(x, y)——计算x的y次方
        }
    }
}
插值器的内容很简单,只需要实现getInterpolation(float input)这个接口即可,这个接口的输入input是根据当前时间得到的一个值,每次刷新得到的值不同,因此输出也就成了一个动态值。因此我们可以得知插值器的原理——输出是时间输入的函数。知晓这个原理,我们就可以编写自己的插值器。下面的内容是前面出现的getTransformation()中的interpolatedTime,也就是input。从下面的这段代码中,我们可以看见它是和当前时间currentTime相关的。
        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;
        }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值