Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add support for custom screen transitions with native-stack v7#12225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
maciekstosio wants to merge20 commits intomain
base:main
Choose a base branch
Loading
from@maciekstosio/use-context-instead-of-passing-screenRefs

Conversation

maciekstosio
Copy link
Contributor

@maciekstosiomaciekstosio commentedNov 4, 2024
edited
Loading

Continuation of#12204 and#11943. Changes from original version:

In general, the idea suggested by@satya164 was that we could move more logic into react-native-screens and avoid leaking it to@react-navigation.

Original description:

Motivation

This PR intents to add implementation of custom screen transitions, recently added in react-native-screens. You can find more about this featurehere.

Test plan

Go to the code of the native stack in example (NativeStack.tsx) and wrap whole navigator with (import fromreact-native-screens/gesture-handler). After that, usegestureType prop on the desired screen.

Ready to paste code 🍝
/* eslint-disable camelcase */import*asReactfrom'react';import{Animated,Platform,StyleSheet,ViewProps,ViewStyle,}from'react-native';//@ts-ignore Getting private component// eslint-disable-next-line import/no-named-as-default, import/default, import/no-named-as-default-member, import/namespaceimportAppContainerfrom'react-native/Libraries/ReactNative/AppContainer';importwarnOncefrom'warn-once';import{StackPresentationTypes,ScreensRefsHolder}from'../../types';importScreenStackfrom'../../components/ScreenStack';importScreenContentWrapperfrom'../../components/ScreenContentWrapper';import{ScreenContext}from'../../components/Screen';import{ParamListBase,StackActions,StackNavigationState,useTheme,Route,NavigationState,PartialState,}from'@react-navigation/native';import{useSafeAreaFrame,useSafeAreaInsets,}from'react-native-safe-area-context';import{NativeStackDescriptorMap,NativeStackNavigationHelpers,NativeStackNavigationOptions,}from'../types';importHeaderConfigfrom'./HeaderConfig';importSafeAreaProviderCompatfrom'../utils/SafeAreaProviderCompat';importgetDefaultHeaderHeightfrom'../utils/getDefaultHeaderHeight';importgetStatusBarHeightfrom'../utils/getStatusBarHeight';importHeaderHeightContextfrom'../utils/HeaderHeightContext';importAnimatedHeaderHeightContextfrom'../utils/AnimatedHeaderHeightContext';importFooterComponentfrom'./FooterComponent';constisAndroid=Platform.OS==='android';letContainer=ScreenContentWrapper;if(__DEV__){constDebugContainer=(props:ViewProps&{stackPresentation:StackPresentationTypes},)=>{const{ stackPresentation, ...rest}=props;if(Platform.OS==='ios'&&stackPresentation!=='push'&&stackPresentation!=='formSheet'){return(<AppContainer><ScreenContentWrapper{...rest}/></AppContainer>);}return<ScreenContentWrapper{...rest}/>;};//@ts-ignore Wrong propsContainer=DebugContainer;}constMaybeNestedStack=({  options,  route,  stackPresentation,  children,  internalScreenStyle,}:{options:NativeStackNavigationOptions;route:Route<string>;stackPresentation:StackPresentationTypes;children:React.ReactNode;internalScreenStyle?:Pick<ViewStyle,'backgroundColor'>;})=>{const{ colors}=useTheme();const{ headerShown=true, contentStyle}=options;constScreen=React.useContext(ScreenContext);constisHeaderInModal=isAndroid    ?false    :stackPresentation!=='push'&&headerShown===true;constheaderShownPreviousRef=React.useRef(headerShown);React.useEffect(()=>{warnOnce(!isAndroid&&stackPresentation!=='push'&&headerShownPreviousRef.current!==headerShown,`Dynamically changing 'headerShown' in modals will result in remounting the screen and losing all local state. See options for the screen '${route.name}'.`,);headerShownPreviousRef.current=headerShown;},[headerShown,stackPresentation,route.name]);constcontent=(<Containerstyle={[stackPresentation==='formSheet'          ?Platform.OS==='ios'            ?styles.absoluteFillNoBottom            :null          :styles.container,stackPresentation!=='transparentModal'&&stackPresentation!=='containedTransparentModal'&&{backgroundColor:colors.background,},contentStyle,]}//@ts-ignore Wrong props passed to ViewstackPresentation={stackPresentation}// This view must *not* be flattened.// See https://github.com/software-mansion/react-native-screens/pull/1825// for detailed explanation.collapsable={false}>{children}</Container>);constdimensions=useSafeAreaFrame();consttopInset=useSafeAreaInsets().top;constisStatusBarTranslucent=options.statusBarTranslucent??false;conststatusBarHeight=getStatusBarHeight(topInset,dimensions,isStatusBarTranslucent,);consthasLargeHeader=options.headerLargeTitle??false;constheaderHeight=getDefaultHeaderHeight(dimensions,statusBarHeight,stackPresentation,hasLargeHeader,);if(isHeaderInModal){return(<ScreenStackstyle={styles.container}><ScreenenabledisNativeStackhasLargeHeader={hasLargeHeader}style={[StyleSheet.absoluteFill,internalScreenStyle]}><HeaderHeightContext.Providervalue={headerHeight}><HeaderConfig{...options}route={route}/>{content}</HeaderHeightContext.Provider></Screen></ScreenStack>);}returncontent;};typeNavigationRoute<ParamListextendsParamListBase,RouteNameextendskeyofParamList,>=Route<Extract<RouteName,string>,ParamList[RouteName]>&{state?:NavigationState|PartialState<NavigationState>;};constRouteView=({  descriptors,  route,  index,  navigation,  stateKey,  screensRefs,}:{descriptors:NativeStackDescriptorMap;route:NavigationRoute<ParamListBase,string>;index:number;navigation:NativeStackNavigationHelpers;stateKey:string;screensRefs:React.MutableRefObject<ScreensRefsHolder>;})=>{const{ options,render:renderScene}=descriptors[route.key];const{    fullScreenSwipeShadowEnabled=false,    gestureEnabled,    headerShown,    hideKeyboardOnSwipe,    homeIndicatorHidden,    sheetLargestUndimmedDetentIndex='none',    sheetGrabberVisible=false,    sheetCornerRadius=-1.0,    sheetElevation=24,    sheetExpandsWhenScrolledToEdge=true,    sheetInitialDetentIndex=0,    nativeBackButtonDismissalEnabled=false,    navigationBarColor,    navigationBarTranslucent,    navigationBarHidden,    replaceAnimation='pop',    screenOrientation,    statusBarAnimation,    statusBarColor,    statusBarHidden,    statusBarStyle,    statusBarTranslucent,    swipeDirection='horizontal',    transitionDuration,    freezeOnBlur,    unstable_sheetFooter=null,    contentStyle,}=options;let{    sheetAllowedDetents=[1.0],    customAnimationOnSwipe,    fullScreenSwipeEnabled,    gestureResponseDistance,    stackAnimation,    stackPresentation='push',}=options;// We take backgroundColor from contentStyle and apply it on Screen.// This allows to workaround one issue with truncated// content with formSheet presentation.letinternalScreenStyle;if(stackPresentation==='formSheet'&&contentStyle){constflattenContentStyles=StyleSheet.flatten(contentStyle);internalScreenStyle={backgroundColor:flattenContentStyles?.backgroundColor,};}if(sheetAllowedDetents==='fitToContents'){sheetAllowedDetents=[-1];}if(swipeDirection==='vertical'){// for `vertical` direction to work, we need to set `fullScreenSwipeEnabled` to `true`// so the screen can be dismissed from any point on screen.// `customAnimationOnSwipe` needs to be set to `true` so the `stackAnimation` set by user can be used,// otherwise `simple_push` will be used.// Also, the default animation for this direction seems to be `slide_from_bottom`.if(fullScreenSwipeEnabled===undefined){fullScreenSwipeEnabled=true;}if(customAnimationOnSwipe===undefined){customAnimationOnSwipe=true;}if(stackAnimation===undefined){stackAnimation='slide_from_bottom';}}if(index===0){// first screen should always be treated as `push`, it resolves problems with no header animation// for navigator with first screen as `modal` and the next as `push`stackPresentation='push';}constdimensions=useSafeAreaFrame();consttopInset=useSafeAreaInsets().top;constisStatusBarTranslucent=options.statusBarTranslucent??false;conststatusBarHeight=getStatusBarHeight(topInset,dimensions,isStatusBarTranslucent,);consthasLargeHeader=options.headerLargeTitle??false;constdefaultHeaderHeight=getDefaultHeaderHeight(dimensions,statusBarHeight,stackPresentation,hasLargeHeader,);constparentHeaderHeight=React.useContext(HeaderHeightContext);constisHeaderInPush=isAndroid    ?headerShown    :stackPresentation==='push'&&headerShown!==false;conststaticHeaderHeight=isHeaderInPush!==false ?defaultHeaderHeight :parentHeaderHeight??0;// We need to ensure the first retrieved header height will be cached and set in animatedHeaderHeight.// We're caching the header height here, as on iOS native side events are not always coming to the JS on first notify.// TODO: Check why first event is not being received once it is cached on the native side.constcachedAnimatedHeaderHeight=React.useRef(defaultHeaderHeight);constanimatedHeaderHeight=React.useRef(newAnimated.Value(staticHeaderHeight,{useNativeDriver:true,}),).current;constScreen=React.useContext(ScreenContext);const{ dark}=useTheme();constscreenRef=React.useRef(null);React.useEffect(()=>{screensRefs.current[route.key]=screenRef;return()=>{// eslint-disable-next-line @typescript-eslint/no-dynamic-deletedeletescreensRefs.current[route.key];};});return(<Screenkey={route.key}ref={screenRef}enabledisNativeStackhasLargeHeader={hasLargeHeader}style={[StyleSheet.absoluteFill,internalScreenStyle]}sheetAllowedDetents={sheetAllowedDetents}sheetLargestUndimmedDetentIndex={sheetLargestUndimmedDetentIndex}sheetGrabberVisible={sheetGrabberVisible}sheetInitialDetentIndex={sheetInitialDetentIndex}sheetCornerRadius={sheetCornerRadius}sheetElevation={sheetElevation}sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge}customAnimationOnSwipe={customAnimationOnSwipe}freezeOnBlur={freezeOnBlur}fullScreenSwipeEnabled={fullScreenSwipeEnabled}fullScreenSwipeShadowEnabled={fullScreenSwipeShadowEnabled}hideKeyboardOnSwipe={hideKeyboardOnSwipe}homeIndicatorHidden={homeIndicatorHidden}gestureEnabled={isAndroid ?false :gestureEnabled}gestureResponseDistance={gestureResponseDistance}nativeBackButtonDismissalEnabled={nativeBackButtonDismissalEnabled}navigationBarColor={navigationBarColor}navigationBarTranslucent={navigationBarTranslucent}navigationBarHidden={navigationBarHidden}replaceAnimation={replaceAnimation}screenOrientation={screenOrientation}stackAnimation={stackAnimation}stackPresentation={stackPresentation}statusBarAnimation={statusBarAnimation}statusBarColor={statusBarColor}statusBarHidden={statusBarHidden}statusBarStyle={statusBarStyle??(dark ?'light' :'dark')}statusBarTranslucent={statusBarTranslucent}swipeDirection={swipeDirection}transitionDuration={transitionDuration}onHeaderBackButtonClicked={()=>{navigation.dispatch({          ...StackActions.pop(),source:route.key,target:stateKey,});}}onWillAppear={()=>{navigation.emit({type:'transitionStart',data:{closing:false},target:route.key,});}}onWillDisappear={()=>{navigation.emit({type:'transitionStart',data:{closing:true},target:route.key,});}}onAppear={()=>{navigation.emit({type:'appear',target:route.key,});navigation.emit({type:'transitionEnd',data:{closing:false},target:route.key,});}}onDisappear={()=>{navigation.emit({type:'transitionEnd',data:{closing:true},target:route.key,});}}onHeaderHeightChange={e=>{constheaderHeight=e.nativeEvent.headerHeight;if(cachedAnimatedHeaderHeight.current!==headerHeight){// Currently, we're setting value by Animated#setValue, because we want to cache animated value.// Also, in React Native 0.72 there was a bug on Fabric causing a large delay between the screen transition,// which should not occur.// TODO: Check if it's possible to replace animated#setValue to Animated#event.animatedHeaderHeight.setValue(headerHeight);cachedAnimatedHeaderHeight.current=headerHeight;}}}onDismissed={e=>{navigation.emit({type:'dismiss',target:route.key,});constdismissCount=e.nativeEvent.dismissCount>0 ?e.nativeEvent.dismissCount :1;navigation.dispatch({          ...StackActions.pop(dismissCount),source:route.key,target:stateKey,});}}onSheetDetentChanged={e=>{navigation.emit({type:'sheetDetentChange',target:route.key,data:{index:e.nativeEvent.index,isStable:e.nativeEvent.isStable,},});}}onGestureCancel={()=>{navigation.emit({type:'gestureCancel',target:route.key,});}}><AnimatedHeaderHeightContext.Providervalue={animatedHeaderHeight}><HeaderHeightContext.Providervalue={staticHeaderHeight}><MaybeNestedStackoptions={options}route={route}stackPresentation={stackPresentation}internalScreenStyle={internalScreenStyle}>{renderScene()}</MaybeNestedStack>{/* HeaderConfig must not be first child of a Screen.           See https://github.com/software-mansion/react-native-screens/pull/1825           for detailed explanation */}<HeaderConfig{...options}route={route}headerShown={isHeaderInPush}/>{stackPresentation==='formSheet'&&unstable_sheetFooter&&(<FooterComponent>{unstable_sheetFooter()}</FooterComponent>)}</HeaderHeightContext.Provider></AnimatedHeaderHeightContext.Provider></Screen>);};typeProps={state:StackNavigationState<ParamListBase>;navigation:NativeStackNavigationHelpers;descriptors:NativeStackDescriptorMap;};functionNativeStackViewInner({  state,  navigation,  descriptors,}:Props):JSX.Element{const{ key, routes}=state;constcurrentRouteKey=routes[state.index].key;const{ goBackGesture, transitionAnimation, screenEdgeGesture}=descriptors[currentRouteKey].options;constscreensRefs=React.useRef<ScreensRefsHolder>({});return(<ScreenStackstyle={styles.container}goBackGesture={goBackGesture}transitionAnimation={transitionAnimation}screenEdgeGesture={screenEdgeGesture??false}screensRefs={screensRefs}currentScreenId={currentRouteKey}>{routes.map((route,index)=>(<RouteViewkey={route.key}descriptors={descriptors}route={route}index={index}navigation={navigation}stateKey={key}screensRefs={screensRefs}/>))}</ScreenStack>);}exportdefaultfunctionNativeStackView(props:Props){return(<SafeAreaProviderCompat><NativeStackViewInner{...props}/></SafeAreaProviderCompat>);}conststyles=StyleSheet.create({container:{flex:1,},absoluteFill:{position:'absolute',top:0,left:0,right:0,bottom:0,},absoluteFillNoBottom:{position:'absolute',top:0,left:0,right:0,},});

Presentation

Screen.Recording.2024-04-16.at.18.18.51.mov

tbobaand others added19 commitsApril 16, 2024 16:32
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
@maciekstosiomaciekstosio changed the titlerefactore: use context instead of passing screenRefsrefactor!: use context instead of passing screenRefsNov 4, 2024
@github-actionsGitHub Actions
Copy link

@netlifyNetlify
Copy link

netlifybot commentedNov 4, 2024
edited
Loading

Deploy Preview forreact-navigation-example ready!

NameLink
🔨 Latest commitb2419c2
🔍 Latest deploy loghttps://app.netlify.com/sites/react-navigation-example/deploys/672a04cd8c5ac0000839065e
😎 Deploy Previewhttps://deploy-preview-12225--react-navigation-example.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to yourNetlify site configuration.

@maciekstosiomaciekstosio changed the base branch frommain to@maciekstosio/refactor-custom-screen-transitionsNovember 4, 2024 10:39
maciekstosio added a commit to software-mansion/react-native-screens that referenced this pull requestNov 5, 2024
## DescriptionThis PR replaces passing screenRefs into using context. This avoidsunnecessary logic on react-navigation side.## ChangesCreate Context that holds screen refs and add this ref inScreenStackItem instead of doing same on react-navigation part. See thechanges there:react-navigation/react-navigation#12225## TestingSee Example App > Swipe Back Animation (changes from react-navigationrequired:react-navigation/react-navigation#12225)ORreact-navigation: TestScreenAnimation.tsxnative-stack v5: TestScreenAnimationV5.tsx|native-stack v5|react-navigation||-|-||<videosrc="https://github.com/user-attachments/assets/0af1422c-4e61-4c7c-ba6d-1c6a852d4e13"/>|<videosrc="https://github.com/user-attachments/assets/03af7230-198f-4081-ba6d-2b78325ebaa7"/>|## Checklist- [x] Included code example that can be used to test this change- [x] Updated TS types- [ ] Updated documentation: <!-- For adding new props to native-stack-->- [ ]https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md- [ ]https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md- [ ]https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx- [ ]https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx- [ ] Ensured that CI passes
@maciekstosiomaciekstosio changed the base branch from@maciekstosio/refactor-custom-screen-transitions tomainNovember 5, 2024 10:53
@maciekstosiomaciekstosio changed the titlerefactor!: use context instead of passing screenRefsfeat: add support for custom screen transitions with native-stack v7Nov 5, 2024
@maciekstosio
Copy link
ContributorAuthor

maciekstosio commentedNov 5, 2024
edited
Loading

The solution feature works, but the PR is still in draft because I'm getting an error aboutfindHostInstance_DEPRECATED being deprecated in strict mode. And Reanimated not findingRNScreensTurboModule. I'm checking what we can do about it.

CST.x.ReactNavigation.mov

@satya164
Copy link
Member

satya164 commentedNov 5, 2024
edited
Loading

I'm getting an error about findHostInstance_DEPRECATED being deprecated in strict mode

It's a warning, React Native core does not fully support strict mode. Likely unrelated to your changes.

@fobos531
Copy link

Hey@maciekstosio - is there still something blocking this PR from proceeding?

@kkafar
Copy link
Contributor

This should not be merged until we figure out the issue in interaction between gesture handler and RN pressables, described here:software-mansion/react-native-screens#2819

maciekstosio and satya164 reacted with thumbs up emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers
No reviews
Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

5 participants
@maciekstosio@satya164@fobos531@kkafar@tboba

[8]ページ先頭

©2009-2025 Movatter.jp