Transition(过渡动画)——一种将一种内容可视地替换为另一种内容的机制:屏幕、片段、视图或界面状态。正确实现的过渡可以引导用户注意力,解释元素之间的相互关系,并使导航变得直观。根据 Material Design 指南(2026),具有精心设计过渡的应用在第二次会话中用户留存率提高 22%。有关其他动画类型的更多信息,请阅读动画通用指南。
要点
Transition — 是一种在一种视觉界面状态被另一种替换时发生的动画。与 Property Animation(单个对象属性的动画)不同,Transition 管理着一整套变化:元素的出现/消失、位置、大小和属性在单个过渡中的改变。Transition 解决了内容平滑变化的问题,使用户理解什么移动到了哪里。
在移动平台上,Transition 应用于三种主要场景:屏幕之间的导航(push、pop、modal present)、片段的切换(Android FragmentTransaction)和屏幕内部状态的变化(展开卡片、切换标签)。每种场景都需要自己的方法:导航过渡 — iOS 中的 UIViewControllerAnimatedTransitioning 或 Android 中的 ActivityOptions,屏幕内过渡 — Transition Framework 或 UIView.transition。
根据 Apple HIG(2026),标准过渡的持续时间不应超过 400–500 毫秒 — 更长的动画会被视为延迟。Material Design 建议移动过渡使用 300–350 毫秒。对于大元素(卡片、图像),时间可以增加到 500–600 毫秒而不会损失感知性能。
过渡类型根据什么内容如何变化分为几个类别。基本类型:淡入淡出(cross-fade)— 一个逐渐消失,另一个出现;滑动(slide)— 一个屏幕离开,另一个进入;缩放(scale/zoom)— 元素放大,打开详细屏幕。组合过渡结合了多种效果。
| 过渡类型 | 描述 | iOS API | Android API |
|---|---|---|---|
| Cross-fade | 淡入淡出:一个内容消失,另一个出现 | UIView.transition(with:duration:options:.transitionCrossDissolve) | TransitionSet + Fade() |
| Slide | 滑动:内容水平或垂直离开/进入 | CATransition(type: .push, subtype: .fromLeft) | Slide() / FragmentTransaction.setCustomAnimations |
| Scale / Zoom | 缩放:元素放大到全屏 | UIViewPropertyAnimator 配合 scale 动画 | Scale() + ActivityOptions.makeScaleUpAnimation |
| Explode | 爆炸式:内容在消失时散开 | CATransition(type: .reveal) | Explode() (API 21+) |
| Fade-through | 通过白色/黑色屏幕淡入淡出 | UIView.transition(with: .transitionCrossDissolve,白色背景) | 通过 ChangeBounds 实现的 Fade() |
Material Design 规则:在一个应用内使用一种过渡类型,以创建统一的视觉语言。建议使用 slide 进行同级屏幕之间的导航(master → detail),cross-fade 用于同一屏幕内的内容更改(标签、视图切换),scale/zoom 用于从卡片到详细屏幕的过渡(产品卡片 → 描述)。
在 iOS 中,UIView.transition 是在单个 View 内对内容更改进行动画的最简单方法。它支持 cross-dissolve、curl-up、curl-down、flip-from-left、flip-from-right。对于 UIViewController 之间的导航过渡,使用 UINavigationControllerDelegate 配合 UIViewControllerAnimatedTransitioning — 这提供了对 push/pop 动画的完全控制。
import UIKit
// UIView.transition:使用 cross-dissolve 切换图像
UIView.transition(with: imageView,
duration: 0.4,
options: [.transitionCrossDissolve, .curveEaseInOut]
) {
self.imageView.image = UIImage(named: "new_image")
} completion: { _ in
print("Transition 完成")
}
// UIView.transition:切换两个 View
UIView.transition(from: firstView, to: secondView,
duration: 0.5,
options: [.transitionFlipFromRight, .showHideTransitionViews]
) { _ in
// firstView 隐藏,secondView 显示
}
// 用于图层的 CATransition(无 Auto Layout)
let transition = CATransition()
transition.type = .push
transition.subtype = .fromRight
transition.duration = 0.35
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
view.layer.add(transition, forKey: "pushTransition")
UIViewPropertyAnimator — iOS 10+ 上自定义过渡的现代替代方案。与 UIView.transition 不同,PropertyAnimator 支持暂停、取消和 reverse(反向动画),这对于交互式过渡(滑动返回)非常重要。对于 UIViewController 之间的动画,请将 UIPercentDrivenInteractiveTransition 与 PropertyAnimator 结合使用 — 这是交互式导航的标准模式。
在 Android 中,带有 setCustomAnimations 的 FragmentTransaction 是 Fragment 之间过渡动画的主要方法。参数:enter(新 Fragment 的进入动画)、exit(旧的退出动画)、popEnter 和 popExit — 用于后退导航。FragmentTransaction 支持资源动画 (R.anim) 和 Transition Framework (R.transition)。
import androidx.fragment.app.FragmentTransaction
import androidx.transition.TransitionInflater
// 带资源动画的 FragmentTransaction
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.slide_in_right, // enter
R.anim.slide_out_left, // exit
R.anim.slide_in_left, // popEnter
R.anim.slide_out_right // popExit
)
.replace(R.id.container, DetailFragment())
.addToBackStack(null)
.commit()
// 带 Transition Framework 的 FragmentTransaction
val detailFragment = DetailFragment()
detailFragment.enterTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.slide_right)
detailFragment.exitTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.fade_out)
supportFragmentManager.beginTransaction()
.replace(R.id.container, detailFragment)
.addToBackStack(null)
.commit()
// ActivityOptions:启动 Activity 时的动画
val options = ActivityOptions.makeScaleUpAnimation(
view, 0, 0, view.width, view.height
).toBundle()
startActivity(intent, options)
Android 最佳实践:Slide() — 用于导航中 Fragment 的替换(enter = Slide(Gravity.END), exit = Slide(Gravity.START))。Fade() — 用于显示/隐藏覆盖层和对话框。Explode() — 用于强调过渡(从列表中删除元素)。对于旧设备 (API < 21),使用 R.anim 作为后备方案。Transition Framework 不支持 SurfaceView、TextureView、GLSurfaceView — 对于视频和摄像头,仅使用通过 R.anim 实现的动画。
UIViewControllerAnimatedTransitioning — iOS 中用于在控制器之间创建完全自定义过渡动画的协议。它提供对容器 View (transitionContext.containerView)、源 View 和目标 View 的访问。与 UIPercentDrivenInteractiveTransition 结合使用时,这种方法允许通过手指滑动创建交互式过渡。
import UIKit
"> 自定义过渡动画器
class ScaleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
init(presenting: Bool) { self.isPresenting = presenting }
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
guard let toView = ctx.view(forKey: .to),
let fromView = ctx.view(forKey: .from)
else { return }
let container = ctx.containerView
let initialScale: CGAffineTransform = CGAffineTransform(scaleX: 0.8, y: 0.8)
let finalFrame = ctx.finalFrame(for: toView)
if isPresenting {
toView.transform = initialScale
toView.alpha = 0
toView.frame = finalFrame
container.addSubview(toView)
}
UIView.animate(withDuration: transitionDuration(using: ctx),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.3) {
if self.isPresenting {
toView.transform = .identity
toView.alpha = 1
fromView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
fromView.alpha = 0
} else {
fromView.transform = initialScale
fromView.alpha = 0
}
} completion: { _ in
fromView.transform = .identity
ctx.completeTransition(!ctx.transitionWasCancelled)
}
}
}
// 在 UINavigationControllerDelegate 中的应用
extension MainViewController: UINavigationControllerDelegate {
func navigationController(
_ nc: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from: UIViewController, to: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return ScaleTransitionAnimator(presenting: operation == .push)
}
}
交互式过渡:向源屏幕添加 UIPanGestureRecognizer。当手势触发时,创建 UIPercentDrivenInteractiveTransition 并调用 update(CGFloat(percent))。要在未达到阈值(通常为 30%)时取消,请调用 cancel()。此模式默认用于 UINavigationController 中的 pop 手势以及具有 pagingEnabled 的 UIScrollView。对于自定义手势,实现 UIViewControllerInteractiveTransitioning。
Transition Framework (android.transition) — 是一个声明式动画系统,自动在两个场景 (Scene) 之间的变化进行动画。场景是具有特定状态的 View 集合。在从 Scene A 到 Scene B 的过渡中,Transition Framework 分析差异并动画显示:位置变化 (ChangeBounds)、大小变化 (ChangeClipBounds, ChangeTransform)、出现 (Fade) 和消失 (Fade)。
import android.transition.Scene
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.transition.AutoTransition
import android.transition.ChangeBounds
import android.transition.Fade
// TransitionSet:ChangeBounds + Fade 组合
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(Fade(Fade.IN))
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
// ViewGroup 中场景之间的过渡
val sceneRoot = findViewById<ViewGroup>(R.id.scene_root)
val scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_initial)
val scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_expanded)
"> 无需手动配置的 AutoTransition
TransitionManager.go(scene2, AutoTransition().apply {
duration = 350L
})
// TransitionManager.beginDelayedTransition — 无场景
TransitionManager.beginDelayedTransition(sceneRoot, ChangeBounds())
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
itemView.requestLayout() "> layout 的变化自动动画化
ChangeBounds — Android 中最常用的过渡,对 View 的位置和大小变化进行动画。ChangeTransform 对 rotation 和 scale 进行动画。ChangeImageTransform — 用于 ImageView 的动画 (centerCrop → fitCenter)。对于层次结构变化的复杂过渡,使用带有 ordering = ORDERING_SEQUENTIAL 的 TransitionSet(动画按顺序执行,而不是并行)。根据 Android Developers 文档(2026),Transition Framework 支持所有标准 View,但不支持表面动画(SurfaceView、TextureView、VideoView)。
常见问题
Animation — 单个对象属性的动画(alpha、translation、scale)。Transition — 对象或状态集合变化的动画(片段 → 片段,场景 → 场景)。Transition 同时管理多个变化:出现、消失、移动、大小变化 — 并自动对两个状态之间的差异进行动画。Animation 需要手动管理每个属性。
使用带有 Transition Framework 的 FragmentTransaction.setCustomAnimations:fragment1.exitTransition = Fade(Fade.OUT), fragment2.enterTransition = Slide(Gravity.END)。对于序列:setReorderingAllowed(true) — 片段将被重新排序以实现平滑的并行过渡。对于简单情况,来自 R.anim 的 slide_in_right / slide_out_left 就足够了。对于后退导航,指定 popEnter 和 popExit。
Transition Framework 只对不会导致布局重新计算的属性进行动画。对于高度动画,使用 ChangeBounds — 它会自动对两个布局状态之间的 View 边界变化进行动画。在更改 LayoutParams 之前调用 TransitionManager.beginDelayedTransition(viewGroup, ChangeBounds())。如果高度通过 WRAP_CONTENT 变化,请确保前后的 layout_height 不同 — 如果两个状态都是 match_parent,ChangeBounds 将不起作用。
使用带有 AnyTransition 的 .transition() 修饰符:.transition(.move(edge: .trailing).combined(with: .opacity))。对于自定义过渡,实现 AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))。对于 NavigationStack 内的动画,在 iOS 18+ 中使用 .navigationTransition(.slide)。SwiftUI 不支持 UIViewControllerAnimatedTransitioning — 仅支持通过 AnyTransition 的声明式过渡。
使用 Profile GPU Rendering(Android)或 Core Animation Instrument(iOS)。对于 Android,通过 adb shell setprop debug.transition.profiling 1 添加 Transition 分析 — 日志中将显示过渡每个阶段的持续时间。在 iOS 上,在模拟器中启用 Slow Animations。目标指标:持续时间 < 400 毫秒,FPS >= 55,frame drops — 每次过渡不超过 2 次。对于具有 15+ 个同时动画 View 的复杂场景,使用 duration 300 毫秒的 ChangeBounds。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。