在Android补间动画体系中,XML动画集合由 <set> 节点承载,其中 android:ordering 属性直接决定集合内多个子动画的执行策略。该属性接收两个值:together 表示所有子动画同时播放,sequentially 表示按照声明顺序依次播放。初看起来这个配置很简单,但把它和子动画自身的时长、startOffset 以及嵌套集合放在一起时,执行结果往往和预期不同,因此有必要深入理解其背后的排序机制。

一、在res/anim目录配置android:ordering的基础方法
补间动画集合资源通常放在 res/anim 目录下,根节点使用 <set>。该节点除了可以设置 shareInterpolator、duration 等通用属性外,最核心的就是 android:ordering。如果省略该属性,系统默认按照 together 方式执行,也就是集合中的所有子动画同时启动。子动画可以是 <alpha>、<scale>、<translate>、<rotate> 中的任意组合。
下面是一个同时播放的配置示例。透明度动画和位移动画会在加载后同时开始,透明度的持续时间是400毫秒,位移动画的持续时间是600毫秒,两者并行执行,视觉上会出现淡入加滑入的重叠效果。
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<alpha
android:duration="400"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<translate
android:duration="600"
android:fromXDelta="-100%"
android:toXDelta="0" />
</set>
如果需要让透明度动画完整播放结束后再启动位移动画,只需把 android:ordering 改为 sequentially。系统会自动把第二个动画的开始时间设置到第一个动画结束之后,无需手动计算第一个动画的时长再赋给第二个动画的 startOffset。
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<alpha
android:duration="400"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<translate
android:duration="600"
android:fromXDelta="-100%"
android:toXDelta="0" />
</set>
二、together与sequentially的排序机制差异
together 模式下的处理非常直观。每个子动画的起始时间完全由自身的 startOffset 决定,系统不会因为集合内其他动画的存在而调整它们。如果没有设置 startOffset,所有子动画的起始时间都相对于父集合的开始时间为零,动画会在同一时刻启动。若想实现错峰并行效果,比如透明度动画先播放100毫秒后位移动画再启动,但两者仍然并行,可以为后启动的动画单独设置 android:startOffset="100"。这种情况下集合的执行总时长取决于最晚结束的那个子动画。
sequentially 模式则会在初始化阶段遍历子动画,并维护一个累计开始时间。系统从第一个子动画开始,把当前累计时间赋值给该动画,然后把该动画的 duration 和 startOffset 累加到累计时间中,作为下一个子动画的开始时间。可以用下面的伪代码近似描述这一过程。
long currentStart = 0;
for (Animation child : children) {
child.setStartOffset(currentStart + child.getStartOffset());
currentStart += child.getDuration() + child.getStartOffset();
}
从机制上可以看出,顺序模式会自动考虑每个子动画自身的 startOffset,并把该值叠加到后续动画的启动时间中。如果在顺序集合中给第二个动画手动配置了和第一个动画时长相同的 startOffset,第二个动画并不会紧接在第一个动画结束后启动,而是会再等待一个额外的偏移量。这是实际开发中最容易产生时序混乱的地方。
源码中 AnimationSet 会读取 XML 属性并设置内部的有序标志,在 initialize 阶段根据该标志决定是否进行累计偏移计算。因此 sequentially 并不是在播放线程中动态等待前一个动画结束,而是在动画初始化时就已经把后续动画的开始时间计算好了。这种设计对性能友好,但也意味着运行期间修改子动画时长不会动态重排后续动画的启动时间,必须重新加载动画资源才能生效。
三、加载方式与常见使用误区
无论是同时播放还是顺序播放,Java 和 Kotlin 中的加载方式完全相同。使用 AnimationUtils.loadAnimation 加载资源,然后通过 startAnimation 应用到目标 View 上即可。也可以在代码中获取 AnimationSet 后继续调整子动画属性,但需要注意重新排序相关逻辑不会自动重算。
Animation anim = AnimationUtils.loadAnimation(this, R.anim.sequential_anim); imageView.startAnimation(anim);
常见误区之一是在顺序模式下仍然为每个子动画手动设置 startOffset。例如第一个动画时长400毫秒,第二个动画设置 android:startOffset="400",同时集合又使用 sequentially,最终第二个动画会从800毫秒处开始播放。正确做法是顺序模式下只配置各个子动画的 duration,让系统自动排列;如果确实需要手动控制间隔,可以把集合改为 together 并明确设置每个动画的 startOffset。
另一个容易忽略的点是嵌套集合。一个 <set> 内部还可以再包含另一个 <set>,外层集合如果使用顺序模式,内层集合会被看作一个整体动画来参与时长累加。内层集合自身的 ordering 决定其内部动画是并行还是串行。这种嵌套结构适合组织较复杂的动画编排,但也更容易出现时间轴计算错误,建议在调试时通过设置不同的背景色或监听回调观察每个阶段的实际执行时间。
除了补间动画,属性动画中的 AnimatorSet 也支持类似的 android:ordering 属性,资源文件放在 res/animator 目录下。其取值同样为 together 和 sequentially,基础思想与补间动画一致,但属性动画对对象属性修改更灵活。如果需要更细粒度的前后依赖控制,可以结合 AnimatorSet.Builder 的 with、before、after 方法实现,而 XML 中的 ordering 更适合表达简单的全并行或全串行关系。
Android动画集合XML动画动画播放顺序修改时间:2026-08-28 05:53:59