设计之妙,理解Android动画流程

2023-12-16

本文基于Android 12进行学习研究,参考《深入理解Android内核源码》思路学习总结,如有理解不对,望各位朋友指正,另外可能留存一些疑问,留后续再探索。输出只是为了分享和提升自己。

动画初始化

按照窗口管理策略类中的定义,动画应该被分为四种类型。

  • TRANSIT_ENTER :窗口进入。
  • TRANSIT_EXIT :窗口移除。
  • TRANSIT_SHOW :窗口可见。
  • TRANSIT_HIDE :窗口隐藏。

TRANSIT_PREVIEW_DONE 其实不算是动画,而是一个标志启动窗口完成的标志位。在 WMS relayoutWindow 函数,会根据窗口状态判断是否要执行动画。下面代码则是判断动画的起始点。

//仅有在窗口在可见状态下且是一个启动窗口类型或者关联的appToken不隐藏时才会进行重新布局
final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
        (win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
                || win.mActivityRecord.isClientVisible());
//当前不需要重新布局、也已经有Surface,且不是在执行退出动画,则需要考虑是否执行动画
if (!shouldRelayout && winAnimator.hasSurface() && !win.mAnimatingExit) {
    result |= RELAYOUT_RES_SURFACE_CHANGED;
    if (!win.mWillReplaceWindow) {
		
        ......
        //尝试开始或者退出动画    
        focusMayChange = tryStartExitingAnimation(win, winAnimator, focusMayChange);
    }
}

在这里插入图片描述

tryStartExitingAnimation 调用了 WindowStateAnimator 类型对象 winAnimator applyAnimationLocked 函数执行动画。

private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator,boolean focusMayChange) {
    int transit = WindowManagerPolicy.TRANSIT_EXIT;
    //属性类型是应用窗口
    if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
      //启动窗口完成
        transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
    }
//窗口处于动画变换
     if (win.inTransition()) {
        focusMayChange = true;
        win.mAnimatingExit = true;
    } else if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
        String reason = null;
        //执行动画,如果未执行动画或动画失败,则释放相关资源
        if (winAnimator.applyAnimationLocked(transit, false)) {
            reason = "applyAnimation";
            focusMayChange = true;
            win.mAnimatingExit = true;
        } else if (
                win.isSelfAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION)
                || win.isAnimating(PARENTS | TRANSITION,ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)|| (win.inTransition()
            reason = "animating";
            win.mAnimatingExit = true;
        } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
                && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
            reason = "isWallpaperTarget";
            win.mAnimatingExit = true;
        }
    .....
        }
    }
    //资源释放
    if (!win.mAnimatingExit) {
        boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
        win.mDestroying = true;
        win.destroySurface(false, stopped);
    }
    if (mAccessibilityController.hasCallbacks()) {
        mAccessibilityController.onWindowTransition(win, transit);
    }

    return focusMayChange;
}

applyAnimationLocked 函数选择合适的动画类型,并设置给 WindowState ,这里也是 判断是否有设置动画的地方。如果设置了动画,调用 WindowState startAnimation 函数。

boolean applyAnimationLocked(int transit, boolean isEntrance) {
  //避免加载相同动画两次
    if (mWin.isAnimating() && mAnimationIsEntrance == isEntrance) {
        return true;
    }

    if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
        mWin.getDisplayContent().adjustForImeIfNeeded();
        if (isEntrance) {
            mWin.setDisplayLayoutNeeded();
            mService.mWindowPlacerLocked.requestTraversal();
        }
    }

    if (mWin.mControllableInsetProvider != null) {
        // All our animations should be driven by the insets control target.
        return false;
    }

    // 如果显示处于 frozen状态,则不执行动画
    if (mWin.mToken.okToAnimate()) {
        int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
        int attr = -1;
        Animation a = null;
        //anim不等于ANIMATION_STYLEABLE,表示给窗口指定了动画
        if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {//anim不等于0,表示指定了动画
            if (anim != DisplayPolicy.ANIMATION_NONE) {
                //加载动画资源
                a = AnimationUtils.loadAnimation(mContext, anim);
            }
        } else {
          //根据动画类型,选择默认的资源文件
            switch (transit) {
                case WindowManagerPolicy.TRANSIT_ENTER:
                    attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                    break;
                case WindowManagerPolicy.TRANSIT_EXIT:
                    attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                    break;
                case WindowManagerPolicy.TRANSIT_SHOW:
                    attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                    break;
                case WindowManagerPolicy.TRANSIT_HIDE:
                    attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                    break;
            }
    //加载动画资源
            if (attr >= 0) {
                a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
                        mWin.mAttrs, attr, TRANSIT_OLD_NONE);
            }
        }
        if (a != null) {
            mWin.startAnimation(a);//获取到动画资源,开始执行动画Animation对象
            mAnimationIsEntrance = isEntrance;
        }
    } else {
        mWin.cancelAnimation();
    }

    return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}

WindowState 在WMS表示一个窗口对象, startAnimation 函数对动画anim进行进行初始化,封装成 AnimationAdpater 类型的 adapter 对象,并调用父类 WindowContainer startAnimation 函数。 WindowContainer 定义了窗口和窗口层级之间一些通用接口。 WindowContainer 用来描述动画和联系相关联的组件,以及执行动画的。

注意到这里 adpter 实际类型是 LocalAnimationAdapter ,表示该动画不需要持有 WindowManager 锁。重点是其构造函数的入参 WindowAnimationSpec SurfaceAnimationRunner 对象,是后续监听VSYNC进行属性计算的地方。

void startAnimation(Animation anim) {
    if (mControllableInsetProvider != null) {
        return;
    }
    final DisplayInfo displayInfo = getDisplayInfo();
    //初始化宽高
    anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
            displayInfo.appWidth, displayInfo.appHeight);
    anim.restrictDuration(MAX_ANIMATION_DURATION);
    anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
	//SurfaceAnimationRunner类型的 mWmService.mSurfaceAnimationRunner
    final AnimationAdapter adapter = new LocalAnimationAdapter(
            new WindowAnimationSpec(anim, mSurfacePosition, false,
                    0),
            mWmService.mSurfaceAnimationRunner);
    //getPendingTransaction分析
    startAnimation(getPengetPendingTransaction分析dingTransaction(), adapter);
    commitPendingTransaction();
}

WindowContainer.startAnimation 函数又调用了 SurfaceAnimator.startAnimation SurfaceAnimator 主要是将一组子 Surface 通过约束重置为新的 Surface ,称为 Leach ,然后交由 AnimationAdapter 执行动画。

// mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION
//animationFinishedCallback、animationCancelledCallback、snapshotAnim此时为null
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
        @AnimationType int type,
        @Nullable OnAnimationFinishedCallback animationFinishedCallback,
        @Nullable Runnable animationCancelledCallback,
        @Nullable AnimationAdapter snapshotAnim) {
    mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
            animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}

startAnimation 建立动画链成功之后,就会调用入参 anim 执行动画。 hidden 表示 WindowContainer 当前持有 Surface 是否可见。按前面分析,此时 startAnimation 只有前面四个参数有值。三种动画:Surface、adapter、snapshot。

void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
        @AnimationType int type,
        @Nullable OnAnimationFinishedCallback animationFinishedCallback,
        @Nullable Runnable animationCancelledCallback,
        @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
   //强制取消与t相同类型的动画,并重新执行
   cancelAnimation(t, true /* restarting */, true /* forwardCancel */);

    mAnimation = anim;
    mAnimationType = type;
    mSurfaceAnimationFinishedCallback = animationFinishedCallback;
    mAnimationCancelledCallback = animationCancelledCallback;

    final SurfaceControl surface = mAnimatable.getSurfaceControl();
    if (surface == null) {
        cancelAnimation();
        return;
    }
    //freezer==null
    mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
    if (mLeash == null) {
      //创建动画链(SurfaceController),同时也会设置给t
        mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                0 /* y */, hidden, mService.mTransactionFactory);
        mAnimatable.onAnimationLeashCreated(t, mLeash);
    }
    //开始动画链
    mAnimatable.onLeashAnimationStarting(t, mLeash);

    if (mAnimationStartDelayed) {
        return;
    }
    //此时执行AnimationAdapter对象mAnimation动画,mInnerAnimationFinishedCallback是WC的监听
    mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
   //开始snapshot动画,此时snapshotAnim==null
   if (snapshotAnim != null) {
        mSnapshot = freezer.takeSnapshotForAnimation();
        if (mSnapshot == null) {
            return;
        }
        mSnapshot.startAnimation(t, snapshotAnim, type);
    }
}

按前面的分析,这里 mAnimation 的实际类型是 LocalAnimationAdapter 。其 startAnimation 又调用了 SurfaceAnimationRunner 对象的 startAnimation 函数。

先是将动画相关内容封装成 RunningAnimation 对象,并存放到 mPendingAnimations 中。

然后向编舞者 Choreographer 注册了 VSYNC 监听,这样当信号来临时,会调用 startAnimations 函数。

image-20231206160556897

void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
        Runnable finishCallback) {
    synchronized (mLock) {
        final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                finishCallback);
        //这样帧信号来临时,会执行该Map中所有动画
        mPendingAnimations.put(animationLeash, runningAnim);
        //如果当前动画部延迟执行,则立即向Choreographer注册垂直信号监听
        //这样垂直信号来临时,执行startAnimations,存在一定延迟
        if (!mAnimationStartDeferred) {
            mChoreographer.postFrameCallback(this::startAnimations);
        }
        //一些动画需要初始并应用一些变换
        applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SurfaceAnimationRunner startAnimations 函数。

private void startAnimations(long frameTimeNanos) {
    synchronized (mLock) {
        startPendingAnimationsLocked();
    }
    mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
}

遍历 mPendingAnimations 中所有等待帧信号来临的 RunningAnimation 对象,调用 startAnimationLocked 执行它们。

private void startPendingAnimationsLocked() {
    for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
        startAnimationLocked(mPendingAnimations.valueAt(i));
    }
    mPendingAnimations.clear();
}

SurfaceAnimatorRunner 对象, startAnimationLocked 主要为每个 RunningAnimation 动画创建 ValueAnimator 对象并设置相关参数,并在 AnimatorUpdateListener 回调中计算动画应用对象的相关属性。

@GuardedBy("mLock")
private void startAnimationLocked(RunningAnimation a) {
    final ValueAnimator anim = mAnimatorFactory.makeAnimator();
    //设置动画相关参数
    anim.overrideDurationScale(1.0f);
    anim.setDuration(a.mAnimSpec.getDuration());
    //更改动画对象相关属性
    anim.addUpdateListener(animation -> {
        synchronized (mCancelLock) {
            if (!a.mCancelled) {
                final long duration = anim.getDuration();
                long currentPlayTime = anim.getCurrentPlayTime();
                if (currentPlayTime > duration) {
                    currentPlayTime = duration;
                }
                //每次更改相关动画属性
                applyTransformation(a, mFrameTransaction, currentPlayTime);
            }
        }

        scheduleApplyTransaction();
    });
	//anim开始后,会回调此处。
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    mFrameTransaction.setAlpha(a.mLeash, 1);
                }
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            synchronized (mLock) {
                mRunningAnimations.remove(a.mLeash);
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        mAnimationThreadHandler.post(a.mFinishCallback);
                    }
                }
            }
        }
    });
    a.mAnim = anim;
    mRunningAnimations.put(a.mLeash, a);
	//调用ValueAnimator的start函数。
    anim.start();
    if (a.mAnimSpec.canSkipFirstFrame()) {
        anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
    }
	//手动触发一帧动画属性
    anim.doAnimationFrame(mChoreographer.getFrameTime());
}

ValueAnimator.start=>start(false), 该函数主要 ValueAnimator 对象对一些变量进行初始化。其中调用了 addAnimationCallback(0) AnimationHandler 注册帧信号的监听。

private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;//此时false,表示为正向动画
    mSelfPulse = !mSuppressSelfPulseRequested;
    //判断是否快进到某个时间点
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            // Calculate the fraction of the current iteration.
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;

    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        //内部初始化动画对象所应用的属性,和回调开始监听
        startAnimation();
        //初始化mStartTime
        if (mSeekFraction == -1) {
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

AnimationHandler 在线程中静态单例的,在默认情况下,向编舞者 Choreographer 注册并接收其帧信号(垂直信号)回调, 向所有的动画 ValueAnimator 对象发送刷新帧的时间,这样它们在同一个线程以相同的时间执行各自窗口属性值的计算,达到视觉动画效果。 AnimationHandler 也为我们提供 设置类似编舞者提供周期信号的 接口,方便测试和特定功能开发。

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
	//注册垂直信号监听
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    //避免重复添加
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
	//延时执行
    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}
//这样垂直信号来临,会调用doFrame函数
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};

动画触发

image-20231206164042870

AnimationHandler 默认情况会向编舞者 Choreographer 注册垂直信号( VSYNC )监听,这样当垂直信号来临时,就回调 mFrameCallback 对象 doFrame 函数。

doFrame 函数获取当前帧时间并传递给 doAnimationFrame 函数,如果执行完之后,还有动画没有执行完成则再次监听VSYNC。所以在 doAnimationFrame 函数中执行动画结束后肯定将 AnimationFrameCallback 对象从 mAnimationCallbacks 移除。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};

doAnimationFrame 函数遍历 mAnimationCallbacks 中所有 AnimationFrameCallback 对象,判断其是否达到开始执行动画(部分动画可能设置延时执行),到达开始时间,则调用它们的 doAnimationFrame 函数。

private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
        if (callback == null) {
            continue;
        }
        //判断动画是否达到开始执行时间
        if (isCallbackDue(callback, currentTime)) {
            callback.doAnimationFrame(frameTime);//回调AnimationFrameCallback,进行逻辑处理
            if (mCommitCallbacks.contains(callback)) {//查看在哪里注册到该map,延迟大的时候
                getProvider().postCommitCallback(new Runnable() {
                    @Override
                    public void run() {
                        //延时问题
                        commitAnimationFrame(callback, getProvider().getFrameTime());
                    }
                });
            }
        }
    }
    cleanUpList();
}

ValueAnimator 对象 doAnimationFrame 函数。在 VSYNC 来临时,需要根据一些情况调整动画的开始时间 mStartTime 。动画未开始、恢复动画、暂停动画、动画快进、动画延迟执行都会调整动画的开始时间。如果此时动画未开始,此帧时间则是开始时间,或者开始倒计时的时间(动画设置延时执行)。如果当前是恢复动画,开始时间则更改为当前时间节点+已暂停时间(mStartTime+elapseTime),而不是停留在上次动画的帧时间。这里可能会理解为从继续上次暂停的动画效果,然后继续执行后续动画。如果此时还是第一帧时间,还需要判断是否存在动画快进的情况。

最后调用 animateBasedOnTime 函数计算此时此刻动画的相关属性值。参数为帧时间(开始之后)或者开始时间(刚开始)。

public final boolean doAnimationFrame(long frameTime) {
  //动画刚初始化,mStartTime=-1,未开始
    if (mStartTime < 0) {
        // 第一帧动画时间,如果有延迟开始动画,此时开始倒计时
        mStartTime = mReversing
                ? frameTime
                : frameTime + (long) (mStartDelay * resolveDurationScale());
    }


    if (mPaused) {//暂停
        mPauseTime = frameTime;
        removeAnimationCallback();
        return false;
    } else if (mResumed) {//恢复
        mResumed = false;
        if (mPauseTime > 0) {
            //恢复并不是从上次动画进度节点恢复,而是以当前帧时间为开始
            mStartTime += (frameTime - mPauseTime);
        }
    }

    if (!mRunning) {
  //动画开始时间未到,并且没有快进
        if (mStartTime > frameTime && mSeekFraction == -1) {
            return false;
        } else {
            mRunning = true;
    //初始化ValueAnimator中各个属性值
            startAnimation();
        }
    }

    if (mLastFrameTime < 0) {//此次第一帧时间
        if (mSeekFraction >= 0) {
            long seekTime = (long) (getScaledDuration() * mSeekFraction);
            mStartTime = frameTime - seekTime;
            mSeekFraction = -1;
        }
        mStartTimeCommitted = false; // allow start time to be compensated for jank
    }
    mLastFrameTime = frameTime;
    //避免当前帧时间比开始时间早的问题
    final long currentTime = Math.max(frameTime, mStartTime);
//计算属性值
    boolean finished = animateBasedOnTime(currentTime);

    if (finished) {
        endAnimation();//移除FrameCallback监听,通知已注册监听
    }
    return finished;
}

我们知道动画是可以指定重复次数或无限循环的。所以 animateBasedOnTime 函数计算出本次帧时间来临时,本次动画进度在整体进度情况 fraction 。0表示刚开始执行动画;0-1表示第一次动画某个时间点;等于1表示刚好执行完第一次动画;大于1则表示开始循环动画。当大于1时,正数部分表示第几次重复动画,而小数部分表示本次动画的时间节点;正数部分如果大于上次fraction,则表示是新的循环。

这个函数有很多设计妙处。

boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        //获取持续时间
        final long scaledDuration = getScaledDuration();
        //currentTime - mStartTime表示动画已执行完的时间长度,等于0表示刚开始,大于0,说明要跳过这部分时间。
        //(float)(currentTime - mStartTime) / scaledDuration大于1表示已经开始重复执行动画了,0-1则是第一次。1刚好第一次动画结束
        final float fraction = scaledDuration > 0 ?
                (float)(currentTime - mStartTime) / scaledDuration : 1f;
        final float lastFraction = mOverallFraction;
        //fraction在整型比上一次大,则判断为新一次循环,相等判断为同一次动画内的不同阶段
        final boolean newIteration = (int) fraction > (int) lastFraction;
        //判断是否最后一次动画循环:在有限循环次数内,本次动画等于或超出设置的总重复次数
        final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                (mRepeatCount != INFINITE);
        if (scaledDuration == 0) {
            //持续时间为0的,直接结束
            done = true;
        } else if (newIteration && !lastIterationFinished) {
            // 当前属于新的迭代次数,且还没有结束动画
            if (mListeners != null) {
                int numListeners = mListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mListeners.get(i).onAnimationRepeat(this);
                }
            }
        } else if (lastIterationFinished) {
            done = true;//当前属于最后一次迭代
        }
        mOverallFraction = clampFraction(fraction);//缓存本次进度节点
        //先计算本次动画是第几次iteration
        //再取小数部分curFraction=fraction-iteration,根据是否反向动画,确定最终的进度curFraction or 1-curFraction
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        animateValue(currentIterationFraction);//通过插值器计算动画属性值
    }
    return done;
}

animateValue 函数以本次动画时间进度节点,通过时间插值器变换进度节点,然后通知属性数组 mValues 中的对象,让它们根据变换后 fraction ,转化具体的属性值。然后回调 UpdateListener 对象的 onAnimationUpdate 函数。

void animateValue(float fraction) {
	//插值器变换fraction
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
    	//各个属性根据fraction转化自己的值
        mValues[i].calculateValue(fraction);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
        	//回调通知
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}

这就回到了 SurfaceAnimatorRunner 对像 startAnimationLocked 函数给 ValueAnimator 设置监听的地方。

private void startAnimationLocked(RunningAnimation a) {
    ......
    //帧动画通过valueAnimator计算产生后回调此处
    anim.addUpdateListener(animation -> {
        synchronized (mCancelLock) {
            if (!a.mCancelled) {
                final long duration = anim.getDuration();
                long currentPlayTime = anim.getCurrentPlayTime();
                if (currentPlayTime > duration) {//执行结束
                    currentPlayTime = duration;
                }
                //动画属性计算
                applyTransformation(a, mFrameTransaction, currentPlayTime);
            }
        }

        // Transaction will be applied in the commit phase.
        scheduleApplyTransaction();
    });

    ......

}

applyTransformation 函数内部有调用了 WindowAnimationSpec 对象的 apply 函数。

private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
    a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
}

apply 函数获取线程本地 TmpValues 对象,将相关属性计算结果写到其 Transformation 类型的 transformation 成员变量。 Transformation 类封装 Matrix 、裁剪、透明度、动画类型。而 Matrix 可以对对象进行缩放、平移、和旋转。也就是说, Transformation 类型囊括了四个动画类型缩放、平移、旋转、透明度,代表了动画某刻要应用的变换。

public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
    final TmpValues tmp = mThreadLocalTmps.get();
    //重置transformation
    tmp.transformation.clear();
    //计算某个时刻的动画属性,并将结果写会tmp.transformation
    mAnimation.getTransformation(currentPlayTime, tmp.transformation);
    //位移
    tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
    //旋转、缩放
    t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
    //透明度
    t.setAlpha(leash, tmp.transformation.getAlpha());

    boolean cropSet = false;
    if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
        if (tmp.transformation.hasClipRect()) {
            t.setWindowCrop(leash, tmp.transformation.getClipRect());
            cropSet = true;
        }
    } else {
        mTmpRect.set(mRootTaskBounds);
        if (tmp.transformation.hasClipRect()) {
            mTmpRect.intersect(tmp.transformation.getClipRect());
        }
        t.setWindowCrop(leash, mTmpRect);
        cropSet = true;
    }

    //设置圆角
    if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) {
        t.setCornerRadius(leash, mWindowCornerRadius);
    }
}

代码中调用了 Animation getTransformation 函数,主要是计算动画的状态,是否开始、运行、结束。如果动画还是处于运行状态,则调用 getTransformationAt 函数。

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) {
		//计算当前所处的位置0
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        //未开始或者结束了
        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");
            }
        }
		//normalized在0f~1f之间
        if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
		//此时是否反向动画
        if (mCycleFlip) {
            normalizedTime = 1.0f - normalizedTime;
        }
		//将动画属性缓冲到outTransformation
        getTransformationAt(normalizedTime, outTransformation);
    }
	
	//结束了,但是可能循环动画
    if (expired) {
        if (mRepeatCount == mRepeated || isCanceled()) {//动画结束
            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;
}

getTransformationAt 函数内部又调用 Animation applyTransformation 函数。这里的 Animation 子类会重写该函数,实现自己的特定属性计算。 Animation 子类可以是在主题或者指定的 Animation 为准,如前面由系统默认指定的动画。

public void getTransformationAt(float normalizedTime, Transformation outTransformation) {
    final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
    applyTransformation(interpolatedTime, outTransformation);
}

动画的展示

回到了 SurfaceAnimatorRunner 对像 startAnimationLocked 函数给 ValueAnimator 设置监听的地方。在上面执行了applyTransformation函数之后,调用了applyTransformation。

private final Runnable mApplyTransactionRunnable = this::applyTransaction;

private void scheduleApplyTransaction() {
    if (!mApplyScheduled) {
        mChoreographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable,
                null /* token */);
        mApplyScheduled = true;
    }
}

private void applyTransaction() {
    mFrameTransaction.setAnimationTransaction();
    mFrameTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
    mFrameTransaction.apply();
    mApplyScheduled = false;
}

根据前面mSurfaceAnimationRunner对象来自WMS的成员变量,其在WMS创建的时候被创建,Transaction类型的mFrameTransaction对象以同步方式设置SurfaceController。applyTransaction函数调用mFrameTransaction对象的几个方法,内部都调用底层代码去设置Surface。这里不再进一步跟进。

总结

通过本文对动画源码的学习,了解在动画上面对Float浮点数巧妙运用,用来合理控制动画的重复行为和单次进度。

同时也了解到动画在底层也是通过ValueAnimator来计算动画的各个属性 ,因此进一步确定原理,动画就是在指定的时间内播放一帧帧图像,根据人眼对图像的感知特色,从而形成动效。

另外,动画会以垂直信号来初始化动画和触发动画的开始。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓ (文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计之妙,理解Android动画流程 的相关文章

  • 如何在 Android 中更改 Drawable 的颜色?

    我正在开发一个 Android 应用程序 并且我有一个从源图像加载的可绘制对象 在此图像上 我想将所有白色像素转换为不同的颜色 例如蓝色 然后缓存生成的 Drawable 对象 以便稍后使用它 举例来说 假设我有一个 20x20 PNG 文
  • Android:初始化本机 AudioRecord 对象时 AudioRecord 错误代码 -20

    Android 我想从麦克风读取缓冲区 以便我可以对其执行处理 以下是我的代码 int sampleRateInHz 8000 44100 22050 and 11025 int channelConfig AudioFormat CHAN
  • Android 图表[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在开发一个项目 其中有一些图表 图形 刻度图 烛台图和范围图 但问题是 没有该图表的库 我有烛台图的
  • socket.io xhr 在连接缓慢时出现错误(3G 移动网络)

    当我在 3G 移动网络 互联网连接速度慢 上测试我的真实聊天应用程序时 Socket io反复断开然后重新连接 我已经记录了原因 它说 xhr post error 这提高了 transport error 然后断开连接 我可以知道什么意思
  • 改造中的多个队列导致内存不足错误?

    我正在使用retrofit2 做我的项目 当我的呼叫失败时 我再次重复相同的呼叫 重复此 呼叫使我的应用程序强制关闭 当我查看日志时 我得到了错误日志 如下所示 我觉得这是由于同一呼叫的多次排队造成的 所以我在排队之前就这样做了 我打电话给
  • 当路径的点超出视野时,Android Canvas 不会绘制路径

    我在绘制路径时遇到了 Android Canvas 的一些问题 我的情况是 我有一个相对布局工作 如地图视图 不使用 google api 或类似的东西 我必须在该视图上绘制一条路径 canvas drawPath polyPath bor
  • 华为手机“受保护的应用程序”设置及处理方法

    我有一台搭载 Android 5 0 的华为 P8 用于测试应用程序 该应用程序需要在后台运行 因为它跟踪 BLE 区域 我发现华为内置了一个名为 受保护的应用程序 的 功能 可以从手机设置 电池管理器 gt 受保护的应用程序 访问该功能
  • HMS 核心地图套件在我的 Android 应用程序上根本无法工作

    我正在尝试在我的应用程序中使用华为 HMS 地图套件 我对整体地图很陌生 无论是来自谷歌还是华为 我按照文档中的教程以及华为提供的代码实验室中的说明进行操作 并将我的代码在一起 但是当我运行地图活动时 什么也没有出现 我得到的只是一个空白活
  • React Native Expo StackNavigator 重叠通知栏

    我正在尝试为我的 React Native Expo 应用程序实现导航栏 这里有一个问题 dependencies expo 18 0 3 react 16 0 0 alpha 12 react native 0 45 1 react na
  • 透明 9patch 图像:显示出线条

    我得到了一个透明的 9 补丁图像 其中有 9 条补丁线显示槽 This is the output 显然我不希望水平线可见 这就是我创建 9patch 的方式 This is the final image that is used in
  • 将 java 中的 byte[] 转换为 C++ 中的 unsigned char* 的正确方法,反之亦然?

    我是 C 和 JNI 的新手 我尝试找到一种正确的方法 通过使用 JNI 将 java 中的 byte 转换为 C 中的 unsigned char 反之亦然 我正在安卓上工作 在谷歌和SO中寻找解决方案后 我还没有找到将java中的byt
  • 如何使用 SharedPreferences 保存多个值?

    我正在开发一个字典应用程序 在我的应用程序中 我假设用户想要保存最喜欢的单词 我决定使用共享首选项保存这些值 我知道 SQLite 和文件更好 但我坚持使用 SharedPreferences 所以继续使用它 下面是我的代码 Overrid
  • 当 minifyEnabled 为 true 时 Android 应用程序崩溃

    我正在使用多模块应用程序 并且该应用程序崩溃时minifyEnabled true in the installed模块的build gradle 以下是从游戏控制台检索到的反混淆堆栈跟踪 FATAL EXCEPTION Controlle
  • Android模拟器中的网络访问

    我试图通过我的 Android 应用程序访问互联网 但我既成功又失败 我在构建应用程序时启动模拟器 并且应用程序安装得很好 我可以使用浏览器访问互联网 但是 当我尝试这个小代码片段时 InetAddress inet try inet In
  • 如何将样式应用于我拥有的所有 TextView? [复制]

    这个问题在这里已经有答案了 可能的重复 设计所有 TextView 或自定义视图 的样式 而不向每个 TextView 添加样式属性 https stackoverflow com questions 6801890 styling all
  • Android 标记如何实现拖放?

    你好 我正在 Android 中开发 MapView 应用程序 我有三个标记 我希望稍后能够使用 Google Map API getlocation function 为了尝试一下 我想使用拖放功能移动标记 然后检查位置 任何人都可以通过
  • Android-dispatchTouchEvent 给了我一个 StackOverflowError

    这里我有一个带有 setOnTouchListener 的 ViewFlipper 它工作得很好 然后我膨胀 ReLayNewsItem 然后将其添加到 ViewFlipper 现在我希望 WebView web 监听触摸事件并将它们传递给
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从
  • 如何访问我的 Android 程序中的联系人

    我正在制作一个短信应用程序 并且想要访问我的 Android 应用程序中的联系人 我想访问联系人 就像他们在实际联系人列表中一样 选择后 我需要返回到我的活动 在其中我可以向该人发送短信 或者是否可以访问存储联系人的数据库 我的代码如下所示
  • Android GetPTLAFormat 上的 Phonegap 错误

    我们正在开发一个使用 jQuery 移动和电话间隙的应用程序 一切似乎都工作正常 但是当在连接的 Android 手机上运行应用程序时 我们在 Eclipse logcat 中看到大量类似这样的错误 0 GetPTLAFormat inva

随机推荐

  • 2024备战春招Java面试八股文合集

    Java就业大环境仍然根基稳定 市场上有很多机会 技术好的人前景就好 就看你有多大本事了 小编得到了一份很不错的资源 建议大家可以认真地来看看以下的资料 来提升一下自己的核心竞争力 在面试中轻松应对面试官拿下offer 本文分享 Java后
  • 2023 Java 面试题精选40道,包含答案

    Java中什么是重载 什么是覆盖 它们有什么区别 重载是指在同一个类中 方法名相同但参数类型 个数或顺序不同的情况下 编译器会根据参数列表的不同自动调用不同的方法 覆盖是指子类重写父类的同名方法 使得子类在调用该方法时执行子类的实现而不是父
  • 2024java面试看完这篇笔记薪资和offer稳了!

    新的一年抓住机会 不管跳槽涨薪 还是学习提升 这篇笔记你都不应该错过 为了帮大家节约时间 整理了这篇 Java面试 核心知识点整理 以及 金三银四高频面试合集 希望大家在新的一年都能拿到理想的薪资和offer 内容涵盖 计算机基础 Java
  • Java面试题及答案整理( 2023年12月最新版,持续更新)

    秋招金九银十快到了 发现网上很多Java面试题都没有答案 所以花了很长时间搜集整理出来了这套Java面试题大全 这套互联网 Java 工程师面试题包括了 MyBatis ZK Dubbo EL Redis MySQL 并发编程 Java面试
  • 面试官:什么是JWT?为什么要用JWT?

    目前传统的后台管理系统 以及不使用第三方登录的系统 使用 JWT 技术的还是挺多的 因此在面试中被问到的频率也比较高 所以今天我们就来看一下 什么是 JWT 为什么要用 JWT 1 什么是 JWT JWT JSON Web Token 是一
  • 浅析特征增强&个性化在CTR预估中的经典方法和效果对比

    在CTR预估中 主流都采用特征embedding MLP的方式 其中特征非常关键 然而对于相同的特征 在不同的样本中 表征是相同的 这种方式输入到下游模型 会限制模型的表达能力 为了解决这个问题 CTR预估领域提出了一系列相关工作 被称为特
  • 总有人说鸿蒙是安卓套壳?鸿蒙5.0之后彻底摆脱安卓

    鸿蒙系统的操作逻辑与安卓基本上差不多 这和安卓系统没啥区别 是不是就是安卓系统套了一个壳啊 为什么到今天还是有不少人在争论它到底是不是安卓套壳 这与鸿蒙早期 完全自主研发 的 过激 宣传不无关系 其次就是鸿蒙生态环境上的不足 确实 华为一开
  • 鸿蒙系统的崛起对程序员来说是机遇、还是挑战呢?

    前言 最近 一个话题在程序员圈子里激起了热烈讨论 那就是鸿蒙系统的崛起是否会影响程序员的就业和发展 我该转去学鸿蒙开发吗 鸿蒙前景如何 值不值得投入时间研究 对此 程序员们表达了各种疑虑和困惑 的确 一个全新的操作系统的出现确实让人眼花缭乱
  • go-zero 开发之安装 etcd

    本文只涉及 Linux 上的安装 二进制安装 下载二进制安装包 ETCD VER v3 4 28 ETCD VER v3 5 10 DOWNLOAD URL https github com etcd io etcd releases do
  • Quartz定时任务运行时,能够否对某个任务重新调度呢?

    背景 quartz 是一个功能丰富 开源 分布式的任务调用框架 我参与的很多项目都用它来实现定时调度功能 关于定时任务 有一个常见的需求是 由 Web 应用来控制定时任务的启动 停止 调度周期等 本文探讨的是 对于当前正在 调度的 耗时较长
  • go-zero开发入门之网关往rpc服务传递数据2

    go zero 的网关服务实际是个 go zero 的 API 服务 也就是一个 http 服务 或者说 rest 服务 http 转 grpc 使用了开源的 grpcurl 库 当网关需要往 rpc 服务传递额外的数据 比如鉴权数据的时候
  • 一呼百应!腾讯、阿里等全都支持鸿蒙了,安卓该担心了

    前言 众所周知 目前华为鸿蒙系统 已经是全球第三大智能手机系统 仅次于安卓 iOS 不过大家也都清楚 这个第三 实际上还是有水份的 因为鸿蒙其实并没有自己的生态 靠的是兼容安卓生态 真正的纯血鸿蒙APP 仅几十个 如果靠着这几十个APP 完
  • 短视频制作:从构思到发布的全方位指南

    在当今数字化时代 短视频已成为备受欢迎的媒体形式 凭借其简洁有趣的内容 短视频成功吸引了大量观众的关注 然而 制作一部引人入胜的短视频并非易事 本文将为你提供从目标设定到平台发布的全面指导 帮助你制作出令人难以忘怀的短视频 第一步 明确目标
  • 有哪些PDF转图片工具好用?PDF转图片免费软件推荐

    在一个阳光明媚的下午 你正在翻阅着一份重要的PDF文件 想要快速将其中的内容以图片形式分享给朋友 然而 复制粘贴不仅繁琐 还会失去原本的排版和格式 那么 如何将PDF文件转换成图片呢 今天就来介绍两款可以实现这一功能的免费软件 如果你也想知
  • 你知道ai写作工具哪个好吗?教你用AI写年终总结

    又是一年的十二月到了 每年到这个时候 朋友圈都总会出现一首常驻歌曲 十二月的奇迹 身为打工人的大家应该都希望 在忙碌了一年的最后一个月被奇迹眷顾吧 不过俗话说得好 靠人不如靠己 与其把自己交给命运的奇迹 那不如自己也努力争取一下 在老板面前
  • 鸿蒙开发入门:快速修复命令行调试开发指导

    快速修复命令行调试开发指导 当前阶段 HarmonyOS为开发者提供了命令行的调试开发工具可供使用 比如 包名为com ohos quickfix的示例应用 版本号为1000000 该应用的当前版本运行中有某问题需要修复 此时 开发者可参考
  • 主动学习与弱监督学习

    人工智能数据的获取没有想象中的那么简单 虽然我们早已身处大数据的浪潮下 很多公司在获取数据的大浪中翻滚却始终没有找到一个合适的获取数据的渠道 很多情况下 获取高质量的人工智能数据需要消耗大量的人力 时间 金钱 但是对于未来世界 以 人机协同
  • Java处理SSH-免密登录

    前提 需要测试主机之间能够免密 配置ssh请自行百度 jar包 旧版 com jcraft jsch 仅支持老版的密钥格式 旧版本 RSA
  • go-zero开发入门-API网关开发示例

    开发一个 API 网关 代理 https blog csdn net Aquester article details 134856271 中的 RPC 服务 网关完整源代码 file main go package main import
  • 设计之妙,理解Android动画流程

    本文基于Android 12进行学习研究 参考 深入理解Android内核源码 思路学习总结 如有理解不对 望各位朋友指正 另外可能留存一些疑问 留后续再探索 输出只是为了分享和提升自己 动画初始化 按照窗口管理策略类中的定义 动画应该被分