如何解决react-navigation-shared-element在FlatList滑动后的转场同步问题?
问题核心
你遇到的问题本质是:sharedElements 函数依赖路由参数同步当前图片ID时,更新参数会触发组件不必要重渲染;但如果不同步,返回时转场仍关联初始点击的图片,而非当前FlatList显示的图片。
解决方案:用React Context管理活跃元素ID
通过Context存储当前可视图片的ID,让sharedElements直接读取Context值,无需依赖路由参数更新,从根源避免重渲染问题。
1. 创建共享元素状态Context
// SharedElementContext.js import React, { createContext, useContext, useState } from 'react'; const SharedElementContext = createContext(); export function SharedElementProvider({ children }) { const [activeImageId, setActiveImageId] = useState(null); return ( <SharedElementContext.Provider value={{ activeImageId, setActiveImageId }}> {children} </SharedElementContext.Provider> ); } export function useSharedElementContext() { return useContext(SharedElementContext); }
2. 在导航容器外层包裹Context Provider
确保导航栈内的页面都能访问到Context状态:
// App.js import { SharedElementProvider } from './SharedElementContext'; import { NavigationContainer } from '@react-navigation/native'; import AppStack from './AppStack'; export default function App() { return ( <SharedElementProvider> <NavigationContainer> <AppStack /> </NavigationContainer> </SharedElementProvider> ); }
3. 全屏FlatList页面跟踪可视元素并更新Context
利用onViewableItemsChanged监听当前显示的图片,同步ID到Context:
// FullScreenImageScreen.js import React, { useCallback, useState } from 'react'; import { FlatList, Image } from 'react-native'; import { SharedElement } from 'react-navigation-shared-element'; import { useSharedElementContext } from './SharedElementContext'; const FullScreenImageScreen = ({ route }) => { const { images, initialImageId } = route.params; const { setActiveImageId } = useSharedElementContext(); const viewabilityConfig = useState({ viewAreaCoveragePercentThreshold: 60 })[0]; // 监听可视元素变化,更新当前活跃图片ID const onViewableItemsChanged = useCallback(({ viewableItems }) => { if (viewableItems.length > 0) { setActiveImageId(viewableItems[0].item.id); } }, [setActiveImageId]); // 初始进入时同步初始图片ID React.useEffect(() => { setActiveImageId(initialImageId); }, [initialImageId, setActiveImageId]); return ( <FlatList horizontal pagingEnabled data={images} keyExtractor={item => item.id} viewabilityConfig={viewabilityConfig} onViewableItemsChanged={onViewableItemsChanged} renderItem={({ item }) => ( <SharedElement id={`image-${item.id}`}> <Image source={{ uri: item.url }} style={{ width: '100%', height: '100%' }} resizeMode="contain" /> </SharedElement> )} /> ); }; export default FullScreenImageScreen;
4. 修改导航栈的sharedElements配置
直接从Context读取当前活跃图片ID,替代路由参数:
// AppStack.js import { createStackNavigator } from '@react-navigation/stack'; import { useSharedElementContext } from './SharedElementContext'; import GridScreen from './GridScreen'; import FullScreenImageScreen from './FullScreenImageScreen'; const Stack = createStackNavigator(); export default function AppStack() { const { activeImageId } = useSharedElementContext(); return ( <Stack.Navigator> <Stack.Screen name="Grid" component={GridScreen} /> <Stack.Screen name="FullScreen" component={FullScreenImageScreen} options={({ route }) => ({ sharedElements: () => { // 优先用Context中的当前ID, fallback到初始ID const targetId = activeImageId || route.params.initialImageId; return [`image-${targetId}`]; }, })} /> </Stack.Navigator> ); }
5. 网格页的共享元素配置
确保网格项的SharedElementID与全屏页一致:
// GridScreen.js import { TouchableOpacity, Image, StyleSheet } from 'react-native'; import { SharedElement } from 'react-navigation-shared-element'; // 网格项渲染函数 const renderGridItem = ({ item, navigation }) => ( <TouchableOpacity onPress={() => navigation.navigate('FullScreen', { images: yourImageList, initialImageId: item.id, })} > <SharedElement id={`image-${item.id}`}> <Image source={{ uri: item.thumbnailUrl }} style={styles.gridImage} resizeMode="cover" /> </SharedElement> </TouchableOpacity> ); const styles = StyleSheet.create({ gridImage: { width: '100%', height: 150, borderRadius: 8, }, });
方案优势
- 避免了通过
navigation.setParams更新路由参数导致的组件重渲染 - Context状态仅在FlatList可视元素变化时更新,性能开销极低
- 转场逻辑始终与当前显示的图片保持同步,完全匹配iOS相册的交互体验
内容的提问来源于stack exchange,提问作者resistor
相关产品推荐
相关产品推荐

