In the NavigationCompositionExample, tabs are implemented using a NavigationView
. NavigationView
renders a single Scene
and does so by unmounting any previous Scene
s. This is not desirable as component state is lost when switching tabs.
I'm trying to implement tabs using AnimatedView
and StackReducer
to keep the views mounted when navigated to and from using JumpToIndex
or JumpTo
.
+------------+
+-+ |
+-+ | |
| | | |
| | | Focused |
| | | Card |
| | | |
+-+ | |
+-+ |
+------------+
AnimatedView
renders each Card
in the order Scene
s are present in ParentState
. It assumes the children.length-1
is the focused Card
. So setting index in ParentState
to anything other than the last Scene
has no effect except that the overlay renders correctly since it's passed the Scene
at the correct index when render is called:
renderOverlay({
layout: this._layout,
navigationState,
onNavigate,
position,
scene: scenes[navigationState.index],
scenes,
});
As for the Scenes
, the focused Card
renders as the one at children.length-1
. So the index
provided by the reducer is ignored.
One way to fix this is to look at how AnimatedView
delegates transition management to StyleInterpolator
s.
Take forHorizontal
for example:
function forHorizontal(props: NavigationSceneRendererProps): Object {
const {
layout,
position,
scene,
} = props;
const index = scene.index;
const inputRange = [index - 1, index, index + 1];
const width = layout.initWidth;
const opacity = position.interpolate({
inputRange,
outputRange: [1, 1, 0.3],
});
const scale = position.interpolate({
inputRange,
outputRange: [1, 1, 0.95],
});
const translateY = 0;
const translateX = position.interpolate({
inputRange,
outputRange: [width, 0, -10],
});
return {
opacity,
transform: [
{ scale },
{ translateX },
{ translateY },
],
};
}
Here the implementation "configures" the focused Card
to be the last in the stack. Since, StyleInterpolator
is the delegate that coordinates how the actual views transition on the screen, unlike AnimatedView
, it's possible to change the implementation to render the focused view correctly and make transition animations work as expected.
The solution ๐ง I came up with is to push the views to the right of the focused Card
off screen:
function forHorizontal(props: NavigationSceneRendererProps): Object {
const {
layout,
position,
scene,
} = props;
const index = scene.index;
const inputRange = [index - 1, index, index + 1];
const width = layout.initWidth;
const translateY = 0;
if(props.navigationState.index < scene.index && width <= 0) {
// Move off screen
const translateX = layout.width.interpolate({
inputRange: [0, 1],
outputRange: [0, -1],
});
return {
opacity: 0, // Avoid flicker
transform: [
{ translateX },
{ translateY },
],
};
}
const opacity = position.interpolate({
inputRange,
outputRange: [1, 1, 0.3],
});
const scale = position.interpolate({
inputRange,
outputRange: [1, 1, 0.95],
});
const translateX = position.interpolate({
inputRange,
outputRange: [width, 0, -10],
});
return {
opacity,
transform: [
{ scale },
{ translateX },
{ translateY },
],
};
}
Here I'm using width <= 0
๐ญ to determine the direction of navigation. A better way would be to include a prevIndex value in the ParentState
type so that we can do this:
navigationState.index >= navigationState.prevIndex
Of course, we'd also have to make some changes to jumpToIndex
, jumpTo
etc in NavigationStateUtils to implicitly keep track of prevIndex.
Alternatively, we could create a custom reducer to achieve pretty much the same thing. But that feels like boilerplate.
{
...NavigationStateUtils.jumpToIndex(state, state.index+1),
prevIndex: state.index
}
For those of you who are looking into implementing tabs using this approach, the transition animations can be "turned off" for tabs by supplying an applyAnimation
fn to AnimatedView
:
function applyAnimation(
position: NavigationAnimatedValue,
navigationState: NavigationParentState): void {
position.setValue(navigationState.index);
}
<NavigationAnimatedView
applyAnimation={applyAnimation}
... />
So far this solution has been working great. But I strongly think some of this can be moved into NavigationExperimental
.
I'd be happy to turn some of these ideas into a PR. Or perhaps there is another way to achieve this that I'm missing?
Oh and, thanks for all the work you guys have been doing on these. ๐
๐บ