【阅读源码-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+1 到 latestId 遍历:

  • ANIMATION_START:mPlayingSet.add(node) + node.mAnimation.startWithoutPulsing(false) + pulseFrame(node, 0)
  • ANIMATION_END:pulseFrame(node, playTime)(注入最后一帧)

pulseFrame :node.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

callOnPlayingSet 对 mPlayingSet 中每个子动画调用 cancel(),子动画的 cancel() 会触发自身的 onAnimationCancel 和 onAnimationEnd。

相关推荐
传奇开心果编程18 小时前
【Jetpack Compose娓娓道来】第29课:状态容器与UI事件建模——把“散装状态”变成“可穷举的契约”
android·kotlin·移动开发·android jetpack·ui框架·开发范式·声明式ui
五彩小白19 小时前
喵呜打卡项目总结
android·团队开发
古法安卓20 小时前
Android-GPU压测工具
android·java·android studio
xcLeigh1 天前
KingbaseES数据库教程:初识数据库,从安装到跑通第一条SQL
android·数据库·sql·安装·数据库教程·kes
TimeFine1 天前
AI 编码工作流的设计(二)
android·ai编程
2401_881828321 天前
MySQL操作命令
android·mysql·adb
用户60709759149061 天前
AOSP 14 OTA 升级指导(三):三条升级链路——Java API、自研 HAL 与 TR069
android
前端小D1 天前
React Native Android 项目搭建
android·react native·react.js
恋猫de小郭1 天前
感人,Flutter 居然有了一个超实用提案,期待落地
android·前端·flutter
CS_Zero1 天前
安卓开发:Linux的android工具命令
android·linux·运维