iPhone Duo 各姿態間的過渡動畫
展開、合上、旋轉和彎折時 iPhone Duo 為你做了哪些系統動效,它留給 App 處理的唯一變化,以及如何為那項變化新增動畫。來自對真實 App 的逐幀螢幕錄影。
展開 iPhone Duo,螢幕上的畫面彷彿凝固在空間中,唯有機身玻璃在周圍轉動。這是系統底層所做的工作,它也引出了每款適配 App 遲早都要面對的問題:當裝置變換姿態時,App 自身究竟應該為哪些變化做動畫?Apple 人機介面指南中關於動效只有一條原則,且並未提供任何專門的 API。本文通過螢幕錄影解答這一問題:電影票根收藏 SwiftUI App Flicit 在 Apple 的 iPhone Duo 模擬器中經歷每次姿態切換,在學會如何做動效的前後,被逐幀錄製比對。
簡短的答案是:極少,且只有一件事。藉助程式設計代理適配 iPhone Duo 的指南提供了製作這些螢幕錄影所需的工具包;姿態指南則詳細介紹了各姿態本身。
要點
- 開合與旋轉均由系統接管。 系統在高斯模糊快照下將 App 在兩塊螢幕間交接,並通過自身的旋轉動效轉動視窗。App 在底層完成尺寸調整:單次排版直接落地新佈局,保留使用者所在位置與狀態,不要新增任何自己的動效。
- 展開狀態下彎折手機歸你處理。 視窗尺寸保持不變,沒有任何遮罩掩蓋變化,佈局在摺痕處所做的任何調整,使用者都會親眼目睹。
- 最好的彎折是什麼都不動。 在彎折前便根據系統未啟用的摺痕區域完成摺痕排版,書本形態下螢幕上便不會發生任何位移。
- 當某個姿態擁有獨立專用佈局時,為佈局切換做動畫,且僅在彎折時觸發:即摺痕在啟用或靜止間切換、同時視窗保持原有尺寸。切勿將動畫繫結到姿態本身,否則它會疊在系統旋轉動畫之上播放。
- 為新佈局建置的檢視起初影像為空白。 在首幀繪製已解碼的點陣圖,否則每次切換都會暴露佔位圖。
- 螢幕錄影驗證。 截圖無法發現跳變、空白幀或佔位圖閃爍。
系統為你做了哪些動效
開合過程
系統使用 App 的高斯模糊快照遮蓋外螢幕與內螢幕之間的交接,快照模糊程度隨機身運動而變化。Empirical Health 對該效果的逆向重建描述為:內容被固定在空間之中,而玻璃在其周圍翻轉——靠近轉軸處清晰,越靠近運動邊緣越模糊,內螢幕起初較暗,隨展開過程逐漸變亮。兩塊螢幕擁有完全一致的寬高比,因此畫面不會發生跳變。
在底層,沒有檢視被銷燬重建。在 Frank Schlegel 記錄的 Apple 小組實驗室(Group Labs)筆記中,全螢幕 App “通常在另一塊螢幕上繼續執行並調整大小,而不是被銷燬重建”;而在 Anton Gubarenko 整理的開發者論壇回覆中,則表述為 “轉軸與螢幕過渡表現為尺寸類別(Size Class)和 Trait 的變更”。在 Flicit 的螢幕錄影中,App 在模糊快照仍未褪去時便單步到位地完成了新佈局。此時它所做的任何動作都不會被看見,因此在此處新增動畫沒有任何意義。
使用者真正能看到的是他們是否依然停留在之前的位置。這才是此處的關鍵任務:同一個螢幕、同樣的滾動位置、剛剛正在輸入的文字。
旋轉
在展開狀態下將手機在橫置與縱向立起之間旋轉屬於螢幕旋轉,系統自帶旋轉動畫。在旋轉完成時,Flicit 的票庫(Library)已經完成了直向排版。之後唯一發生位移的是:轉回橫向時,其底部標籤列逐漸淡出,側邊欄(Side Rail)逐漸淡入——這是旋轉完成後的短暫交接,而非疊加在旋轉過程之上的額外動效。這在視覺上自然流暢。如果將彈性動畫(Spring)繫結到姿態上,並在系統仍在旋轉視窗時觸發,效果就會大打折扣。
彎折
將展開的手機部分折起,視窗尺寸保持不變。這裡既沒有模糊快照,也沒有螢幕旋轉:唯一的變化是摺痕區域從靜止變為啟用態,你的佈局為此所做的任何調整都會在眾目睽睽之下發生。在模擬器中,傾斜背離視線的那一側摺頁在轉軸運動時會暗化並模糊,並在轉軸停止運動的瞬間恢復清晰。

系統自身的元件在彎折時已經會自動調整。警告框(Alert)、選單、浮層(Popover)與表單(Sheet)會圍繞摺痕重新定位,分割畫面(Split View)也會平衡其分欄寬度。其餘的部分則由你決定。
Apple 的原則:微調為先
人機介面指南明確指出:“Avoid extreme layout changes as people fold the device. Move only what’s necessary to keep elements visible and easy to tap. Controls that disappear or shift dramatically are harder to find and track, so favor small adjustments over rearrangement.”(在使用者摺疊裝置時,避免極端的佈局變動。僅移動必要的元素以保證其可見且易於點選。消失或大幅位移的控制元件更難找尋和跟蹤,因此優先做小幅調整而非重新排布。)
關於佈局的 Tech Talk 以“位移(displacement)”為名補充了細節。將元素移動到符合其用途的區域,但不要遠離其原有位置,因為距離會割裂兩者之間的視覺聯絡。將相關的元件一同移動。連續滾動的正文內容(文章、資訊流、文件、列表)應留在原處,因為移動它們“會打斷連續性”。連移動方向都有考量:像書本一樣半折時,警告框移向靠後的一側,“更接近裝置合上時它們出現的位置”。
還有兩個事實框定了動畫的形式。轉軸資料“非常適合驅動互動或動效”,而佈局則歸屬於保留區域(Region)與 Arrangement API。並且系統沒有提供任何用於對姿態變換做動畫的公共 API。onHingeChange 僅報告狀態和角度,不含速度;唯一帶有 animated: 引數的摺疊 API 是 UIKit 的 UIArrangementViewController,用於手動切換排列方式。
最好的彎折是什麼都不動
Flicit 最初的螢幕錄影捕捉到了一處並非由系統引起的動效:在彎折時,票庫的票根列表列在摺痕變為啟用態的瞬間突兀跳動對齊至摺痕,而在完全展平時又跳回原位。修復手段並非新增動畫。使用 .includeInactive 請求摺痕區域,系統便會在手機仍完全平放時報告摺痕未來的走向,從而讓雙欄排版從一開始就將間距對齊在該位置:
let divisions = proxy.reservedRegions(kind: .division, options: .includeInactive)
let crease = divisions.first { $0.isActive }?.frame // 半折狀態
?? divisions.first { !$0.isActive }?.frame // 平放狀態:未來摺痕所在位置
在兩種姿態下間距均對齊該線後,Flicit 橫向彎折的螢幕錄影中便除了系統的模糊之外什麼位移都沒有了。摺痕指南介紹了該 API,AI 代理指南的書本姿態一節則提供了防止旋轉導致的陳舊區域汙染布局的校驗邏輯。儘可能這麼做:最好的微調就是不做調整。
僅對彎折做動畫,且僅在彎折時觸發
有些姿態值得擁有獨立的專用佈局。將展開的手機縱向立起並折至約 110°,Flicit 的票根頁面便一分為二:立起的上半螢幕呈現橫向票樣,平放的下半螢幕承載翻頁操作檯。電影詳情頁同樣拆分,上方為海報與簡介,下方為評分與操作按鈕。

在新增任何動效之前,兩個頁面在兩個方向上均以單幀硬切切換佈局。這正是人機指南所警告的那類劇烈變動,但筆記本佈局有其存在的理由:在立起的半螢幕上,橫向票樣的面積大約是縱向票樣的三倍。可以柔化的是切換動作本身。
問題在於何時做動畫。姿態可以通過多種方式改變,而其中只有一種是沒有系統遮蓋的。開合與旋轉均會改變視窗尺寸;彎折則不會。因此彎折的判定標準是:摺痕在啟用或靜止間切換、同時視窗保持原有尺寸。將視窗度量為受追蹤檢視加上其 Inset 安全邊距,因為彎折可能會為頁面注入邊距。首次讀取也不屬於彎折:摺痕保留區域是在首輪佈局之後到達的,為它們新增動畫會導致頁面在 App 啟動時滑入。
struct FoldStage: Equatable {
var seam: CGRect? // 啟用的 division 保留區域;平放或合上時為 nil
var contentSize: CGSize = .zero // 受追蹤的檢視尺寸,位於安全邊距內
var insets = EdgeInsets()
var windowSize: CGSize {
CGSize(width: contentSize.width + insets.leading + insets.trailing,
height: contentSize.height + insets.top + insets.bottom)
}
/// 摺痕在啟用或靜止間切換,同時視窗保持尺寸不變。
func isBend(to next: FoldStage) -> Bool {
(seam == nil) != (next.seam == nil) && windowSize.width > 0
&& abs(windowSize.width - next.windowSize.width) <= 0.5
&& abs(windowSize.height - next.windowSize.height) <= 0.5
}
}
在視窗根節點的唯一位置讀取 stage,並在該處裁決新讀數是否需要執行動畫:
@Environment(\.accessibilityReduceMotion) private var reduceMotion
func publish(_ next: FoldStage) {
guard next != stage else { return }
guard stage.isBend(to: next), !reduceMotion else {
stage = next // 開合、旋轉:由系統處理
return
}
withAnimation(.interpolatingSpring(mass: 0.9, stiffness: 280, damping: 24)) {
stage = next
}
}
下方的每個子檢視均從環境中讀取 stage,而那些因姿態改變佈局的檢視無需瞭解細節即可自然繼承彎折動畫。通過 if let leaves = … { laptop } else { pager } 切換佈局的頁面獲得 SwiftUI 預設的淡入淡出過渡。Flicit 的票根頁面現在在雙向切換中大約耗時 120 毫秒。

在資料來源頭做裁決,切勿在各個子檢視中處理。如果使用針對姿態值的 .animation(_:value:),它會在姿態每次變化時觸發,包括手機在彎折狀態下發生旋轉,此時動畫會疊加在系統旋轉之上。Flicit 從書本形態旋轉至筆電形態的螢幕錄影顯示,筆記本佈局在旋轉結束時單步就位,這正是正確的表現。
在首幀繪製已解碼的影像
淡入淡出暴露了硬切所掩蓋的第二個瑕疵。切入的新佈局是全新建置的,其影像起初為空白。即使圖片早已解碼完成,非同步載入的圖片檢視也會展示一幀或多幀的佔位圖,然後才淡入真實影像。在 Flicit 的螢幕錄影中,每次彎折時票根副券黑螢幕、電影海報灰螢幕均持續了 60 到 100 毫秒。
將圖片檢視最後繪製的點陣圖儲存在一份可同步讀取的小快取中,在展示佔位圖前先查快取:
- 優先按完全匹配的請求查詢:相同檔案、相同像素尺寸與縮放倍率解碼所得像素完全一致,新檢視在首幀即可將其繪製出來,不會露出任何破綻。
- 其次按圖片查詢:同張圖片的已有最大解碼圖可作為墊底圖,只要其尺寸達到所需尺寸的一半以上。尚未繪製過的尺寸(如手機首次彎折時的副券)否則依然會閃爍。短暫的輕微模糊遠勝於露出一塊佔位圖。
- 讓檢視自身的非同步載入照常進行,並在完成後無淡入地替換墊底圖,因為兩者是同一張圖。
如果業務邏輯需要讀取點陣圖像素,需校驗其在墊底圖下依然正常運作。Flicit 的海報票根標籤顏色取自海報在 24×24 像素下的取樣,因此同張圖片在不同尺寸下能得到相同的色彩;螢幕錄影證明當精準解碼替換墊底圖時,標籤顏色沒有發生任何跳變。有了這套快取,Flicit 螢幕錄影中的任何切換無論首次還是後續均不再露出佔位圖。
這不僅對摺疊螢幕有益,對整個 App 都有幫助:任何因已在螢幕上的圖片而重建的檢視都能立即繪製出內容。
當淡入淡出閃爍時,改用硬切
Flicit 的電影詳情頁並不能像票根頁那樣優雅地淡入淡出。其退出的舊佈局在單幀內直接消失而不是漸變褪去;展平手機時,切入的新頁面則在裸白紙底上淡入約 40 毫秒(兩到三幀的空白頁)。改用提前展現新頁面的 Ease-Out 曲線也無法消除這一空白。該頁面的兩套佈局均基於 ScrollView 建置,而票根頁的手動排版 Pager 卻能正常漸隱漸顯;螢幕錄影所證止於此,更深的原因暫未探明。
空白閃爍比硬切更難以忍受。電影詳情頁退出了彎折動畫,在海報早已繪製完成的前提下以單幀利落硬切:
.transaction(value: isLaptopLayout) { $0.animation = nil }
務必為每一個因姿態切換佈局的頁面逐一螢幕錄影。一個頁面的淡入淡出正常,並不保證下一個頁面也是如此。
跟隨轉軸連續運動的動效
上述所有內容均屬於兩個狀態之間的離散切換。有些動效則應持續跟隨轉軸運動:隨著立起半螢幕一同傾斜的表單、隨彎折翹起的票根副券。這正是轉軸角度的用途。根據 Gubarenko 收集的論壇回覆,系統對 onHingeChange 不保證任何“更新頻率、角度增量、範圍或精度”,因此應將每次更新視為狀態變更,而非動畫的逐幀驅動源。在動效需要保持平滑的地方,另一條論壇回覆建議“朝著最新角度驅動一個彈簧動畫”。Flicit 通過柔和的彈簧使立起半螢幕上的票根副券隨角度傾斜數度,並在“減弱動態效果”開啟時保持垂直。轉軸指南詳細介紹了將角度讀作位置、速度或捕捉點(Snap)的方案。
保持使用者的瀏覽位置與狀態
動效是姿態變換可見的一半;另一半則是確保使用者狀態不丟失。論壇回覆與小組實驗室(Group Labs)對此觀點一致。在幾何形態變化期間保持檢視標識(View Identity)與資料流穩定。不要防抖:將“保留區域、安全區域與邊界”視為“聲明式 UI 的普通輸入”。並警惕基於 Size Class 的 if 分支,“除非將共享狀態提升至這些分支之上,否則在螢幕切換期間可能會銷燬區域性檢視狀態”。開源專案 meow-ios 正是出於此原因將一個分割槽的狀態提升到穩定的父級:該分割槽在摺疊時改變了容器,若不提升會導致狀態重置並意外彈出已壓棧的頁面。
Flicit 的電影詳情頁展示了切換佈局的侷限:其兩套佈局屬於不同的 ScrollView,因此切換前滾動的瀏覽位置無法帶入新佈局。
逐幀螢幕錄影驗證
截圖只能顯示佈局最終落地的形態。它們無法展現跳變、空白幀、佔位圖或意外漏出的遊走動效,而本文列出的所有瑕疵均集中在這些瞬間。AI 代理指南的工具包可通過指令碼驅動模擬器彎折,並錄製其中一塊螢幕:
python3 duo.py orient upright
python3 duo.py record --out films/ --name laptop path:1.0:180:110 wait:2.5 path:1.0:110:180 wait:2 # 進入筆電形態並返回展平
ffmpeg -i films/laptop-inner.mp4 -fps_mode passthrough -enc_time_base 1/1000 -frame_pts 1 frames/%06d.png
錄製工具僅在螢幕發生變化時才輸出幀,因此需以直通模式提取原汁原味的幀;每個檔案以其毫秒時間戳命名。要找到值得審查的關鍵幀,將每幀與前一幀比對並按差異大小排序。單次大幅度差異是硬切,一連串細微差異則是淡入淡出:
prev=; for f in frames/*.png; do
[ -n "$prev" ] && echo "$f $(compare -metric RMSE "$prev" "$f" null: 2>&1)"; prev=$f
done | sort -t'(' -k2 -g -r | head
然後檢視每個關鍵時刻附近的畫面。重點排查:系統模糊褪去後仍在位移的佈局、原本有圖片的地方閃出佔位圖、兩套佈局之間的空白幀,以及在旋轉過程中發生位移的任何元素。在修改任何程式碼之前也先錄製一段,這樣你才能分清哪些動效是系統自帶的。
模擬器的兩項特性在這裡十分關鍵。摺痕在轉軸停止大約一秒後才變為啟用狀態,因此在螢幕錄影中佈局切換顯示在彎折之後,而非彎折途中;在真機上很可能會在彎折中途、手機仍在手中轉動時觸發。而在旋轉之後,保留區域可能需要數秒才能收斂穩定:Flicit 的一段螢幕錄影在結束時手機已轉回橫向但仍顯示筆記本佈局,數秒後的靜態截圖則恢復正常。在認定螢幕錄影所見為 Bug 之前,請務必給錄製留出足夠的等待尾部。
速查表
| 姿態變換 | 驅動來源 | App 的應對策略 |
|---|---|---|
| 合上 ↔ 展開 | 系統高斯模糊快照 | 單步落地新佈局;保留使用者所在位置 |
| 橫置 ↔ 立起 | 系統螢幕旋轉 | 同樣單步就位;切勿繫結姿態動畫 |
| 平放 ↔ 半折 | 無:App 的變化完全可見 | 儘可能不產生位移;否則為佈局切換做動畫 |
| 轉軸運動中 | 無 | 僅做視覺特效,通過彈簧平滑跟隨角度 |
| 目標 | 可行的方案 |
|---|---|
| 讓書本形態不產生二次位移 | 從一開始便將間距對齊在未啟用摺痕保留區域(.includeInactive)之上 |
| 準確識別彎折而非其他變換 | 摺痕在啟用或靜止間切換,且視窗(檢視加安全邊距)尺寸保持不變 |
| 為彎折新增動畫 | 在發布 stage 的源頭做裁決:僅為彎折包裹 withAnimation,“減弱動態效果”時不執行動畫 |
| 讓兩套佈局優雅淡入淡出 | 針對姿態使用 if/else;彎折事務將賦予其預設的漸變過渡 |
| 新佈局首幀不閃現佔位圖 | 在首幀繪製快取點陣圖:完全匹配的請求優先,否則使用尺寸達標一半以上的已解碼圖 |
| 淡入淡出會閃爍的頁面 | .transaction(value:) { $0.animation = nil }:改用硬切,並提前備好已解碼點陣圖 |
| 排查螢幕錄影中的跳變時刻 | 使用 -fps_mode passthrough 匯出幀,再用 compare -metric RMSE 逐幀比對 |
本指南的侷限
本文全部內容均在 Apple 的 iPhone Duo 模擬器(iOS 27.1,版本號 24A94232)中錄製,而 iPhone Duo 定於 10 月 23 日發售。真機上的摺疊系統動效與模擬器的模糊效果可能會有出入,摺痕可能在彎折中途而非停止後一秒啟用,因此在螢幕錄影上調優的淡入淡出在真機手持體驗中可能會顯得偏快或多餘。電影詳情頁的滾動檢視為何會在單幀內直接脫落而不是漸隱漸褪,深層原因暫未探明。文中的時間資料均來自單一 App 在單臺 Mac 上的實測:票根頁淡入淡出耗時 120 毫秒,修復前佔位圖閃爍持續 60 到 100 毫秒。
繼續閱讀

iPhone Duo App 創意:開發者正在用摺疊螢幕做的 6 件事
300 多個 iPhone Duo 示範、概念和原型,歸納為反覆出現的六種模式,從半折的筆記型電腦形態到把轉軸當控制器。如果你正在考慮做點什麼,可以從這裡開始。

iPhone Duo 分割畫面:兩個 App 並排使用
iPhone Duo 是第一臺能同時顯示兩個 App 的 iPhone。分割畫面怎麼用、有哪些限制,以及哪些 App 為共享螢幕做了設計。

iPhone Duo 姿態:合上、展開、書本、立起與分割畫面
用大白話講清楚 iPhone Duo 的幾種拿法、手機本身在每種姿態下會做什麼,以及最能發揮它們的 App 和創意。寫給準備購買的人,也寫給為它開發的人。