Stack、弹窗、半屏面板与标签页
由于 react-native-shared-hero 将飞行克隆节点渲染在窗口级浮层(OverlayHost 在 iOS 上使用 UIWindow.Level.alert + 1 的独立 UIWindow,在 Android 上使用 DecorView.overlay),它不受限于单一导航容器,几乎可以跨越所有页面呈现形式。
1. 原生栈导航(@react-navigation/native-stack / Expo Router)
对于标准的推入/弹出转场,建议为详情页设置如下导航选项,使背景转场与共享元素飞行相得益彰:
<Stack.Screen
name="Detail"
component={DetailScreen}
options={Platform.select({
ios: {
animation: 'fade',
headerTransparent: true,
},
default: {
headerTransparent: true,
},
})}
/>为什么只在 iOS 上设置 animation: 'fade'?
- iOS:iOS 默认的 Stack 推入会将整个页面从右向左平移,设置
animation: 'fade'可以让背景柔和淡化,将视觉焦点留给共享元素。 - Android:在
react-native-screens中,animation: 'fade'在返回时使用较短的 XML 淡出动画,会在 320 ms 的返程飞行结束前提前卸载目标 Fragment 视图树。保留 Android 默认的 Stack 转场动画能让往返过程都保持流畅。
2. 原生 Modal 与透明弹窗(transparentModal)
当使用 presentation: 'modal' 或 presentation: 'transparentModal' 打开新路由时,底层的源页面依然保持挂载状态,不会被卸载。
HeroRegistry 通过为每个 namespace::id 维护有序挂载列表(live[key])自动处理这种场景:
- 当弹窗挂载第二个
<SharedHero id="item-1">时,HeroRegistry将弹窗中新挂载的视图作为目标端,将底层列表页上的视图作为源端进行配对。 - 当弹窗关闭(点击关闭按钮或下拉手势关闭)时,弹窗内的视图注销并自动飞回底层列表页中仍在挂载的源视图。
原生 Modal(presentation: 'modal')
| Android | iOS |
|---|---|
透明弹窗(presentation: 'transparentModal')
| Android | iOS |
|---|---|
<Stack.Screen
name="QuickPreviewModal"
component={QuickPreviewModal}
options={{
presentation: 'transparentModal',
animation: 'fade',
headerShown: false,
}}
/>3. 半屏面板与 Bottom Sheet(presentation: 'formSheet')
在 iOS 15+ 上,react-native-screens 支持基于系统 UISheetPresentationController 的原生半屏面板(presentation: 'formSheet' 配合 sheetAllowedDetents)。
传统的视图树内 Portal 方案在飞出半屏面板顶部圆角边界时会被裁切;而 OverlayHost 的 UIWindow 层级(.alert + 1)高于半屏面板容器,共享元素可以直接从背景页面飞入半屏面板当前的停靠高度(detent),且 InteractiveModalReturn 会自动跟踪半屏面板的下拉关闭手势。
| Android | iOS |
|---|---|
<Stack.Screen
name="ProductSheet"
component={ProductSheetScreen}
options={{
presentation: 'formSheet',
sheetAllowedDetents: [0.55, 1.0],
sheetGrabberVisible: true,
headerShown: false,
}}
/>4. React Native 内置 <Modal>
你甚至不需要使用导航库就能触发跨窗口的共享元素过渡。React Native 内置的 <Modal> 会在独立的原生窗口中挂载内容(iOS 为 RCTModalHostView 的 UIWindow,Android 为独立的 Dialog Window)。
OverlayHost 在双端均已自动适配:
- iOS:
OverlayHost的窗口层级(UIWindow.Level.alert + 1)位于RCTModalHostView窗口之上。 - Android:
OverlayHost.resolveOverlay(view)会直接从目标视图自身的rootView(DecorView)解析ViewGroupOverlay。打开<Modal>时,去程飞行渲染在Dialog自身的DecorView.overlay上(位于弹窗内容上方);关闭<Modal>时,返程飞行渲染在宿主 Activity 的DecorView.overlay上。
| Android | iOS |
|---|---|
<Modal
visible={activeItem != null}
transparent
animationType="slide"
statusBarTranslucent
navigationBarTranslucent
onRequestClose={() => setActiveItem(null)}
>
{activeItem ? (
<View style={styles.modalRoot}>
<SharedHero
id={`core-modal-${activeItem.id}`}
namespace="core-modal"
mode="snapshot"
duration={380}
style={styles.hero}
>
<Image source={{ uri: activeItem.uri }} style={styles.fill} />
</SharedHero>
</View>
) : null}
</Modal>5. 标签页与多级连续跳转(Multi-step)
从标签页推入详情页
为标签页中的卡片指定 namespace="tabs",并在详情页使用相同的 namespace="tabs":
| Android | iOS |
|---|---|
多级连续跳转(A -> B -> C)
可以在多个连续推入的页面(如 列表页 -> 概览页 -> 全屏大图页)之间通过匹配的 id 串联起每一步的共享元素飞行:
| Android | iOS |
|---|---|