Optimize gesture logic, Reduce unnecessary creation of duplicates AnimatedNode

This commit is contained in:
yinhf 2018-12-06 19:13:15 +08:00
parent 177ca217c9
commit 63df3e7290
2 changed files with 274 additions and 262 deletions

View File

@ -29,24 +29,7 @@ class StackView extends React.Component {
this.props.onTransitionStart || this.props.onTransitionStart ||
this.props.navigationConfig.onTransitionStart this.props.navigationConfig.onTransitionStart
} }
onTransitionEnd={(transition, lastTransition) => { onTransitionEnd={this._onTransitionEnd}
const { navigationConfig, navigation } = this.props;
const onTransitionEnd =
this.props.onTransitionEnd || navigationConfig.onTransitionEnd;
const transitionDestKey = transition.scene.route.key;
const isCurrentKey =
navigation.state.routes[navigation.state.index].key ===
transitionDestKey;
if (transition.navigation.state.isTransitioning && isCurrentKey) {
navigation.dispatch(
StackActions.completeTransition({
key: navigation.state.key,
toChildKey: transitionDestKey,
})
);
}
onTransitionEnd && onTransitionEnd(transition, lastTransition);
}}
/> />
); );
} }
@ -107,6 +90,26 @@ class StackView extends React.Component {
/> />
); );
}; };
_onTransitionEnd = (transition, lastTransition) => {
const {
navigationConfig,
navigation,
onTransitionEnd = navigationConfig.onTransitionEnd,
} = this.props;
const transitionDestKey = transition.scene.route.key;
const isCurrentKey =
navigation.state.routes[navigation.state.index].key === transitionDestKey;
if (transition.navigation.state.isTransitioning && isCurrentKey) {
navigation.dispatch(
StackActions.completeTransition({
key: navigation.state.key,
toChildKey: transitionDestKey,
})
);
}
onTransitionEnd && onTransitionEnd(transition, lastTransition);
};
} }
export default StackView; export default StackView;

View File

@ -84,14 +84,6 @@ const getDefaultHeaderHeight = isLandscape => {
}; };
class StackViewLayout extends React.Component { class StackViewLayout extends React.Component {
/**
* Used to identify the starting point of the position when the gesture starts, such that it can
* be updated according to its relative position. This means that a card can effectively be
* "caught"- If a gesture starts while a card is animating, the card does not jump into a
* corresponding location for the touch.
*/
_gestureStartValue = 0;
/** /**
* immediateIndex is used to represent the expected index that we will be on after a * immediateIndex is used to represent the expected index that we will be on after a
* transition. To achieve a smooth animation when swiping back, the action to go back * transition. To achieve a smooth animation when swiping back, the action to go back
@ -106,6 +98,28 @@ class StackViewLayout extends React.Component {
this.panGestureRef = React.createRef(); this.panGestureRef = React.createRef();
this.gestureX = new Animated.Value(0); this.gestureX = new Animated.Value(0);
this.gestureY = new Animated.Value(0); this.gestureY = new Animated.Value(0);
this.positionSwitch = new Animated.Value(1);
if (Animated.subtract) {
this.gestureSwitch = Animated.subtract(1, this.positionSwitch);
} else {
this.gestureSwitch = Animated.add(
1,
Animated.multiply(-1, this.positionSwitch)
);
}
this.gestureEvent = Animated.event(
[
{
nativeEvent: {
translationX: this.gestureX,
translationY: this.gestureY,
},
},
],
{
useNativeDriver: USE_NATIVE_DRIVER,
}
);
this.state = { this.state = {
// Used when card's header is null and mode is float to make transition // Used when card's header is null and mode is float to make transition
@ -114,7 +128,6 @@ class StackViewLayout extends React.Component {
// on mount what the header height is so we have just used the most // on mount what the header height is so we have just used the most
// common cases here. // common cases here.
floatingHeaderHeight: getDefaultHeaderHeight(props.isLandscape), floatingHeaderHeight: getDefaultHeaderHeight(props.isLandscape),
gesturePosition: null,
}; };
} }
@ -145,9 +158,9 @@ class StackViewLayout extends React.Component {
headerTitleInterpolator, headerTitleInterpolator,
headerRightInterpolator, headerRightInterpolator,
headerBackgroundInterpolator, headerBackgroundInterpolator,
} = this._getTransitionConfig(); } = this._transitionConfig;
let backgroundTransitionPresetInterpolator = this._getHeaderBackgroundTransitionPreset(); const backgroundTransitionPresetInterpolator = this._getHeaderBackgroundTransitionPreset();
if (backgroundTransitionPresetInterpolator) { if (backgroundTransitionPresetInterpolator) {
headerBackgroundInterpolator = backgroundTransitionPresetInterpolator; headerBackgroundInterpolator = backgroundTransitionPresetInterpolator;
} }
@ -159,7 +172,7 @@ class StackViewLayout extends React.Component {
{renderHeader({ {renderHeader({
...passProps, ...passProps,
...transitionProps, ...transitionProps,
position: this._getPosition(), position: this.position,
scene, scene,
mode: headerMode, mode: headerMode,
transitionPreset: this._getHeaderTransitionPreset(), transitionPreset: this._getHeaderTransitionPreset(),
@ -240,15 +253,38 @@ class StackViewLayout extends React.Component {
} }
_onFloatingHeaderLayout = e => { _onFloatingHeaderLayout = e => {
this.setState({ floatingHeaderHeight: e.nativeEvent.layout.height }); const { height } = e.nativeEvent.layout;
if (height !== this.state.floatingHeaderHeight) {
this.setState({ floatingHeaderHeight: height });
}
}; };
render() { _prepareAnimated() {
let floatingHeader = null; if (this.props === this._prevProps) {
const headerMode = this._getHeaderMode(); return;
}
this._prevProps = this.props;
this._prepareGesture();
this._preparePosition();
this._prepareTransitionConfig();
}
render() {
this._prepareAnimated();
const { transitionProps } = this.props;
const {
navigation: {
state: { index },
},
scenes,
} = transitionProps;
const headerMode = this._getHeaderMode();
let floatingHeader = null;
if (headerMode === 'float') { if (headerMode === 'float') {
const { scene } = this.props.transitionProps; const { scene } = transitionProps;
floatingHeader = ( floatingHeader = (
<View <View
style={styles.floatingHeader} style={styles.floatingHeader}
@ -259,46 +295,21 @@ class StackViewLayout extends React.Component {
</View> </View>
); );
} }
const {
transitionProps: { navigation, scene, scenes },
} = this.props;
const { options } = scene.descriptor;
const { index } = navigation.state;
const gesturesEnabled =
typeof options.gesturesEnabled === 'boolean'
? options.gesturesEnabled
: Platform.OS === 'ios';
const containerStyle = [
styles.container,
this._getTransitionConfig().containerStyle,
];
return ( return (
<PanGestureHandler <PanGestureHandler
{...this._gestureActivationCriteria()} {...this._gestureActivationCriteria()}
ref={this.panGestureRef} ref={this.panGestureRef}
onGestureEvent={Animated.event( onGestureEvent={this.gestureEvent}
[
{
nativeEvent: {
translationX: this.gestureX,
translationY: this.gestureY,
},
},
],
{
useNativeDriver: USE_NATIVE_DRIVER,
}
)}
onHandlerStateChange={this._handlePanGestureStateChange} onHandlerStateChange={this._handlePanGestureStateChange}
enabled={index > 0 && gesturesEnabled} enabled={index > 0 && this._isGesturesEnabled()}
> >
<Animated.View style={containerStyle}> <Animated.View
style={[styles.container, this._transitionConfig.containerStyle]}
>
<StackGestureContext.Provider value={this.panGestureRef}> <StackGestureContext.Provider value={this.panGestureRef}>
<ScreenContainer style={styles.scenes}> <ScreenContainer style={styles.scenes}>
{scenes.map(s => this._renderCard(s))} {scenes.map(this._renderCard)}
</ScreenContainer> </ScreenContainer>
{floatingHeader} {floatingHeader}
</StackGestureContext.Provider> </StackGestureContext.Provider>
@ -315,7 +326,7 @@ class StackViewLayout extends React.Component {
} }
} }
_getGestureResponseDistance = () => { _getGestureResponseDistance() {
const { scene } = this.props.transitionProps; const { scene } = this.props.transitionProps;
const { options } = scene.descriptor; const { options } = scene.descriptor;
const { const {
@ -328,15 +339,15 @@ class StackViewLayout extends React.Component {
GESTURE_RESPONSE_DISTANCE_VERTICAL GESTURE_RESPONSE_DISTANCE_VERTICAL
: userGestureResponseDistance.horizontal || : userGestureResponseDistance.horizontal ||
GESTURE_RESPONSE_DISTANCE_HORIZONTAL; GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
}; }
_gestureActivationCriteria = () => { _gestureActivationCriteria() {
let { layout } = this.props.transitionProps; const { layout } = this.props.transitionProps;
let gestureResponseDistance = this._getGestureResponseDistance(); const gestureResponseDistance = this._getGestureResponseDistance();
let isMotionInverted = this._isMotionInverted(); const isMotionInverted = this._isMotionInverted();
if (this._isMotionVertical()) { if (this._isMotionVertical()) {
let height = layout.height.__getValue(); const height = layout.height.__getValue();
return { return {
maxDeltaX: 15, maxDeltaX: 15,
@ -346,8 +357,8 @@ class StackViewLayout extends React.Component {
: { bottom: -height + gestureResponseDistance }, : { bottom: -height + gestureResponseDistance },
}; };
} else { } else {
let width = layout.width.__getValue(); const width = layout.width.__getValue();
let hitSlop = -width + gestureResponseDistance; const hitSlop = -width + gestureResponseDistance;
return { return {
minOffsetX: isMotionInverted ? -5 : 5, minOffsetX: isMotionInverted ? -5 : 5,
@ -355,28 +366,26 @@ class StackViewLayout extends React.Component {
hitSlop: isMotionInverted ? { left: hitSlop } : { right: hitSlop }, hitSlop: isMotionInverted ? { left: hitSlop } : { right: hitSlop },
}; };
} }
}; }
// Without using Reanimated it's not possible to do all of the following _isGesturesEnabled() {
// stuff with native driver. const gesturesEnabled = this.props.transitionProps.scene.descriptor.options
_handlePanGestureEvent = ({ nativeEvent }) => { .gesturesEnabled;
if (this._isMotionVertical()) { return typeof gesturesEnabled === 'boolean'
this._handleVerticalPan(nativeEvent); ? gesturesEnabled
} else { : Platform.OS === 'ios';
this._handleHorizontalPan(nativeEvent); }
}
};
_isMotionVertical = () => { _isMotionVertical() {
return this._isModal(); return this._isModal();
}; }
_isModal = () => { _isModal() {
return this.props.mode === 'modal'; return this.props.mode === 'modal';
}; }
// This only currently applies to the horizontal gesture! // This only currently applies to the horizontal gesture!
_isMotionInverted = () => { _isMotionInverted() {
const { const {
transitionProps: { scene }, transitionProps: { scene },
} = this.props; } = this.props;
@ -390,49 +399,44 @@ class StackViewLayout extends React.Component {
? gestureDirection === 'inverted' ? gestureDirection === 'inverted'
: I18nManager.isRTL; : I18nManager.isRTL;
} }
}; }
_handleHorizontalPan = nativeEvent => { _computeHorizontalGestureValue({ translationX }) {
let value = this._computeHorizontalGestureValue(nativeEvent); const {
this.props.transitionProps.position.setValue(Math.max(0, value));
};
_computeHorizontalGestureValue = ({ translationX }) => {
let {
transitionProps: { navigation, layout }, transitionProps: { navigation, layout },
} = this.props; } = this.props;
let { index } = navigation.state; const { index } = navigation.state;
// TODO: remove this __getValue! // TODO: remove this __getValue!
let distance = layout.width.__getValue(); const distance = layout.width.__getValue();
let x = this._isMotionInverted() ? -1 * translationX : translationX; const x = this._isMotionInverted() ? -1 * translationX : translationX;
let value = index - x / distance; const value = index - x / distance;
return clamp(index - 1, value, index); return clamp(index - 1, value, index);
}; }
_computeVerticalGestureValue = ({ translationY }) => { _computeVerticalGestureValue({ translationY }) {
let { const {
transitionProps: { navigation, layout }, transitionProps: { navigation, layout },
} = this.props; } = this.props;
let { index } = navigation.state; const { index } = navigation.state;
// TODO: remove this __getValue! // TODO: remove this __getValue!
let distance = layout.height.__getValue(); const distance = layout.height.__getValue();
let y = this._isMotionInverted() ? -1 * translationY : translationY; const y = this._isMotionInverted() ? -1 * translationY : translationY;
let value = index - y / distance; const value = index - y / distance;
return clamp(index - 1, value, index); return clamp(index - 1, value, index);
}; }
_handlePanGestureStateChange = ({ nativeEvent }) => { _handlePanGestureStateChange = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) { if (nativeEvent.oldState === State.ACTIVE) {
// Gesture was cancelled! For example, some navigation state update // Gesture was cancelled! For example, some navigation state update
// arrived while the gesture was active that cancelled it out // arrived while the gesture was active that cancelled it out
if (!this.state.gesturePosition) { if (this.positionSwitch.__getValue() === 1) {
return; return;
} }
@ -442,10 +446,9 @@ class StackViewLayout extends React.Component {
this._handleReleaseHorizontal(nativeEvent); this._handleReleaseHorizontal(nativeEvent);
} }
} else if (nativeEvent.state === State.ACTIVE) { } else if (nativeEvent.state === State.ACTIVE) {
if (this._isMotionVertical()) { // HACK if current is in animation don't start gesture
this._handleActivateGestureVertical(nativeEvent); if (!this.props.transitionProps.position._animation) {
} else { this.positionSwitch.setValue(0);
this._handleActivateGestureHorizontal(nativeEvent);
} }
} }
}; };
@ -454,87 +457,87 @@ class StackViewLayout extends React.Component {
// of the gesturePosition, so if we are in the middle of swiping the screen away // of the gesturePosition, so if we are in the middle of swiping the screen away
// and back is programatically fired then we will reset to the initial position // and back is programatically fired then we will reset to the initial position
// and animate from there // and animate from there
_maybeCancelGesture = () => { _maybeCancelGesture() {
if (this.state.gesturePosition) { this.positionSwitch.setValue(1);
this.setState({ gesturePosition: null }); }
}
};
_handleActivateGestureHorizontal = () => { _prepareGesture() {
let { index } = this.props.transitionProps.navigation.state; if (!this._isGesturesEnabled()) {
if (this.positionSwitch.__getValue() !== 1) {
this.positionSwitch.setValue(1);
}
this.gesturePosition = undefined;
return;
}
if (this._isMotionVertical()) {
this._prepareGestureVertical();
} else {
this._prepareGestureHorizontal();
}
}
_prepareGestureHorizontal() {
const { index } = this.props.transitionProps.navigation.state;
if (this._isMotionInverted()) { if (this._isMotionInverted()) {
this.setState({ this.gesturePosition = Animated.add(
gesturePosition: Animated.add( index,
index, Animated.divide(this.gestureX, this.props.transitionProps.layout.width)
).interpolate({
inputRange: [index - 1, index],
outputRange: [index - 1, index],
extrapolate: 'clamp',
});
} else {
this.gesturePosition = Animated.add(
index,
Animated.multiply(
-1,
Animated.divide( Animated.divide(
this.gestureX, this.gestureX,
this.props.transitionProps.layout.width this.props.transitionProps.layout.width
) )
).interpolate({ )
inputRange: [index - 1, index], ).interpolate({
outputRange: [index - 1, index], inputRange: [index - 1, index],
extrapolate: 'clamp', outputRange: [index - 1, index],
}), extrapolate: 'clamp',
});
} else {
this.setState({
gesturePosition: Animated.add(
index,
Animated.multiply(
-1,
Animated.divide(
this.gestureX,
this.props.transitionProps.layout.width
)
)
).interpolate({
inputRange: [index - 1, index],
outputRange: [index - 1, index],
extrapolate: 'clamp',
}),
}); });
} }
}; }
_handleActivateGestureVertical = () => { _prepareGestureVertical() {
let { index } = this.props.transitionProps.navigation.state; const { index } = this.props.transitionProps.navigation.state;
if (this._isMotionInverted()) { if (this._isMotionInverted()) {
this.setState({ this.gesturePosition = Animated.add(
gesturePosition: Animated.add( index,
index, Animated.divide(this.gestureY, this.props.transitionProps.layout.height)
).interpolate({
inputRange: [index - 1, index],
outputRange: [index - 1, index],
extrapolate: 'clamp',
});
} else {
this.gesturePosition = Animated.add(
index,
Animated.multiply(
-1,
Animated.divide( Animated.divide(
this.gestureY, this.gestureY,
this.props.transitionProps.layout.height this.props.transitionProps.layout.height
) )
).interpolate({ )
inputRange: [index - 1, index], ).interpolate({
outputRange: [index - 1, index], inputRange: [index - 1, index],
extrapolate: 'clamp', outputRange: [index - 1, index],
}), extrapolate: 'clamp',
});
} else {
this.setState({
gesturePosition: Animated.add(
index,
Animated.multiply(
-1,
Animated.divide(
this.gestureY,
this.props.transitionProps.layout.height
)
)
).interpolate({
inputRange: [index - 1, index],
outputRange: [index - 1, index],
extrapolate: 'clamp',
}),
}); });
} }
}; }
_handleReleaseHorizontal = nativeEvent => { _handleReleaseHorizontal(nativeEvent) {
const { const {
transitionProps: { navigation, position, layout }, transitionProps: { navigation, position, layout },
} = this.props; } = this.props;
@ -558,35 +561,35 @@ class StackViewLayout extends React.Component {
// Get the current position value and reset to using the statically driven // Get the current position value and reset to using the statically driven
// (rather than gesture driven) position. // (rather than gesture driven) position.
let value = this._computeHorizontalGestureValue(nativeEvent); const value = this._computeHorizontalGestureValue(nativeEvent);
position.setValue(value); position.setValue(value);
this.setState({ gesturePosition: null }, () => { this.positionSwitch.setValue(1);
// If the speed of the gesture release is significant, use that as the indication
// of intent
if (gestureVelocity < -50) {
this.props.onGestureCanceled && this.props.onGestureCanceled();
this._reset(immediateIndex, resetDuration);
return;
}
if (gestureVelocity > 50) {
this.props.onGestureFinish && this.props.onGestureFinish();
this._goBack(immediateIndex, goBackDuration);
return;
}
// Then filter based on the distance the screen was moved. Over a third of the way swiped, // If the speed of the gesture release is significant, use that as the indication
// and the back will happen. // of intent
if (value <= index - POSITION_THRESHOLD) { if (gestureVelocity < -50) {
this.props.onGestureFinish && this.props.onGestureFinish(); this.props.onGestureCanceled && this.props.onGestureCanceled();
this._goBack(immediateIndex, goBackDuration); this._reset(immediateIndex, resetDuration);
} else { return;
this.props.onGestureCanceled && this.props.onGestureCanceled(); }
this._reset(immediateIndex, resetDuration); if (gestureVelocity > 50) {
} this.props.onGestureFinish && this.props.onGestureFinish();
}); this._goBack(immediateIndex, goBackDuration);
}; return;
}
_handleReleaseVertical = nativeEvent => { // Then filter based on the distance the screen was moved. Over a third of the way swiped,
// and the back will happen.
if (value <= index - POSITION_THRESHOLD) {
this.props.onGestureFinish && this.props.onGestureFinish();
this._goBack(immediateIndex, goBackDuration);
} else {
this.props.onGestureCanceled && this.props.onGestureCanceled();
this._reset(immediateIndex, resetDuration);
}
}
_handleReleaseVertical(nativeEvent) {
const { const {
transitionProps: { navigation, position, layout }, transitionProps: { navigation, position, layout },
} = this.props; } = this.props;
@ -609,33 +612,33 @@ class StackViewLayout extends React.Component {
? movedDistance / velocity ? movedDistance / velocity
: (distance - movedDistance) / velocity; : (distance - movedDistance) / velocity;
let value = this._computeVerticalGestureValue(nativeEvent); const value = this._computeVerticalGestureValue(nativeEvent);
position.setValue(value); position.setValue(value);
this.setState({ gesturePosition: null }, () => { this.positionSwitch.setValue(1);
// If the speed of the gesture release is significant, use that as the indication
// of intent
if (gestureVelocity < -50) {
this.props.onGestureCanceled && this.props.onGestureCanceled();
this._reset(immediateIndex, resetDuration);
return;
}
if (gestureVelocity > 50) {
this.props.onGestureFinish && this.props.onGestureFinish();
this._goBack(immediateIndex, goBackDuration);
return;
}
// Then filter based on the distance the screen was moved. Over a third of the way swiped, // If the speed of the gesture release is significant, use that as the indication
// and the back will happen. // of intent
if (value <= index - POSITION_THRESHOLD) { if (gestureVelocity < -50) {
this.props.onGestureFinish && this.props.onGestureFinish(); this.props.onGestureCanceled && this.props.onGestureCanceled();
this._goBack(immediateIndex, goBackDuration); this._reset(immediateIndex, resetDuration);
} else { return;
this.props.onGestureCanceled && this.props.onGestureCanceled(); }
this._reset(immediateIndex, resetDuration); if (gestureVelocity > 50) {
} this.props.onGestureFinish && this.props.onGestureFinish();
}); this._goBack(immediateIndex, goBackDuration);
}; return;
}
// Then filter based on the distance the screen was moved. Over a third of the way swiped,
// and the back will happen.
if (value <= index - POSITION_THRESHOLD) {
this.props.onGestureFinish && this.props.onGestureFinish();
this._goBack(immediateIndex, goBackDuration);
} else {
this.props.onGestureCanceled && this.props.onGestureCanceled();
this._reset(immediateIndex, resetDuration);
}
}
_getHeaderMode() { _getHeaderMode() {
if (this.props.headerMode) { if (this.props.headerMode) {
@ -662,14 +665,12 @@ class StackViewLayout extends React.Component {
} else if (headerBackgroundTransitionPreset === 'toggle') { } else if (headerBackgroundTransitionPreset === 'toggle') {
return HeaderStyleInterpolator.forBackgroundWithInactiveHidden; return HeaderStyleInterpolator.forBackgroundWithInactiveHidden;
} }
} else { } else if (__DEV__) {
if (__DEV__) { console.error(
console.error( `Invalid configuration applied for headerBackgroundTransitionPreset - expected one of ${HEADER_BACKGROUND_TRANSITION_PRESET.join(
`Invalid configuration applied for headerBackgroundTransitionPreset - expected one of ${HEADER_BACKGROUND_TRANSITION_PRESET.join( ', '
', ' )} but received ${JSON.stringify(headerBackgroundTransitionPreset)}`
)} but received ${JSON.stringify(headerBackgroundTransitionPreset)}` );
);
}
} }
} }
@ -779,41 +780,49 @@ class StackViewLayout extends React.Component {
); );
} }
_getTransitionConfig = () => { _prepareTransitionConfig() {
return TransitionConfigs.getTransitionConfig( this._transitionConfig = TransitionConfigs.getTransitionConfig(
this.props.transitionConfig, this.props.transitionConfig,
{ {
...this.props.transitionProps, ...this.props.transitionProps,
position: this._getPosition(), position: this.position,
}, },
this.props.lastTransitionProps, this.props.lastTransitionProps,
this._isModal() this._isModal()
); );
}; }
_getPosition = () => { _preparePosition() {
if (!this.state.gesturePosition) { if (this.gesturePosition) {
return this.props.transitionProps.position; this.position = Animated.add(
} else { Animated.multiply(
let { gesturePosition } = this.state; this.props.transitionProps.position,
let staticPosition = Animated.add( this.positionSwitch
this.props.transitionProps.position, ),
Animated.multiply(-1, this.props.transitionProps.position) Animated.multiply(this.gesturePosition, this.gestureSwitch)
); );
return Animated.add(gesturePosition, staticPosition); } else {
this.position = this.props.transitionProps.position;
} }
}; }
_renderCard = scene => { _renderCard = scene => {
const { screenInterpolator } = this._getTransitionConfig(); const {
transitionProps,
shadowEnabled,
cardOverlayEnabled,
transparentCard,
cardStyle,
} = this.props;
const { screenInterpolator } = this._transitionConfig;
const style = const style =
screenInterpolator && screenInterpolator &&
screenInterpolator({ screenInterpolator({
...this.props.transitionProps, ...transitionProps,
shadowEnabled: this.props.shadowEnabled, shadowEnabled,
cardOverlayEnabled: this.props.cardOverlayEnabled, cardOverlayEnabled,
position: this._getPosition(), position: this.position,
scene, scene,
}); });
@ -822,20 +831,20 @@ class StackViewLayout extends React.Component {
const { options } = scene.descriptor; const { options } = scene.descriptor;
const hasHeader = options.header !== null; const hasHeader = options.header !== null;
const headerMode = this._getHeaderMode(); const headerMode = this._getHeaderMode();
let paddingTop = 0; let paddingTopStyle;
if (hasHeader && headerMode === 'float' && !options.headerTransparent) { if (hasHeader && headerMode === 'float' && !options.headerTransparent) {
paddingTop = this.state.floatingHeaderHeight; paddingTopStyle = { paddingTop: this.state.floatingHeaderHeight };
} }
return ( return (
<Card <Card
{...this.props.transitionProps} {...transitionProps}
key={`card_${scene.key}`} key={`card_${scene.key}`}
position={this._getPosition()} position={this.position}
realPosition={this.props.transitionProps.position} realPosition={transitionProps.position}
animatedStyle={style} animatedStyle={style}
transparent={this.props.transparentCard} transparent={transparentCard}
style={[{ paddingTop }, this.props.cardStyle]} style={[paddingTopStyle, cardStyle]}
scene={scene} scene={scene}
> >
{this._renderInnerScene(scene)} {this._renderInnerScene(scene)}