mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-11 17:34:54 +08:00
Compare commits
6 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
220af93db5 | ||
|
|
1f27e4b1f6 | ||
|
|
9c06a92d09 | ||
|
|
e0e0f79793 | ||
|
|
c7e4bf94e6 | ||
|
|
7024d4bb81 |
@@ -71,13 +71,12 @@ export default function BottomTabsScreen() {
|
|||||||
>
|
>
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
|
component={SimpleStackScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Article',
|
title: 'Article',
|
||||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
|
||||||
</BottomTabs.Screen>
|
|
||||||
<BottomTabs.Screen
|
<BottomTabs.Screen
|
||||||
name="Chat"
|
name="Chat"
|
||||||
component={Chat}
|
component={Chat}
|
||||||
|
|||||||
@@ -22,14 +22,13 @@ export default function MaterialBottomTabsScreen() {
|
|||||||
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
||||||
<MaterialBottomTabs.Screen
|
<MaterialBottomTabs.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
|
component={SimpleStackScreen}
|
||||||
options={{
|
options={{
|
||||||
tabBarLabel: 'Article',
|
tabBarLabel: 'Article',
|
||||||
tabBarIcon: 'file-document-box',
|
tabBarIcon: 'file-document-box',
|
||||||
tabBarColor: '#C9E7F8',
|
tabBarColor: '#C9E7F8',
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
|
||||||
</MaterialBottomTabs.Screen>
|
|
||||||
<MaterialBottomTabs.Screen
|
<MaterialBottomTabs.Screen
|
||||||
name="Chat"
|
name="Chat"
|
||||||
component={Chat}
|
component={Chat}
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
|||||||
return (
|
return (
|
||||||
<ModalPresentationStack.Navigator
|
<ModalPresentationStack.Navigator
|
||||||
mode="modal"
|
mode="modal"
|
||||||
headerMode="screen"
|
|
||||||
screenOptions={({ route, navigation }) => ({
|
screenOptions={({ route, navigation }) => ({
|
||||||
...TransitionPresets.ModalPresentationIOS,
|
...TransitionPresets.ModalPresentationIOS,
|
||||||
cardOverlayEnabled: true,
|
cardOverlayEnabled: true,
|
||||||
|
|||||||
@@ -111,17 +111,17 @@ const AlbumsScreen = ({
|
|||||||
|
|
||||||
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
const SimpleStack = createStackNavigator<SimpleStackParams>();
|
||||||
|
|
||||||
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
|
type Props = {
|
||||||
navigation: StackNavigationProp<ParamListBase>;
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
export default function SimpleStackScreen({ navigation }: Props) {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleStack.Navigator {...rest}>
|
<SimpleStack.Navigator>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
name="Article"
|
name="Article"
|
||||||
component={ArticleScreen}
|
component={ArticleScreen}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Button, Appbar } from 'react-native-paper';
|
import { Button, Appbar } from 'react-native-paper';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
import { useTheme, RouteProp, ParamListBase } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
StackNavigationProp,
|
StackNavigationProp,
|
||||||
@@ -124,6 +124,8 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
headerShown: false,
|
headerShown: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { colors, dark } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleStack.Navigator {...rest}>
|
<SimpleStack.Navigator {...rest}>
|
||||||
<SimpleStack.Screen
|
<SimpleStack.Screen
|
||||||
@@ -167,9 +169,15 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
|||||||
headerBackTitle: 'Back',
|
headerBackTitle: 'Back',
|
||||||
headerTransparent: true,
|
headerTransparent: true,
|
||||||
headerBackground: () => (
|
headerBackground: () => (
|
||||||
<HeaderBackground style={{ backgroundColor: 'transparent' }}>
|
<HeaderBackground
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: colors.border,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<BlurView
|
<BlurView
|
||||||
tint="light"
|
tint={dark ? 'dark' : 'light'}
|
||||||
intensity={75}
|
intensity={75}
|
||||||
style={StyleSheet.absoluteFill}
|
style={StyleSheet.absoluteFill}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.4.2...@react-navigation/stack@5.5.0) (2020-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix blank screen with animationEnabled: false & headerShown: false ([9c06a92](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/9c06a92d092af150d653c3a2f7fdccd28090bb14)), closes [#8391](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8391)
|
||||||
|
* ignore onOpen from route that wasn't closing ([1f27e4b](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/1f27e4b1f659e59ad15ecbf44b4fb0a80cae302f)), closes [#8257](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8257)
|
||||||
|
* pass gestureRef to PanGestureHandlerNative ([#8394](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8394)) ([c7e4bf9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/c7e4bf94e664563892cbdafccc108ad519ccec50))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* automatically hide header in nested stacks ([e0e0f79](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/e0e0f79793be552e5532cd0afe9444000d21341e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.4.1...@react-navigation/stack@5.4.2) (2020-06-06)
|
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.4.1...@react-navigation/stack@5.4.2) (2020-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/stack",
|
"name": "@react-navigation/stack",
|
||||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||||
"version": "5.4.2",
|
"version": "5.5.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ export type StackHeaderOptions = {
|
|||||||
*/
|
*/
|
||||||
headerBackAllowFontScaling?: boolean;
|
headerBackAllowFontScaling?: boolean;
|
||||||
/**
|
/**
|
||||||
* Title string used by the back button on iOS, or `null` to disable label. Defaults to the previous scene's `headerTitle`.
|
* Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`.
|
||||||
|
* Use `headerBackTitleVisible: false` to hide it.
|
||||||
*/
|
*/
|
||||||
headerBackTitle?: string;
|
headerBackTitle?: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
5
packages/stack/src/utils/HeaderShownContext.tsx
Normal file
5
packages/stack/src/utils/HeaderShownContext.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const HeaderShownContext = React.createContext(false);
|
||||||
|
|
||||||
|
export default HeaderShownContext;
|
||||||
@@ -10,7 +10,7 @@ export function PanGestureHandler(props: PanGestureHandlerProperties) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRefContext.Provider value={gestureRef}>
|
<GestureHandlerRefContext.Provider value={gestureRef}>
|
||||||
<PanGestureHandlerNative {...props} />
|
<PanGestureHandlerNative {...props} ref={gestureRef} />
|
||||||
</GestureHandlerRefContext.Provider>
|
</GestureHandlerRefContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
forNoAnimation,
|
forNoAnimation,
|
||||||
forSlideRight,
|
forSlideRight,
|
||||||
} from '../../TransitionConfigs/HeaderStyleInterpolators';
|
} from '../../TransitionConfigs/HeaderStyleInterpolators';
|
||||||
|
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
Scene,
|
Scene,
|
||||||
@@ -54,6 +55,7 @@ export default function HeaderContainer({
|
|||||||
style,
|
style,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const focusedRoute = getFocusedRoute();
|
const focusedRoute = getFocusedRoute();
|
||||||
|
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View pointerEvents="box-none" style={style}>
|
<View pointerEvents="box-none" style={style}>
|
||||||
@@ -62,7 +64,16 @@ export default function HeaderContainer({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { options } = scene.descriptor;
|
const {
|
||||||
|
header,
|
||||||
|
headerShown = isParentHeaderShown === false,
|
||||||
|
headerTransparent,
|
||||||
|
} = scene.descriptor.options || {};
|
||||||
|
|
||||||
|
if (!headerShown) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const isFocused = focusedRoute.key === scene.route.key;
|
const isFocused = focusedRoute.key === scene.route.key;
|
||||||
const previousRoute = getPreviousRoute({ route: scene.route });
|
const previousRoute = getPreviousRoute({ route: scene.route });
|
||||||
|
|
||||||
@@ -85,13 +96,20 @@ export default function HeaderContainer({
|
|||||||
// This makes the header look like it's moving with the screen
|
// This makes the header look like it's moving with the screen
|
||||||
const previousScene = self[i - 1];
|
const previousScene = self[i - 1];
|
||||||
const nextScene = self[i + 1];
|
const nextScene = self[i + 1];
|
||||||
|
|
||||||
|
const {
|
||||||
|
headerShown: previousHeaderShown = isParentHeaderShown === false,
|
||||||
|
} = previousScene?.descriptor.options || {};
|
||||||
|
|
||||||
|
const { headerShown: nextHeaderShown = isParentHeaderShown === false } =
|
||||||
|
nextScene?.descriptor.options || {};
|
||||||
|
|
||||||
const isHeaderStatic =
|
const isHeaderStatic =
|
||||||
(previousScene &&
|
(previousHeaderShown === false &&
|
||||||
previousScene.descriptor.options.headerShown === false &&
|
|
||||||
// We still need to animate when coming back from next scene
|
// We still need to animate when coming back from next scene
|
||||||
// A hacky way to check this is if the next scene exists
|
// A hacky way to check this is if the next scene exists
|
||||||
!nextScene) ||
|
!nextScene) ||
|
||||||
(nextScene && nextScene.descriptor.options.headerShown === false);
|
nextHeaderShown === false;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
mode,
|
mode,
|
||||||
@@ -139,18 +157,12 @@ export default function HeaderContainer({
|
|||||||
style={
|
style={
|
||||||
// Avoid positioning the focused header absolutely
|
// Avoid positioning the focused header absolutely
|
||||||
// Otherwise accessibility tools don't seem to be able to find it
|
// Otherwise accessibility tools don't seem to be able to find it
|
||||||
(mode === 'float' && !isFocused) || options.headerTransparent
|
(mode === 'float' && !isFocused) || headerTransparent
|
||||||
? styles.header
|
? styles.header
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{options.headerShown !== false ? (
|
{header !== undefined ? header(props) : <Header {...props} />}
|
||||||
options.header !== undefined ? (
|
|
||||||
options.header(props)
|
|
||||||
) : (
|
|
||||||
<Header {...props} />
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
</NavigationRouteContext.Provider>
|
</NavigationRouteContext.Provider>
|
||||||
</NavigationContext.Provider>
|
</NavigationContext.Provider>
|
||||||
|
|||||||
@@ -493,6 +493,12 @@ export default class Card extends React.Component<Props> {
|
|||||||
? Color(backgroundColor).alpha() === 0
|
? Color(backgroundColor).alpha() === 0
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
// This is a dummy style that doesn't actually change anything visually.
|
||||||
|
// Animated needs the animated value to be used somewhere, otherwise things don't update properly.
|
||||||
|
// If we disable animations and hide header, it could end up making the value unused.
|
||||||
|
// So we have this dummy style that will always be used regardless of what else changed.
|
||||||
|
const dummyStyle = { opacity: Animated.diffClamp(current, 1, 1) };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardAnimationContext.Provider value={animationContext}>
|
<CardAnimationContext.Provider value={animationContext}>
|
||||||
<View pointerEvents="box-none" {...rest}>
|
<View pointerEvents="box-none" {...rest}>
|
||||||
@@ -502,7 +508,12 @@ export default class Card extends React.Component<Props> {
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[styles.container, containerStyle, customContainerStyle]}
|
style={[
|
||||||
|
styles.container,
|
||||||
|
dummyStyle,
|
||||||
|
containerStyle,
|
||||||
|
customContainerStyle,
|
||||||
|
]}
|
||||||
pointerEvents="box-none"
|
pointerEvents="box-none"
|
||||||
>
|
>
|
||||||
<PanGestureHandler
|
<PanGestureHandler
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Route, useTheme } from '@react-navigation/native';
|
|||||||
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import HeaderHeightContext from '../../utils/HeaderHeightContext';
|
import HeaderHeightContext from '../../utils/HeaderHeightContext';
|
||||||
|
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||||
import {
|
import {
|
||||||
Scene,
|
Scene,
|
||||||
Layout,
|
Layout,
|
||||||
@@ -53,6 +54,7 @@ type Props = TransitionPreset & {
|
|||||||
gestureVelocityImpact?: number;
|
gestureVelocityImpact?: number;
|
||||||
mode: StackCardMode;
|
mode: StackCardMode;
|
||||||
headerMode: StackHeaderMode;
|
headerMode: StackHeaderMode;
|
||||||
|
headerShown: boolean;
|
||||||
hasAbsoluteHeader: boolean;
|
hasAbsoluteHeader: boolean;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
onHeaderHeightChange: (props: {
|
onHeaderHeightChange: (props: {
|
||||||
@@ -81,6 +83,7 @@ function CardContainer({
|
|||||||
getFocusedRoute,
|
getFocusedRoute,
|
||||||
mode,
|
mode,
|
||||||
headerMode,
|
headerMode,
|
||||||
|
headerShown,
|
||||||
headerStyleInterpolator,
|
headerStyleInterpolator,
|
||||||
hasAbsoluteHeader,
|
hasAbsoluteHeader,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
@@ -158,6 +161,9 @@ function CardContainer({
|
|||||||
};
|
};
|
||||||
}, [pointerEvents, scene.progress.next]);
|
}, [pointerEvents, scene.progress.next]);
|
||||||
|
|
||||||
|
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||||
|
const isCurrentHeaderShown = headerMode !== 'none' && headerShown !== false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
index={index}
|
index={index}
|
||||||
@@ -191,9 +197,13 @@ function CardContainer({
|
|||||||
>
|
>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.scene}>
|
<View style={styles.scene}>
|
||||||
<HeaderHeightContext.Provider value={headerHeight}>
|
<HeaderShownContext.Provider
|
||||||
{renderScene({ route: scene.route })}
|
value={isParentHeaderShown || isCurrentHeaderShown}
|
||||||
</HeaderHeightContext.Provider>
|
>
|
||||||
|
<HeaderHeightContext.Provider value={headerHeight}>
|
||||||
|
{renderScene({ route: scene.route })}
|
||||||
|
</HeaderHeightContext.Provider>
|
||||||
|
</HeaderShownContext.Provider>
|
||||||
</View>
|
</View>
|
||||||
{headerMode === 'screen'
|
{headerMode === 'screen'
|
||||||
? renderHeader({
|
? renderHeader({
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '../../TransitionConfigs/TransitionPresets';
|
} from '../../TransitionConfigs/TransitionPresets';
|
||||||
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
|
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
|
||||||
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
|
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
|
||||||
|
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||||
import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
@@ -335,24 +336,6 @@ export default class CardStack extends React.Component<Props, State> {
|
|||||||
return state.routes[state.index];
|
return state.routes[state.index];
|
||||||
};
|
};
|
||||||
|
|
||||||
private doesFloatHeaderNeedAbsolutePositioning = () => {
|
|
||||||
if (this.props.headerMode !== 'float') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.state.scenes.slice(-2).some((scene) => {
|
|
||||||
const { descriptor } = scene;
|
|
||||||
const options = descriptor ? descriptor.options : {};
|
|
||||||
const { headerTransparent, headerShown } = options;
|
|
||||||
|
|
||||||
if (headerTransparent || headerShown === false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
mode,
|
mode,
|
||||||
@@ -381,8 +364,6 @@ export default class CardStack extends React.Component<Props, State> {
|
|||||||
const focusedDescriptor = descriptors[focusedRoute.key];
|
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||||
const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {};
|
const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {};
|
||||||
|
|
||||||
const isFloatHeaderAbsolute = this.doesFloatHeaderNeedAbsolutePositioning();
|
|
||||||
|
|
||||||
let defaultTransitionPreset =
|
let defaultTransitionPreset =
|
||||||
mode === 'modal' ? ModalTransition : DefaultTransition;
|
mode === 'modal' ? ModalTransition : DefaultTransition;
|
||||||
|
|
||||||
@@ -404,193 +385,222 @@ export default class CardStack extends React.Component<Props, State> {
|
|||||||
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
||||||
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
|
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
|
||||||
|
|
||||||
let floatingHeader;
|
|
||||||
|
|
||||||
if (headerMode === 'float') {
|
|
||||||
floatingHeader = (
|
|
||||||
<React.Fragment key="header">
|
|
||||||
{renderHeader({
|
|
||||||
mode: 'float',
|
|
||||||
layout,
|
|
||||||
insets: { top, right, bottom, left },
|
|
||||||
scenes,
|
|
||||||
getPreviousRoute,
|
|
||||||
getFocusedRoute: this.getFocusedRoute,
|
|
||||||
onContentHeightChange: this.handleHeaderLayout,
|
|
||||||
gestureDirection:
|
|
||||||
focusedOptions.gestureDirection !== undefined
|
|
||||||
? focusedOptions.gestureDirection
|
|
||||||
: defaultTransitionPreset.gestureDirection,
|
|
||||||
styleInterpolator:
|
|
||||||
focusedOptions.headerStyleInterpolator !== undefined
|
|
||||||
? focusedOptions.headerStyleInterpolator
|
|
||||||
: defaultTransitionPreset.headerStyleInterpolator,
|
|
||||||
style: isFloatHeaderAbsolute ? styles.floating : undefined,
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<HeaderShownContext.Consumer>
|
||||||
{isFloatHeaderAbsolute ? null : floatingHeader}
|
{(isParentHeaderShown) => {
|
||||||
<MaybeScreenContainer
|
const isFloatHeaderAbsolute =
|
||||||
enabled={isScreensEnabled}
|
headerMode === 'float'
|
||||||
style={styles.container}
|
? this.state.scenes.slice(-2).some((scene) => {
|
||||||
onLayout={this.handleLayout}
|
const { descriptor } = scene;
|
||||||
>
|
const options = descriptor ? descriptor.options : {};
|
||||||
{routes.map((route, index, self) => {
|
const {
|
||||||
const focused = focusedRoute.key === route.key;
|
headerTransparent,
|
||||||
const gesture = gestures[route.key];
|
headerShown = isParentHeaderShown === false,
|
||||||
const scene = scenes[index];
|
} = options;
|
||||||
|
|
||||||
const isScreenActive = scene.progress.next
|
if (headerTransparent || headerShown === false) {
|
||||||
? scene.progress.next.interpolate({
|
return true;
|
||||||
inputRange: [0, 1 - EPSILON, 1],
|
|
||||||
outputRange: [1, 1, 0],
|
|
||||||
extrapolate: 'clamp',
|
|
||||||
})
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
const {
|
|
||||||
safeAreaInsets,
|
|
||||||
headerShown,
|
|
||||||
headerTransparent,
|
|
||||||
cardShadowEnabled,
|
|
||||||
cardOverlayEnabled,
|
|
||||||
cardOverlay,
|
|
||||||
cardStyle,
|
|
||||||
animationEnabled,
|
|
||||||
gestureResponseDistance,
|
|
||||||
gestureVelocityImpact,
|
|
||||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
|
||||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
|
||||||
cardStyleInterpolator = animationEnabled === false
|
|
||||||
? forNoAnimationCard
|
|
||||||
: defaultTransitionPreset.cardStyleInterpolator,
|
|
||||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
|
||||||
} = scene.descriptor
|
|
||||||
? scene.descriptor.options
|
|
||||||
: ({} as StackNavigationOptions);
|
|
||||||
|
|
||||||
let transitionConfig = {
|
|
||||||
gestureDirection,
|
|
||||||
transitionSpec,
|
|
||||||
cardStyleInterpolator,
|
|
||||||
headerStyleInterpolator,
|
|
||||||
};
|
|
||||||
|
|
||||||
// When a screen is not the last, it should use next screen's transition config
|
|
||||||
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
|
||||||
// For example combining a slide and a modal transition would look wrong otherwise
|
|
||||||
// With this approach, combining different transition styles in the same navigator mostly looks right
|
|
||||||
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
|
||||||
// but majority of the transitions look alright
|
|
||||||
if (index !== self.length - 1) {
|
|
||||||
const nextScene = scenes[index + 1];
|
|
||||||
|
|
||||||
if (nextScene) {
|
|
||||||
const {
|
|
||||||
animationEnabled,
|
|
||||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
|
||||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
|
||||||
cardStyleInterpolator = animationEnabled === false
|
|
||||||
? forNoAnimationCard
|
|
||||||
: defaultTransitionPreset.cardStyleInterpolator,
|
|
||||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
|
||||||
} = nextScene.descriptor
|
|
||||||
? nextScene.descriptor.options
|
|
||||||
: ({} as StackNavigationOptions);
|
|
||||||
|
|
||||||
transitionConfig = {
|
|
||||||
gestureDirection,
|
|
||||||
transitionSpec,
|
|
||||||
cardStyleInterpolator,
|
|
||||||
headerStyleInterpolator,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
top: safeAreaInsetTop = insets.top,
|
|
||||||
right: safeAreaInsetRight = insets.right,
|
|
||||||
bottom: safeAreaInsetBottom = insets.bottom,
|
|
||||||
left: safeAreaInsetLeft = insets.left,
|
|
||||||
} = safeAreaInsets || {};
|
|
||||||
|
|
||||||
const previousRoute = getPreviousRoute({ route: scene.route });
|
|
||||||
|
|
||||||
let previousScene = scenes[index - 1];
|
|
||||||
|
|
||||||
if (previousRoute) {
|
|
||||||
// The previous scene will be shortly before the current scene in the array
|
|
||||||
// So loop back from current index to avoid looping over the full array
|
|
||||||
for (let j = index - 1; j >= 0; j--) {
|
|
||||||
const s = scenes[j];
|
|
||||||
|
|
||||||
if (s && s.route.key === previousRoute.key) {
|
|
||||||
previousScene = s;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MaybeScreen
|
|
||||||
key={route.key}
|
|
||||||
style={StyleSheet.absoluteFill}
|
|
||||||
enabled={isScreensEnabled}
|
|
||||||
active={isScreenActive}
|
|
||||||
pointerEvents="box-none"
|
|
||||||
>
|
|
||||||
<CardContainer
|
|
||||||
index={index}
|
|
||||||
active={index === self.length - 1}
|
|
||||||
focused={focused}
|
|
||||||
closing={closingRouteKeys.includes(route.key)}
|
|
||||||
layout={layout}
|
|
||||||
gesture={gesture}
|
|
||||||
scene={scene}
|
|
||||||
previousScene={previousScene}
|
|
||||||
safeAreaInsetTop={safeAreaInsetTop}
|
|
||||||
safeAreaInsetRight={safeAreaInsetRight}
|
|
||||||
safeAreaInsetBottom={safeAreaInsetBottom}
|
|
||||||
safeAreaInsetLeft={safeAreaInsetLeft}
|
|
||||||
cardOverlay={cardOverlay}
|
|
||||||
cardOverlayEnabled={cardOverlayEnabled}
|
|
||||||
cardShadowEnabled={cardShadowEnabled}
|
|
||||||
cardStyle={cardStyle}
|
|
||||||
onPageChangeStart={onPageChangeStart}
|
|
||||||
onPageChangeConfirm={onPageChangeConfirm}
|
|
||||||
onPageChangeCancel={onPageChangeCancel}
|
|
||||||
gestureResponseDistance={gestureResponseDistance}
|
|
||||||
headerHeight={headerHeights[route.key]}
|
|
||||||
onHeaderHeightChange={this.handleHeaderLayout}
|
|
||||||
getPreviousRoute={getPreviousRoute}
|
|
||||||
getFocusedRoute={this.getFocusedRoute}
|
|
||||||
mode={mode}
|
|
||||||
headerMode={headerMode}
|
|
||||||
hasAbsoluteHeader={
|
|
||||||
isFloatHeaderAbsolute &&
|
|
||||||
headerShown !== false &&
|
|
||||||
!headerTransparent
|
|
||||||
}
|
}
|
||||||
renderHeader={renderHeader}
|
|
||||||
renderScene={renderScene}
|
return false;
|
||||||
onOpenRoute={onOpenRoute}
|
})
|
||||||
onCloseRoute={onCloseRoute}
|
: false;
|
||||||
onTransitionStart={onTransitionStart}
|
|
||||||
onTransitionEnd={onTransitionEnd}
|
const floatingHeader =
|
||||||
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
|
headerMode === 'float' ? (
|
||||||
gestureVelocityImpact={gestureVelocityImpact}
|
<React.Fragment key="header">
|
||||||
{...transitionConfig}
|
{renderHeader({
|
||||||
/>
|
mode: 'float',
|
||||||
</MaybeScreen>
|
layout,
|
||||||
);
|
insets: { top, right, bottom, left },
|
||||||
})}
|
scenes,
|
||||||
</MaybeScreenContainer>
|
getPreviousRoute,
|
||||||
{isFloatHeaderAbsolute ? floatingHeader : null}
|
getFocusedRoute: this.getFocusedRoute,
|
||||||
</React.Fragment>
|
onContentHeightChange: this.handleHeaderLayout,
|
||||||
|
gestureDirection:
|
||||||
|
focusedOptions.gestureDirection !== undefined
|
||||||
|
? focusedOptions.gestureDirection
|
||||||
|
: defaultTransitionPreset.gestureDirection,
|
||||||
|
styleInterpolator:
|
||||||
|
focusedOptions.headerStyleInterpolator !== undefined
|
||||||
|
? focusedOptions.headerStyleInterpolator
|
||||||
|
: defaultTransitionPreset.headerStyleInterpolator,
|
||||||
|
style: isFloatHeaderAbsolute ? styles.floating : undefined,
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isFloatHeaderAbsolute ? null : floatingHeader}
|
||||||
|
<MaybeScreenContainer
|
||||||
|
enabled={isScreensEnabled}
|
||||||
|
style={styles.container}
|
||||||
|
onLayout={this.handleLayout}
|
||||||
|
>
|
||||||
|
{routes.map((route, index, self) => {
|
||||||
|
const focused = focusedRoute.key === route.key;
|
||||||
|
const gesture = gestures[route.key];
|
||||||
|
const scene = scenes[index];
|
||||||
|
|
||||||
|
const isScreenActive = scene.progress.next
|
||||||
|
? scene.progress.next.interpolate({
|
||||||
|
inputRange: [0, 1 - EPSILON, 1],
|
||||||
|
outputRange: [1, 1, 0],
|
||||||
|
extrapolate: 'clamp',
|
||||||
|
})
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
const {
|
||||||
|
safeAreaInsets,
|
||||||
|
headerShown = isParentHeaderShown === false,
|
||||||
|
headerTransparent,
|
||||||
|
cardShadowEnabled,
|
||||||
|
cardOverlayEnabled,
|
||||||
|
cardOverlay,
|
||||||
|
cardStyle,
|
||||||
|
animationEnabled,
|
||||||
|
gestureResponseDistance,
|
||||||
|
gestureVelocityImpact,
|
||||||
|
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||||
|
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||||
|
cardStyleInterpolator = animationEnabled === false
|
||||||
|
? forNoAnimationCard
|
||||||
|
: defaultTransitionPreset.cardStyleInterpolator,
|
||||||
|
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||||
|
} = scene.descriptor
|
||||||
|
? scene.descriptor.options
|
||||||
|
: ({} as StackNavigationOptions);
|
||||||
|
|
||||||
|
let transitionConfig = {
|
||||||
|
gestureDirection,
|
||||||
|
transitionSpec,
|
||||||
|
cardStyleInterpolator,
|
||||||
|
headerStyleInterpolator,
|
||||||
|
};
|
||||||
|
|
||||||
|
// When a screen is not the last, it should use next screen's transition config
|
||||||
|
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
||||||
|
// For example combining a slide and a modal transition would look wrong otherwise
|
||||||
|
// With this approach, combining different transition styles in the same navigator mostly looks right
|
||||||
|
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
||||||
|
// but majority of the transitions look alright
|
||||||
|
if (index !== self.length - 1) {
|
||||||
|
const nextScene = scenes[index + 1];
|
||||||
|
|
||||||
|
if (nextScene) {
|
||||||
|
const {
|
||||||
|
animationEnabled,
|
||||||
|
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||||
|
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||||
|
cardStyleInterpolator = animationEnabled === false
|
||||||
|
? forNoAnimationCard
|
||||||
|
: defaultTransitionPreset.cardStyleInterpolator,
|
||||||
|
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||||
|
} = nextScene.descriptor
|
||||||
|
? nextScene.descriptor.options
|
||||||
|
: ({} as StackNavigationOptions);
|
||||||
|
|
||||||
|
transitionConfig = {
|
||||||
|
gestureDirection,
|
||||||
|
transitionSpec,
|
||||||
|
cardStyleInterpolator,
|
||||||
|
headerStyleInterpolator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
top: safeAreaInsetTop = insets.top,
|
||||||
|
right: safeAreaInsetRight = insets.right,
|
||||||
|
bottom: safeAreaInsetBottom = insets.bottom,
|
||||||
|
left: safeAreaInsetLeft = insets.left,
|
||||||
|
} = safeAreaInsets || {};
|
||||||
|
|
||||||
|
const previousRoute = getPreviousRoute({
|
||||||
|
route: scene.route,
|
||||||
|
});
|
||||||
|
|
||||||
|
let previousScene = scenes[index - 1];
|
||||||
|
|
||||||
|
if (previousRoute) {
|
||||||
|
// The previous scene will be shortly before the current scene in the array
|
||||||
|
// So loop back from current index to avoid looping over the full array
|
||||||
|
for (let j = index - 1; j >= 0; j--) {
|
||||||
|
const s = scenes[j];
|
||||||
|
|
||||||
|
if (s && s.route.key === previousRoute.key) {
|
||||||
|
previousScene = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerHeight =
|
||||||
|
headerMode !== 'none' && headerShown !== false
|
||||||
|
? headerHeights[route.key]
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MaybeScreen
|
||||||
|
key={route.key}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
enabled={isScreensEnabled}
|
||||||
|
active={isScreenActive}
|
||||||
|
pointerEvents="box-none"
|
||||||
|
>
|
||||||
|
<CardContainer
|
||||||
|
index={index}
|
||||||
|
active={index === self.length - 1}
|
||||||
|
focused={focused}
|
||||||
|
closing={closingRouteKeys.includes(route.key)}
|
||||||
|
layout={layout}
|
||||||
|
gesture={gesture}
|
||||||
|
scene={scene}
|
||||||
|
previousScene={previousScene}
|
||||||
|
safeAreaInsetTop={safeAreaInsetTop}
|
||||||
|
safeAreaInsetRight={safeAreaInsetRight}
|
||||||
|
safeAreaInsetBottom={safeAreaInsetBottom}
|
||||||
|
safeAreaInsetLeft={safeAreaInsetLeft}
|
||||||
|
cardOverlay={cardOverlay}
|
||||||
|
cardOverlayEnabled={cardOverlayEnabled}
|
||||||
|
cardShadowEnabled={cardShadowEnabled}
|
||||||
|
cardStyle={cardStyle}
|
||||||
|
onPageChangeStart={onPageChangeStart}
|
||||||
|
onPageChangeConfirm={onPageChangeConfirm}
|
||||||
|
onPageChangeCancel={onPageChangeCancel}
|
||||||
|
gestureResponseDistance={gestureResponseDistance}
|
||||||
|
headerHeight={headerHeight}
|
||||||
|
onHeaderHeightChange={this.handleHeaderLayout}
|
||||||
|
getPreviousRoute={getPreviousRoute}
|
||||||
|
getFocusedRoute={this.getFocusedRoute}
|
||||||
|
mode={mode}
|
||||||
|
headerMode={headerMode}
|
||||||
|
headerShown={headerShown}
|
||||||
|
hasAbsoluteHeader={
|
||||||
|
isFloatHeaderAbsolute && !headerTransparent
|
||||||
|
}
|
||||||
|
renderHeader={renderHeader}
|
||||||
|
renderScene={renderScene}
|
||||||
|
onOpenRoute={onOpenRoute}
|
||||||
|
onCloseRoute={onCloseRoute}
|
||||||
|
onTransitionStart={onTransitionStart}
|
||||||
|
onTransitionEnd={onTransitionEnd}
|
||||||
|
gestureEnabled={
|
||||||
|
index !== 0 && getGesturesEnabled({ route })
|
||||||
|
}
|
||||||
|
gestureVelocityImpact={gestureVelocityImpact}
|
||||||
|
{...transitionConfig}
|
||||||
|
/>
|
||||||
|
</MaybeScreen>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MaybeScreenContainer>
|
||||||
|
{isFloatHeaderAbsolute ? floatingHeader : null}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</HeaderShownContext.Consumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,13 +330,15 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
|
|
||||||
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
||||||
const { state, navigation } = this.props;
|
const { state, navigation } = this.props;
|
||||||
|
const { closingRouteKeys, replacingRouteKeys } = this.state;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.state.replacingRouteKeys.every((key) => key !== route.key) &&
|
closingRouteKeys.some((key) => key === route.key) &&
|
||||||
|
replacingRouteKeys.every((key) => key !== route.key) &&
|
||||||
state.routeNames.includes(route.name) &&
|
state.routeNames.includes(route.name) &&
|
||||||
!state.routes.some((r) => r.key === route.key)
|
!state.routes.some((r) => r.key === route.key)
|
||||||
) {
|
) {
|
||||||
// If route isn't present in current state, assume that a close animation was cancelled
|
// If route isn't present in current state, but was closing, assume that a close animation was cancelled
|
||||||
// So we need to add this route back to the state
|
// So we need to add this route back to the state
|
||||||
navigation.navigate(route);
|
navigation.navigate(route);
|
||||||
} else {
|
} else {
|
||||||
@@ -409,6 +411,9 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
navigation,
|
navigation,
|
||||||
keyboardHandlingEnabled,
|
keyboardHandlingEnabled,
|
||||||
mode = 'card',
|
mode = 'card',
|
||||||
|
headerMode = mode === 'card' && Platform.OS === 'ios'
|
||||||
|
? 'float'
|
||||||
|
: 'screen',
|
||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -419,9 +424,6 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
closingRouteKeys,
|
closingRouteKeys,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const headerMode =
|
|
||||||
mode === 'card' && Platform.OS === 'ios' ? 'float' : 'screen';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationHelpersContext.Provider value={navigation}>
|
<NavigationHelpersContext.Provider value={navigation}>
|
||||||
<GestureHandlerWrapper style={styles.container}>
|
<GestureHandlerWrapper style={styles.container}>
|
||||||
|
|||||||
Reference in New Issue
Block a user