mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-10 17:23:42 +08:00
fix: use pure component for stack items
This commit is contained in:
@@ -39,6 +39,9 @@ export const WipeFromBottomAndroidSpec: TransitionSpec = {
|
||||
duration: 425,
|
||||
// This is super rough approximation of the path used for the curve by android
|
||||
// See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/interpolator/fast_out_extra_slow_in.xml
|
||||
easing: t => Easing.bezier(0.90, 0.06, 0.57, 0)((Easing.bezier(0.06, 0.94, 0.22, 1.02)(t))),
|
||||
easing: t =>
|
||||
Easing.bezier(0.9, 0.06, 0.57, 0)(
|
||||
Easing.bezier(0.06, 0.94, 0.22, 1.02)(t)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
} from '../../types';
|
||||
import Header from './Header';
|
||||
|
||||
type Props = {
|
||||
export type Props = {
|
||||
mode: 'float' | 'screen';
|
||||
layout: Layout;
|
||||
scenes: HeaderScene<Route>[];
|
||||
scenes: Array<HeaderScene<Route> | undefined>;
|
||||
navigation: NavigationProp;
|
||||
getPreviousRoute: (props: { route: Route }) => Route | undefined;
|
||||
onLayout?: (e: LayoutChangeEvent) => void;
|
||||
@@ -42,7 +42,7 @@ export default function HeaderContainer({
|
||||
return (
|
||||
<View pointerEvents="box-none" style={style}>
|
||||
{scenes.map((scene, i, self) => {
|
||||
if (mode === 'screen' && i !== self.length - 1) {
|
||||
if ((mode === 'screen' && i !== self.length - 1) || !scene) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -56,8 +56,10 @@ export default function HeaderContainer({
|
||||
// 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 = i - 1; j >= 0; j--) {
|
||||
if (self[j].route.key === previousRoute.key) {
|
||||
previous = self[j];
|
||||
const s = self[j];
|
||||
|
||||
if (s && s.route.key === previousRoute.key) {
|
||||
previous = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||
import HeaderContainer from '../Header/HeaderContainer';
|
||||
import Card from './Card';
|
||||
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import StackItem from './StackItem';
|
||||
import {
|
||||
Route,
|
||||
Layout,
|
||||
@@ -38,6 +38,7 @@ type Props = {
|
||||
onCloseRoute: (props: { route: Route }) => void;
|
||||
getPreviousRoute: (props: { route: Route }) => Route | undefined;
|
||||
getGesturesEnabled: (props: { route: Route }) => boolean;
|
||||
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
||||
renderScene: (props: { route: Route }) => React.ReactNode;
|
||||
transparentCard?: boolean;
|
||||
headerMode: HeaderMode;
|
||||
@@ -182,6 +183,7 @@ export default class Stack extends React.Component<Props, State> {
|
||||
onGoBack,
|
||||
getPreviousRoute,
|
||||
getGesturesEnabled,
|
||||
renderHeader,
|
||||
renderScene,
|
||||
transparentCard,
|
||||
headerMode,
|
||||
@@ -212,29 +214,22 @@ export default class Stack extends React.Component<Props, State> {
|
||||
const scene = scenes[index];
|
||||
|
||||
return (
|
||||
<Card
|
||||
<StackItem
|
||||
key={route.key}
|
||||
index={index}
|
||||
active={index === self.length - 1}
|
||||
transparent={transparentCard}
|
||||
direction={direction}
|
||||
focused={focused}
|
||||
closing={closingRoutes.includes(route.key)}
|
||||
layout={layout}
|
||||
current={current}
|
||||
next={scene.progress.next}
|
||||
closing={closingRoutes.includes(route.key)}
|
||||
onOpen={() => onOpenRoute({ route })}
|
||||
onClose={() => onCloseRoute({ route })}
|
||||
overlayEnabled={cardOverlayEnabled}
|
||||
shadowEnabled={cardShadowEnabled}
|
||||
scene={scene}
|
||||
previousScene={scenes[index - 1]}
|
||||
navigation={navigation}
|
||||
direction={direction}
|
||||
transparentCard={transparentCard}
|
||||
cardOverlayEnabled={cardOverlayEnabled}
|
||||
cardShadowEnabled={cardShadowEnabled}
|
||||
gesturesEnabled={index !== 0 && getGesturesEnabled({ route })}
|
||||
onTransitionStart={({ closing }) => {
|
||||
onTransitionStart &&
|
||||
onTransitionStart(
|
||||
{ index: closing ? index - 1 : index },
|
||||
{ index }
|
||||
);
|
||||
|
||||
closing && onGoBack({ route });
|
||||
}}
|
||||
onGestureBegin={onGestureBegin}
|
||||
onGestureCanceled={onGestureCanceled}
|
||||
onGestureEnd={onGestureEnd}
|
||||
@@ -242,49 +237,34 @@ export default class Stack extends React.Component<Props, State> {
|
||||
descriptor.options.gestureResponseDistance
|
||||
}
|
||||
transitionSpec={transitionSpec}
|
||||
styleInterpolator={cardStyleInterpolator}
|
||||
accessibilityElementsHidden={!focused}
|
||||
importantForAccessibility={
|
||||
focused ? 'auto' : 'no-hide-descendants'
|
||||
}
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
headerMode === 'float' &&
|
||||
descriptor &&
|
||||
descriptor.options.header !== null
|
||||
? { marginTop: floaingHeaderHeight }
|
||||
: null,
|
||||
]}
|
||||
>
|
||||
{headerMode === 'screen' ? (
|
||||
<HeaderContainer
|
||||
mode="screen"
|
||||
layout={layout}
|
||||
scenes={[scenes[index - 1], scenes[index]]}
|
||||
navigation={navigation}
|
||||
getPreviousRoute={getPreviousRoute}
|
||||
styleInterpolator={headerStyleInterpolator}
|
||||
style={styles.header}
|
||||
/>
|
||||
) : null}
|
||||
{renderScene({ route })}
|
||||
</Card>
|
||||
headerStyleInterpolator={headerStyleInterpolator}
|
||||
cardStyleInterpolator={cardStyleInterpolator}
|
||||
floaingHeaderHeight={floaingHeaderHeight}
|
||||
hasCustomHeader={descriptor.options.header === null}
|
||||
getPreviousRoute={getPreviousRoute}
|
||||
headerMode={headerMode}
|
||||
renderHeader={renderHeader}
|
||||
renderScene={renderScene}
|
||||
onOpenRoute={onOpenRoute}
|
||||
onCloseRoute={onCloseRoute}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onGoBack={onGoBack}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
{headerMode === 'float' ? (
|
||||
<HeaderContainer
|
||||
mode="float"
|
||||
layout={layout}
|
||||
scenes={scenes}
|
||||
navigation={navigation}
|
||||
getPreviousRoute={getPreviousRoute}
|
||||
onLayout={this.handleFloatingHeaderLayout}
|
||||
styleInterpolator={headerStyleInterpolator}
|
||||
style={[styles.header, styles.floating]}
|
||||
/>
|
||||
) : null}
|
||||
{headerMode === 'float'
|
||||
? renderHeader({
|
||||
mode: 'float',
|
||||
layout,
|
||||
scenes,
|
||||
navigation,
|
||||
getPreviousRoute,
|
||||
onLayout: this.handleFloatingHeaderLayout,
|
||||
styleInterpolator: headerStyleInterpolator,
|
||||
style: [styles.header, styles.floating],
|
||||
})
|
||||
: null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
160
packages/stack/src/views/Stack/StackItem.tsx
Normal file
160
packages/stack/src/views/Stack/StackItem.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import Card from './Card';
|
||||
import {
|
||||
Route,
|
||||
HeaderScene,
|
||||
GestureDirection,
|
||||
Layout,
|
||||
TransitionSpec,
|
||||
CardStyleInterpolator,
|
||||
HeaderMode,
|
||||
NavigationProp,
|
||||
HeaderStyleInterpolator,
|
||||
} from '../../types';
|
||||
|
||||
type Props = {
|
||||
index: number;
|
||||
active: boolean;
|
||||
focused: boolean;
|
||||
closing: boolean;
|
||||
layout: Layout;
|
||||
current: Animated.Value<number>;
|
||||
previousScene?: HeaderScene<Route>;
|
||||
scene: HeaderScene<Route>;
|
||||
navigation: NavigationProp;
|
||||
transparentCard?: boolean;
|
||||
cardOverlayEnabled?: boolean;
|
||||
cardShadowEnabled?: boolean;
|
||||
gesturesEnabled?: boolean;
|
||||
direction: GestureDirection;
|
||||
getPreviousRoute: (props: { route: Route }) => Route | undefined;
|
||||
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
||||
renderScene: (props: { route: Route }) => React.ReactNode;
|
||||
onOpenRoute: (props: { route: Route }) => void;
|
||||
onCloseRoute: (props: { route: Route }) => void;
|
||||
onGoBack: (props: { route: Route }) => void;
|
||||
onTransitionStart?: (
|
||||
curr: { index: number },
|
||||
prev: { index: number }
|
||||
) => void;
|
||||
onGestureBegin?: () => void;
|
||||
onGestureCanceled?: () => void;
|
||||
onGestureEnd?: () => void;
|
||||
gestureResponseDistance?: {
|
||||
vertical?: number;
|
||||
horizontal?: number;
|
||||
};
|
||||
transitionSpec: {
|
||||
open: TransitionSpec;
|
||||
close: TransitionSpec;
|
||||
};
|
||||
headerStyleInterpolator: HeaderStyleInterpolator;
|
||||
cardStyleInterpolator: CardStyleInterpolator;
|
||||
headerMode: HeaderMode;
|
||||
floaingHeaderHeight: number;
|
||||
hasCustomHeader: boolean;
|
||||
};
|
||||
|
||||
export default class StackItem extends React.PureComponent<Props> {
|
||||
private handleOpen = () =>
|
||||
this.props.onOpenRoute({ route: this.props.scene.route });
|
||||
|
||||
private handleClose = () =>
|
||||
this.props.onOpenRoute({ route: this.props.scene.route });
|
||||
|
||||
private handleTransitionStart = ({ closing }: { closing: boolean }) => {
|
||||
const { index, scene, onTransitionStart, onGoBack } = this.props;
|
||||
|
||||
onTransitionStart &&
|
||||
onTransitionStart({ index: closing ? index - 1 : index }, { index });
|
||||
|
||||
closing && onGoBack({ route: scene.route });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
layout,
|
||||
active,
|
||||
focused,
|
||||
closing,
|
||||
current,
|
||||
navigation,
|
||||
scene,
|
||||
previousScene,
|
||||
direction,
|
||||
transparentCard,
|
||||
cardOverlayEnabled,
|
||||
cardShadowEnabled,
|
||||
gesturesEnabled,
|
||||
onGestureBegin,
|
||||
onGestureCanceled,
|
||||
onGestureEnd,
|
||||
gestureResponseDistance,
|
||||
transitionSpec,
|
||||
headerStyleInterpolator,
|
||||
cardStyleInterpolator,
|
||||
floaingHeaderHeight,
|
||||
hasCustomHeader,
|
||||
getPreviousRoute,
|
||||
headerMode,
|
||||
renderHeader,
|
||||
renderScene,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
active={active}
|
||||
transparent={transparentCard}
|
||||
direction={direction}
|
||||
layout={layout}
|
||||
current={current}
|
||||
next={scene.progress.next}
|
||||
closing={closing}
|
||||
onOpen={this.handleOpen}
|
||||
onClose={this.handleClose}
|
||||
overlayEnabled={cardOverlayEnabled}
|
||||
shadowEnabled={cardShadowEnabled}
|
||||
gesturesEnabled={gesturesEnabled}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onGestureBegin={onGestureBegin}
|
||||
onGestureCanceled={onGestureCanceled}
|
||||
onGestureEnd={onGestureEnd}
|
||||
gestureResponseDistance={gestureResponseDistance}
|
||||
transitionSpec={transitionSpec}
|
||||
styleInterpolator={cardStyleInterpolator}
|
||||
accessibilityElementsHidden={!focused}
|
||||
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
headerMode === 'float' && !hasCustomHeader
|
||||
? { marginTop: floaingHeaderHeight }
|
||||
: null,
|
||||
]}
|
||||
>
|
||||
{headerMode === 'screen'
|
||||
? renderHeader({
|
||||
mode: 'screen',
|
||||
layout,
|
||||
scenes: [previousScene, scene],
|
||||
navigation,
|
||||
getPreviousRoute,
|
||||
styleInterpolator: headerStyleInterpolator,
|
||||
style: styles.header,
|
||||
})
|
||||
: null}
|
||||
{renderScene({ route: scene.route })}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
// This is needed to show elevation shadow
|
||||
zIndex: Platform.OS === 'android' ? 1 : 0,
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { SceneView, StackActions } from '@react-navigation/core';
|
||||
import Stack from './Stack';
|
||||
import HeaderContainer, {
|
||||
Props as HeaderContainerProps,
|
||||
} from '../Header/HeaderContainer';
|
||||
import {
|
||||
DefaultTransition,
|
||||
ModalSlideFromBottomIOS,
|
||||
@@ -11,7 +15,6 @@ import {
|
||||
NavigationConfig,
|
||||
Route,
|
||||
} from '../../types';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
type Descriptors = { [key: string]: SceneDescriptor };
|
||||
|
||||
@@ -223,6 +226,10 @@ class StackView extends React.Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
private renderHeader = (props: HeaderContainerProps) => {
|
||||
return <HeaderContainer {...props} />;
|
||||
};
|
||||
|
||||
private handleTransitionComplete = () => {
|
||||
// TODO: remove when the new event system lands
|
||||
this.props.navigation.dispatch(StackActions.completeTransition());
|
||||
@@ -294,6 +301,7 @@ class StackView extends React.Component<Props, State> {
|
||||
onGestureBegin={onGestureBegin}
|
||||
onGestureCanceled={onGestureCanceled}
|
||||
onGestureEnd={onGestureEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
navigation={navigation}
|
||||
|
||||
Reference in New Issue
Block a user