交互式手势返回与 FlatList
真实业务中的信息流往往将虚拟化滚动列表(FlatList 或 FlashList)与手势返回(iOS 左边缘侧滑返回或垂直下拉关闭)结合在一起。react-native-shared-hero 在原生层对这两类场景提供了专门支持。
1. iOS 交互式左边缘侧滑返回
在 iOS 上,当用户从屏幕左边缘向右拖动以弹出 UINavigationController 页面(interactivePopGestureRecognizer)时:
- 手势开始瞬间,原生协调器会自动识别当前活跃的
UIGestureRecognizer与UINavigationController.topViewController?.transitionCoordinator。 - 协调器不会立即运行固定计时器动画,而是进入交互式手势驱动模式(interactive driver mode)。
- 随着手指在屏幕上滑动,飞行克隆节点的位置、缩放比例、圆角半径与透明度混合会 1:1 实时跟随手指位移百分比。
- 当手指抬起时:
- 确认返回(Commit):克隆节点在剩余时间内(约
220 ms)从松手位置平滑飞入列表源单元格。 - 取消返回(手指在左边缘附近松开):克隆节点平滑飞回详情页原位置并恢复详情页真实视图显示。
- 确认返回(Commit):克隆节点在剩余时间内(约
你无需编写任何额外代码即可在 iOS 上获得交互式侧滑返回支持,只要系统侧滑返回手势触发,它就会自动生效。
2. 自定义拖拽关闭手势衔接(Pan-to-dismiss)
许多图片与视频应用允许用户在详情页垂直拖动图片进行缩放关闭(类似 Instagram 或 Apple 相册)。
当你在 PanResponder 或 react-native-gesture-handler 中通过 transform: [{ translateX }, { translateY }, { scale }] 实时拖拽 <SharedHero> 时:
| Android | iOS |
|---|---|
tsx
<Animated.View
{...panResponder.panHandlers}
style={{
transform: [
{ translateX: dragX },
{ translateY: dragY },
{ scale: dragScale },
],
}}
>
<SharedHero
id={`photo-${item.id}`}
mode="morph"
spring={{ damping: 24, stiffness: 260, mass: 1 }}
style={{ borderRadius: 20, overflow: 'hidden' }}
>
<Image source={{ uri: item.fullUrl }} style={styles.heroImage} />
</SharedHero>
</Animated.View>当拖动距离超过阈值并调用 navigation.goBack() 时:
- 在卸载前一刻,原生
SharedHeroView会测量包含当前 transform 矩阵在内的实时窗口坐标框(即手指松开瞬间的真实偏移与缩放尺寸),而不是静态初始布局框。 - 返程飞行直接从松手位置无缝接力并飞回列表单元格,不会出现先闪回屏幕中央再飞行的跳变。
3. 虚拟化列表(FlatList 与 FlashList)
虚拟列表在滚动时会不断复用原生组件实例。如果没有专门处理,当一个复用单元格的 id 从 "photo-1" 变为 "photo-19" 时,"photo-1" 的注册桶可能会错误地指向正在展示第 19 项内容的视图。
| Android | iOS |
|---|---|
react-native-shared-hero 在原生层对虚拟列表做了三层保护:
- 属性变更时自动清理旧
id:当复用视图上的heroId发生变化时,HeroRegistry会先将该视图从旧id桶中移除并清理其旧快照,然后再注册到新id下。 - 严格受限的 LRU 快照缓存:
HeroRegistry.lastKnownSnapshots采用容量受限的 LRU 缓存池(SnapshotLRU)。即使列表滚动浏览上千条数据,最早的位图也会被自动淘汰,内存占用始终保持平稳。 - 脏检查按需刷新(Dirty check):仅当视图的
bounds尺寸或子视图数量实际发生变化时才重新捕获快照,避免在列表惯性滚动期间产生多余的位图渲染。
FlatList 最佳实践
- 始终提供稳定的
keyExtractor:使用数据项的唯一 ID(keyExtractor={(item) => item.id}),不要使用数组下标index。 - 详情页横向翻页后同步滚动列表:如果用户在详情页内左右滑动切换了当前图片(例如从第 1 张滑到了第 8 张),在返回前调用
flatListRef.current?.scrollToIndex({ index, animated: false }),确保第 8 张对应的源<SharedHero>已挂载在当前窗口中。 - 源视图滚出屏幕时的安全降级:如果返回时源
<SharedHero>尺寸为 0 或已不在窗口中,react-native-shared-hero会安全跳过本次飞行并正常恢复页面显示,绝不会崩溃或导致视图卡在隐藏状态。