mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-05-11 07:04:02 +08:00
feat: add animationTypeForReplace option (#297)
Currently, when a screen is replaced the new screen comes into focus with a push animation. However, sometimes you might want to customize how the animation looks like. For example, when the user logs out, animating out the previous screen like pop feels more natural than doing a push animation with the sign in screen. The PR adds a new `animationTypeForReplace` option to control this. Specifying `animationTypeForReplace: 'pop'` will pop the previous screen, otherwise the new screen will be pushed like before. Co-authored-by: Michał Osadnik <micosa97@gmail.com>
This commit is contained in:
@@ -73,6 +73,7 @@ type Props = {
|
||||
|
||||
type State = {
|
||||
isLoading: boolean;
|
||||
isSignout: boolean;
|
||||
userToken: undefined | string;
|
||||
};
|
||||
|
||||
@@ -94,17 +95,20 @@ export default function SimpleStackScreen({ navigation }: Props) {
|
||||
case 'SIGN_IN':
|
||||
return {
|
||||
...prevState,
|
||||
isSignout: false,
|
||||
userToken: action.token,
|
||||
};
|
||||
case 'SIGN_OUT':
|
||||
return {
|
||||
...prevState,
|
||||
isSignout: true,
|
||||
userToken: undefined,
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading: true,
|
||||
isSignout: false,
|
||||
userToken: undefined,
|
||||
}
|
||||
);
|
||||
@@ -147,7 +151,10 @@ export default function SimpleStackScreen({ navigation }: Props) {
|
||||
) : state.userToken === undefined ? (
|
||||
<SimpleStack.Screen
|
||||
name="SignIn"
|
||||
options={{ title: 'Sign in' }}
|
||||
options={{
|
||||
title: 'Sign in',
|
||||
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
|
||||
}}
|
||||
component={SignInScreen}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -295,6 +295,11 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
* If you set it to `false`, the screen won't animate when pushing or popping. Defaults to `true`.
|
||||
*/
|
||||
animationEnabled?: boolean;
|
||||
/**
|
||||
* The type of animation to use when this screen replaces another screen. Defaults to `push`.
|
||||
* When `pop` is used, the `pop` animation is applied to the screen being replaced.
|
||||
*/
|
||||
animationTypeForReplace?: 'push' | 'pop';
|
||||
/**
|
||||
* Whether you can use gestures to dismiss this screen. Defaults to `true` on iOS, `false` on Android.
|
||||
*/
|
||||
|
||||
@@ -96,6 +96,12 @@ class StackView extends React.Component<Props, State> {
|
||||
return descriptor ? descriptor.options.animationEnabled !== false : true;
|
||||
};
|
||||
|
||||
const getAnimationTypeForReplace = (key: string) => {
|
||||
const descriptor = props.descriptors[key] || state.descriptors[key];
|
||||
|
||||
return descriptor.options.animationTypeForReplace ?? 'push';
|
||||
};
|
||||
|
||||
if (
|
||||
previousFocusedRoute &&
|
||||
previousFocusedRoute.key !== nextFocusedRoute.key
|
||||
@@ -125,23 +131,41 @@ class StackView extends React.Component<Props, State> {
|
||||
if (!routes.find(r => r.key === previousFocusedRoute.key)) {
|
||||
// The previous focused route isn't present in state, we treat this as a replace
|
||||
|
||||
replacingRouteKeys = [
|
||||
...replacingRouteKeys,
|
||||
previousFocusedRoute.key,
|
||||
];
|
||||
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
);
|
||||
closingRouteKeys = closingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
);
|
||||
|
||||
// Keep the old route in state because it's visible under the new route, and removing it will feel abrupt
|
||||
// We need to insert it just before the focused one (the route being pushed)
|
||||
// After the push animation is completed, routes being replaced will be removed completely
|
||||
routes = routes.slice();
|
||||
routes.splice(routes.length - 1, 0, previousFocusedRoute);
|
||||
if (getAnimationTypeForReplace(nextFocusedRoute.key) === 'pop') {
|
||||
closingRouteKeys = [
|
||||
...closingRouteKeys,
|
||||
previousFocusedRoute.key,
|
||||
];
|
||||
|
||||
// By default, new routes have a push animation, so we add it to `openingRouteKeys` before
|
||||
// But since user configured it to animate the old screen like a pop, we need to add this without animation
|
||||
// So remove it from `openingRouteKeys` which will remove the animation
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
key => key !== nextFocusedRoute.key
|
||||
);
|
||||
|
||||
// Keep the route being removed at the end to animate it out
|
||||
routes = [...routes, previousFocusedRoute];
|
||||
} else {
|
||||
replacingRouteKeys = [
|
||||
...replacingRouteKeys,
|
||||
previousFocusedRoute.key,
|
||||
];
|
||||
|
||||
closingRouteKeys = closingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
);
|
||||
|
||||
// Keep the old route in the state because it's visible under the new route, and removing it will feel abrupt
|
||||
// We need to insert it just before the focused one (the route being pushed)
|
||||
// After the push animation is completed, routes being replaced will be removed completely
|
||||
routes = routes.slice();
|
||||
routes.splice(routes.length - 1, 0, previousFocusedRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!routes.find(r => r.key === previousFocusedRoute.key)) {
|
||||
@@ -151,10 +175,10 @@ class StackView extends React.Component<Props, State> {
|
||||
isAnimationEnabled(previousFocusedRoute.key) &&
|
||||
!closingRouteKeys.includes(previousFocusedRoute.key)
|
||||
) {
|
||||
// Sometimes a route can be closed before the opening animation finishes
|
||||
// So we also need to remove it from the opening list
|
||||
closingRouteKeys = [...closingRouteKeys, previousFocusedRoute.key];
|
||||
|
||||
// Sometimes a route can be closed before the opening animation finishes
|
||||
// So we also need to remove it from the opening list
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
);
|
||||
@@ -185,7 +209,9 @@ class StackView extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
if (!routes.length) {
|
||||
throw new Error(`There should always be at least one route.`);
|
||||
throw new Error(
|
||||
'There should always be at least one route in the navigation state.'
|
||||
);
|
||||
}
|
||||
|
||||
const descriptors = routes.reduce<StackDescriptorMap>((acc, route) => {
|
||||
|
||||
Reference in New Issue
Block a user