【阅读源码-Android】动画之AnimatorSet--2

3.4 Reverse 流程

java 复制代码
public void reverse() {
    start(true, true);  // inReverse=true, selfPulse=true
}

条件canReverse() 要求 getTotalDuration() != DURATION_INFINITE(无限时长的 AnimatorSet 不可反向)。

reverse 的本质 :不是在运行中翻转方向,而是以反向模式重新 start()mReversing = true 影响以下行为:

行为 正向 反向
handleAnimationEvents 遍历方向 从 mLastEventId+1 向前 从 mLastEventId-1 向后
事件语义 START→启动, END→结束 END→reverse启动, DELAY_ENDED→结束
findLatestEventIdForTime time <= currentPlayTime time >= (totalDuration - currentPlayTime)
getPlayTimeForNodeIncludingDelay overallPlayTime - node.mStartTime node.mEndTime - (totalDuration - overallPlayTime)
结束条件 mPlayingSet 空且 mLastEventId 到末尾 mPlayingSet 仅剩 Root 或 mLastEventId < 3

3.5 Seek 流程

java 复制代码
public void setCurrentPlayTime(long playTime) {
    initAnimation();  // 确保依赖图已构建

    long lastPlayTime = mSeekState.getPlayTime();
    if (!isStarted() || isPaused()) {
        if (!mSeekState.isActive()) {
            // 首次 seek:初始化子动画起始值
            findLatestEventIdForTime(0);
            initChildren();
            skipToEndValue(!mReversing);
            mSeekState.setPlayTime(0, mReversing);
        }
    }
    mSeekState.setPlayTime(playTime, mReversing);
    animateBasedOnPlayTime(playTime, lastPlayTime, mReversing);
}

SeekState 的作用 :存储 seek 位置和方向,在 doAnimationFrame 首帧时应用:

java 复制代码
if (mSeekState.isActive()) {
    mSeekState.updateSeekDirection(mReversing);
    mFirstFrame = frameTime - mSeekState.getPlayTime() * durationScale;
    mSeekState.reset();
}

animateBasedOnPlayTime 通过 mChildStartAndStopTimes 二分查找定位区间,逐个时间点调用 animateSkipToEnds(跳到端点)和 animateValuesInRange(计算中间值)。

3.6 Start 流程

java 复制代码
start(false, true):
  mStarted = true
  mSelfPulse = true
  重置所有 Node.mEnded = false
  initAnimation()  ← 构建 DAG + events
  mReversing = false

  if (!isEmptySet):
    startAnimation():
      1. addAnimationEndListener()  ← 为子动画添加结束监听
      2. addAnimationCallback(0)     ← 注册到 AnimationHandler
      3. if mShouldResetValuesAtStart:
           initChildren() + skipToEndValue(!mReversing)  ← 重置到起始值
      4. if mReversing || mStartDelay == 0 || mSeekState.isActive():
           快进到当前播放位置:
             playTime = seek ? mSeekState.getPlayTime() : 0
             toId = findLatestEventIdForTime(playTime)
             handleAnimationEvents(-1, toId, playTime)  ← 从头处理到当前位置
             清理已结束的 mPlayingSet
             mLastEventId = toId

  notifyStartListeners(inReverse)

isEmptySet 检查:如果 AnimatorSet 不含任何非 AnimatorSet 子动画且无 startDelay,直接 start + end。

快进条件 :仅在反向、无延迟、或有 seek 时立即快进。正向 + 有 startDelay 时等待 doAnimationFrame 中 delay 过去后自然启动。

3.7 doAnimationFrame 流程

java 复制代码
doAnimationFrame(frameTime):
  if durationScale == 0: forceToEnd(); return true

  if mFirstFrame < 0: mFirstFrame = frameTime  ← 记录首帧

  // pause/resume 处理
  if mPaused: mPauseTime = frameTime; removeCallback; return false
  if mPauseTime > 0: mFirstFrame += (frameTime - mPauseTime); mPauseTime = -1

  // seek 对齐
  if mSeekState.isActive():
    mFirstFrame = frameTime - mSeekState.getPlayTime() * durationScale
    mSeekState.reset()

  // startDelay 等待
  if !mReversing && frameTime < mFirstFrame + mStartDelay * durationScale:
    return false

  // 计算播放时间
  unscaledPlayTime = (frameTime - mFirstFrame) / durationScale
  mLastFrameTime = frameTime

  // 1. 处理事件(启动/结束子动画)
  latestId = findLatestEventIdForTime(unscaledPlayTime)
  handleAnimationEvents(mLastEventId, latestId, unscaledPlayTime)
  mLastEventId = latestId

  // 2. 向活跃子动画注入帧
  for node in mPlayingSet:
    if !node.mEnded:
      pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node))

  // 3. 清理已结束的子动画
  mPlayingSet.removeIf(n -> n.mEnded)

  // 4. 判断是否全部完成
  finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1
  if finished: endAnimation(); return true
  return false

handleAnimationEvents 正向时从 mLastEventId+1latestId 遍历:

  • ANIMATION_STARTmPlayingSet.add(node) + node.mAnimation.startWithoutPulsing(false) + pulseFrame(node, 0)
  • ANIMATION_ENDpulseFrame(node, playTime)(注入最后一帧)

pulseFramenode.mAnimation.pulseAnimationFrame(animPlayTime * durationScale),返回 true 表示子动画已结束,设置 node.mEnded = true

3.8 Pause 流程

java 复制代码
pause():
  super.pause()  ← mPaused = true
  mPauseTime = -1  ← 标记,等待下一帧记录暂停时刻
  callOnPlayingSet(Animator::pause)  ← 暂停所有活跃子动画

下一帧 doAnimationFrame 中:

java 复制代码
if mPaused:
  mPauseTime = frameTime  ← 记录暂停时刻
  removeAnimationCallback()  ← 注销帧回调,停止接收帧
  return false

子动画的 pause 由 AnimatorSet 委托调用,但子动画不自行注册帧回调,所以实际暂停效果由 AnimatorSet 的 removeAnimationCallback 实现。

3.9 Resume 流程

java 复制代码
resume():
  super.resume()  ← mPaused = false
  if mPauseTime >= 0:
    addAnimationCallback(0)  ← 重新注册帧回调
  callOnPlayingSet(Animator::resume)  ← 恢复所有子动画

下一帧 doAnimationFrame 中:

java 复制代码
if mPauseTime > 0:
  mFirstFrame += (frameTime - mPauseTime)  ← 偏移首帧,跳过暂停期间
  mPauseTime = -1

3.10 End 流程

java 复制代码
end():
  if mShouldIgnoreEndWithoutStart && !isStarted(): return  ← Pre-N 兼容

  if isStarted():
    mStarted = false  ← 防止重入
    if mReversing:
      // 反向:从 mLastEventId-1 向后遍历
      for eventId = mLastEventId-1 downto 0:
        if ANIMATION_END: anim.reverse()  ← 反向启动
        if ANIMATION_DELAY_ENDED && anim.isStarted(): anim.end()  ← 结束
    else:
      // 正向:从 mLastEventId+1 向前遍历
      for eventId = mLastEventId+1 to size:
        if ANIMATION_START: anim.start()  ← 启动未启动的
        if ANIMATION_END && anim.isStarted(): anim.end()  ← 结束未结束的

  endAnimation()  ← 清理状态

end 的语义:快速跳过所有未处理的事件,将所有子动画推到最终状态。正向时启动所有未启动的、结束所有未结束的;反向时反转所有 END 事件、结束所有 DELAY_ENDED 事件。

3.11 Stop 流程

AnimatorSet 没有 stop() 方法。结束动画通过 end()cancel() 实现。内部 endAnimation() 是状态清理的统一入口:

java 复制代码
private void endAnimation() {
  mStarted = false
  mLastFrameTime = -1
  mFirstFrame = -1
  mLastEventId = -1
  mPaused = false
  mPauseTime = -1
  mSeekState.reset()
  mPlayingSet.clear()
  removeAnimationCallback()  ← 注销帧回调
  notifyEndListeners(mReversing)  ← 通知 onAnimationEnd
  removeAnimationEndListener()  ← 移除子动画监听
  mSelfPulse = true
  mReversing = false
}

3.12 Cancel 流程

java 复制代码
cancel():
  if isStarted() || mStartListenersCalled:
    notifyListeners(ON_CANCEL)  ← 通知 onAnimationCancel
    callOnPlayingSet(Animator::cancel)  ← 取消所有活跃子动画
    mPlayingSet.clear()
    endAnimation()  ← 清理状态

cancel 与 end 的区别

  • cancel:子动画在当前位置停止,触发 onAnimationCancel,然后 endAnimation
  • end:子动画被推到最终状态(start/end),不触发 cancel

callOnPlayingSetmPlayingSet 中每个子动画调用 cancel(),子动画的 cancel() 会触发自身的 onAnimationCancelonAnimationEnd

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
哈基米南北1 天前
ViewRootImpl 事件分发责任链分析
源码
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas