天天看点

从屏幕脉冲角度彻底理解属性动画原理

一.有个细节,就是设置不同差值器或者估值的,可以定义一个接口,不同的差值器比如线性插值器估值器,定义好,在主要也就是上层实现动画的时候,直接调用不同的差值器就行了,因为上层的需求就是一个插值器,调用的设置不同的差值器就行了,面向接口编程。

二.整个动画原理流程:

1.先明白2个概念,帧刷新率和屏幕刷新率,帧刷新率相当于在咱的应用中,经过cpu执行测量,布局和绘制流程,对图像转换成文理,再经过gpu对图像渲染,其实就是放到一个缓冲区中,这个时候还没有显示;

显示就需要用到屏幕刷新率了,屏幕刷新率也就是屏幕每隔16ms发送一个脉冲信号,只有vsync脉冲信号到了,才会去缓冲区拿显示的那些数据来显示。

下边正式开始:

首先,开始动画,

private void scheduleAnimation() {

    if (!mAnimationScheduled) {

mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);

        mAnimationScheduled = true;

    }

}

发送一个同步信号,这个同步信号会在底层进行注册,有个信号回调,代表下一帧也就是底层的屏幕刷新脉冲信号也就是16ms调用一次的机制,会回调我注册的一个回调监听,记住,发送一个这个同步信号,只是一次有效,这个也没什么用,像动画的这个回调里,就是动画机制自己处理了,只是使用了脉冲的回调的时间,

scheduleVsyncLocked

下边的及其核心

public void scheduleVsync() {

    if (mReceiverPtr == 0) {

        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "

                + "receiver has already been disposed.");

    } else {

        nativeScheduleVsync(mReceiverPtr);

    }

}

仔细看英文,就是发送一个垂直同步信号,当下一帧显示的时候开始的时候,会回调。

当scheduleAnimation调用了以后,会在下一个16ms回调animationHandler的run方法,

// Called by the Choreographer.

@Override

public void run() {

    mAnimationScheduled = false;

    doAnimationFrame(mChoreographer.getFrameTime());

}

继续看注释,这个回调,是在编舞者的脉冲回来的时候调用的,间隔了16ms才调用的,

private void doAnimationFrame(long frameTime) {

    // mPendingAnimations holds any animations that have requested to be started

    // We're going to clear mPendingAnimations, but starting animation may

    // cause more to be added to the pending list (for example, if one animation

    // starting triggers another starting). So we loop until mPendingAnimations

    // is empty.

    while (mPendingAnimations.size() > 0) {

        ArrayList<ValueAnimator> pendingCopy =

                (ArrayList<ValueAnimator>) mPendingAnimations.clone();

        mPendingAnimations.clear();

        int count = pendingCopy.size();

        for (int i = 0; i < count; ++i) {

            ValueAnimator anim = pendingCopy.get(i);

            // If the animation has a startDelay, place it on the delayed list

            if (anim.mStartDelay == 0) {

                anim.startAnimation(this);

            } else {

                mDelayedAnims.add(anim);

            }

        }

    }

    // Next, process animations currently sitting on the delayed queue, adding

    // them to the active animations if they are ready

    int numDelayedAnims = mDelayedAnims.size();

    for (int i = 0; i < numDelayedAnims; ++i) {

        ValueAnimator anim = mDelayedAnims.get(i);

        if (anim.delayedAnimationFrame(frameTime)) {

            mReadyAnims.add(anim);

        }

    }

    int numReadyAnims = mReadyAnims.size();

    if (numReadyAnims > 0) {

        for (int i = 0; i < numReadyAnims; ++i) {

            ValueAnimator anim = mReadyAnims.get(i);

            anim.startAnimation(this);

            anim.mRunning = true;

            mDelayedAnims.remove(anim);

        }

        mReadyAnims.clear();

    }

    // Now process all active animations. The return value from animationFrame()

    // tells the handler whether it should now be ended

    int numAnims = mAnimations.size();

    for (int i = 0; i < numAnims; ++i) {

        mTmpAnimations.add(mAnimations.get(i));

    }

    for (int i = 0; i < numAnims; ++i) {

        ValueAnimator anim = mTmpAnimations.get(i);

        if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {

            mEndingAnims.add(anim);

        }

    }

    mTmpAnimations.clear();

    if (mEndingAnims.size() > 0) {

        for (int i = 0; i < mEndingAnims.size(); ++i) {

            mEndingAnims.get(i).endAnimation(this);

        }

        mEndingAnims.clear();

    }

    // If there are still active or delayed animations, schedule a future call to

    // onAnimate to process the next frame of the animations.

    if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {

        scheduleAnimation();

    }

}

当脉冲回来了,会走run的回调,会调用这个处理动画的逻辑,这个逻辑有两个地方需要记住的:

1.anim.doAnimationFrame,循环遍历已经准备好的动画,根据时间流逝的百分比,来设置属性值,也就达到了16ms设置一次动画值的效果和目的和流程。

2.scheduleAnimation,如果还有动画,在处理完动画以后,还会继续调用开始动画,也就是发送一个动画的脉冲,这样就相当于每隔16ms调用一次run的doAnimationFrame了,处理动画。

三.细节,计算动画的值部分,理解keyframe

根据时间,计算出来百分比,时间的流逝,在前边的poertryholder里,保存了每个关键帧的比例和时间,比如(0,100,200),keyframe的key的百分比,就是0,二分之一,1,还有对应的0,100,200。

而此时动画的值,就是时间流逝的百分比*前后帧值的间隔。

这个就是关键帧的计算法:

比如前一帧和下一帧,fraction代表当下的百分比,前一帧的开始百分比,下一帧的百分比值,所以求出来的结果值就是现在的百分比减去前一帧的百分比代表间隔,除以后帧减去前帧,计算出来的结果就是百分比,再乘以前帧和后帧的值的差值,就是当前的值。

public Object getValue(float fraction) {

    // Special-case optimization for the common case of only two keyframes

    if (mNumKeyframes == 2) {

        if (mInterpolator != null) {

            fraction = mInterpolator.getInterpolation(fraction);

        }

        return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),

                mLastKeyframe.getValue());

    }

    if (fraction <= 0f) {

        final Keyframe nextKeyframe = mKeyframes.get(1);

        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();

        if (interpolator != null) {

            fraction = interpolator.getInterpolation(fraction);

        }

        final float prevFraction = mFirstKeyframe.getFraction();

        float intervalFraction = (fraction - prevFraction) /

            (nextKeyframe.getFraction() - prevFraction);

        return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),

                nextKeyframe.getValue());

    } else if (fraction >= 1f) {

        final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);

        final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();

        if (interpolator != null) {

            fraction = interpolator.getInterpolation(fraction);

        }

        final float prevFraction = prevKeyframe.getFraction();

        float intervalFraction = (fraction - prevFraction) /

            (mLastKeyframe.getFraction() - prevFraction);

        return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),

                mLastKeyframe.getValue());

    }

    Keyframe prevKeyframe = mFirstKeyframe;

    for (int i = 1; i < mNumKeyframes; ++i) {

        Keyframe nextKeyframe = mKeyframes.get(i);

        if (fraction < nextKeyframe.getFraction()) {

            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();

            if (interpolator != null) {

                fraction = interpolator.getInterpolation(fraction);

            }

            final float prevFraction = prevKeyframe.getFraction();

            float intervalFraction = (fraction - prevFraction) /

                (nextKeyframe.getFraction() - prevFraction);

这个就是关键帧的计算法:

比如前一帧和下一帧,fraction代表当下的百分比,前一帧的开始百分比,下一帧的百分比值,所以求出来的结果值就是现在的百分比减去前一帧的百分比代表间隔,除以后帧减去前帧,计算出来的结果就是百分比,再乘以前帧和后帧的值的差值,就是当前的值。

            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),

                    nextKeyframe.getValue());

        }

        prevKeyframe = nextKeyframe;

    }

    // shouldn't reach here

    return mLastKeyframe.getValue();

}