【阅读源码-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

相关推荐
叶辞树4 小时前
Android CLI
android
我命由我123457 小时前
Android 开发问题:unexpected element <service> found in <manifest>
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
程思扬7 小时前
Android ANR实战排查:主线程SystemClock.sleep导致页面启动偶现无响应
android·笔记
码农coding7 小时前
android12 InputManagerService分析之输入事件如何回到应用层
android
zhangguojia78 小时前
从一个窗口覆盖问题出发,理解 Android Window 的层级与权限
android
古法安卓10 小时前
Android-SELinux 策略调试实战:从 AVC 日志到策略修复
android·java·android studio
Dovis(誓平步青云)11 小时前
DevEco Studio 6.1.1 Windows 安装实录:从下载校验到首次启动
android·开发语言·数据库·人工智能·windows·harmonyos
Zender Han11 小时前
Flutter 自适应(Adaptive)与响应式(Responsive)设计实践:官方推荐方案详解
android·flutter·ios
我命由我1234512 小时前
Android 开发问题:TopAppBar 和 topAppBarColors API is experimental...
android·java·java-ee·kotlin·android studio·android jetpack·android-studio