mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-28 12:25:21 +08:00
feat: lazy initialized MaterialTopTabNavigator routes (#9)
Routes in `MaterialTopTabNavigator` are now lazy initialized like in `MaterialBottomTabNavigator`. A scene visibility is computed from multiple states and props: To handle the pan between tabs, we check if you're currently swiping between tabs and the prop `lazyOnSwipe` is true (default value) or if the tab have been already loaded, we'll check if this tab is a sibling of the focused tab. Then, we'll display the tab if it's a sibling. ~With the prop `animationEnabled` to true, we shouldn't hide a tab before the transition is done. So we're waiting `COMPLETE_TRANSITION` action to hide it. Also, if the prop `sceneAlwaysVisible` is true (default value), we won't hide scenes between A and D while transitioning.~ If the current tab has not been loaded and must not be visible, we do not render it. I'll update the docs accordingly to this PR.  <!-- #### Default behavior Tabs are lazy initialized on swipe or focus and are always visible while transitioning.  #### Hide tabs between while transitioning ```js { sceneAlwaysVisible: false, } ```  #### Fallback to only lazy initialized tabs on focus ```js { lazyOnSwipe: false, } ```  -->
This commit is contained in:
committed by
satyajit.happy
parent
79e1dacb13
commit
18fa1315cf
@@ -110,7 +110,7 @@ class TabNavigationView extends React.PureComponent<Props, State> {
|
||||
StyleSheet.absoluteFill,
|
||||
{ opacity: isFocused ? 1 : 0 },
|
||||
]}
|
||||
isFocused={isFocused}
|
||||
isVisible={isFocused}
|
||||
>
|
||||
{renderScene({ route })}
|
||||
</ResourceSavingScene>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { View, Platform } from 'react-native';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
import { TabView, PagerPan } from 'react-native-tab-view';
|
||||
import createTabNavigator, {
|
||||
type InjectedProps,
|
||||
@@ -13,10 +14,19 @@ import ResourceSavingScene from '../views/ResourceSavingScene';
|
||||
|
||||
type Props = InjectedProps & {
|
||||
animationEnabled?: boolean,
|
||||
lazy?: boolean,
|
||||
optimizationsEnabled?: boolean,
|
||||
swipeEnabled?: boolean,
|
||||
tabBarPosition?: 'top' | 'bottom',
|
||||
tabBarComponent?: React.ComponentType<*>,
|
||||
tabBarOptions?: TabBarOptions,
|
||||
tabBarPosition?: 'top' | 'bottom',
|
||||
};
|
||||
|
||||
type State = {
|
||||
index: number,
|
||||
isSwiping: boolean,
|
||||
loaded: Array<number>,
|
||||
transitioningFromIndex: ?number,
|
||||
};
|
||||
|
||||
class MaterialTabView extends React.PureComponent<Props> {
|
||||
@@ -25,6 +35,31 @@ class MaterialTabView extends React.PureComponent<Props> {
|
||||
initialLayout: Platform.select({
|
||||
android: { width: 1, height: 0 },
|
||||
}),
|
||||
animationEnabled: true,
|
||||
lazy: false,
|
||||
optimizationsEnabled: false,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const { index } = nextProps.navigation.state;
|
||||
|
||||
if (prevState.index === index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
loaded: prevState.loaded.includes(index)
|
||||
? prevState.loaded
|
||||
: [...prevState.loaded, index],
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
index: 0,
|
||||
isSwiping: false,
|
||||
loaded: [this.props.navigation.state.index],
|
||||
transitioningFromIndex: null,
|
||||
};
|
||||
|
||||
_renderIcon = ({ focused, route, tintColor }) => {
|
||||
@@ -80,22 +115,98 @@ class MaterialTabView extends React.PureComponent<Props> {
|
||||
|
||||
_renderPanPager = props => <PagerPan {...props} />;
|
||||
|
||||
_handleAnimationEnd = () => {
|
||||
const { lazy } = this.props;
|
||||
|
||||
if (lazy) {
|
||||
this.setState({
|
||||
transitioningFromIndex: null,
|
||||
isSwiping: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_handleSwipeStart = () => {
|
||||
const { navigation, lazy } = this.props;
|
||||
|
||||
if (lazy) {
|
||||
this.setState({
|
||||
isSwiping: true,
|
||||
loaded: [
|
||||
...new Set([
|
||||
...this.state.loaded,
|
||||
Math.max(navigation.state.index - 1, 0),
|
||||
Math.min(
|
||||
navigation.state.index + 1,
|
||||
navigation.state.routes.length - 1
|
||||
),
|
||||
]),
|
||||
],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_handleIndexChange = index => {
|
||||
const { animationEnabled, navigation, onIndexChange, lazy } = this.props;
|
||||
|
||||
if (lazy && animationEnabled) {
|
||||
this.setState({
|
||||
transitioningFromIndex: navigation.state.index || 0,
|
||||
});
|
||||
}
|
||||
|
||||
onIndexChange(index);
|
||||
};
|
||||
|
||||
_mustBeVisible = ({ index, focused }) => {
|
||||
const { animationEnabled, navigation } = this.props;
|
||||
const { isSwiping, transitioningFromIndex } = this.state;
|
||||
|
||||
if (isSwiping) {
|
||||
const isSibling =
|
||||
navigation.state.index === index - 1 ||
|
||||
navigation.state.index === index + 1;
|
||||
|
||||
if (isSibling) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The previous tab should remain visible while transitioning
|
||||
if (animationEnabled && transitioningFromIndex === index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return focused;
|
||||
};
|
||||
|
||||
_renderScene = ({ route }) => {
|
||||
const {
|
||||
renderScene,
|
||||
animationEnabled,
|
||||
swipeEnabled,
|
||||
descriptors,
|
||||
lazy,
|
||||
optimizationsEnabled,
|
||||
} = this.props;
|
||||
|
||||
if (animationEnabled === false && swipeEnabled === false) {
|
||||
if (lazy) {
|
||||
const { loaded } = this.state;
|
||||
const { routes } = this.props.navigation.state;
|
||||
const index = routes.findIndex(({ key }) => key === route.key);
|
||||
const { navigation } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
<ResourceSavingScene isFocused={navigation.isFocused()}>
|
||||
{renderScene({ route })}
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
const mustBeVisible = this._mustBeVisible({ index, focused: navigation.isFocused()});
|
||||
|
||||
if (!loaded.includes(index) && !mustBeVisible) {
|
||||
return <View />;
|
||||
}
|
||||
|
||||
if (optimizationsEnabled) {
|
||||
return (
|
||||
<ResourceSavingScene isVisible={mustBeVisible}>
|
||||
{renderScene({ route })}
|
||||
</ResourceSavingScene>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return renderScene({ route });
|
||||
@@ -107,6 +218,8 @@ class MaterialTabView extends React.PureComponent<Props> {
|
||||
animationEnabled,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
renderScene,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onIndexChange,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
@@ -137,6 +250,9 @@ class MaterialTabView extends React.PureComponent<Props> {
|
||||
navigationState={navigation.state}
|
||||
animationEnabled={animationEnabled}
|
||||
swipeEnabled={swipeEnabled}
|
||||
onAnimationEnd={this._handleAnimationEnd}
|
||||
onIndexChange={this._handleIndexChange}
|
||||
onSwipeStart={this._handleSwipeStart}
|
||||
renderPager={renderPager}
|
||||
renderTabBar={this._renderTabBar}
|
||||
renderScene={
|
||||
@@ -148,4 +264,6 @@ class MaterialTabView extends React.PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(MaterialTabView);
|
||||
|
||||
export default createTabNavigator(MaterialTabView);
|
||||
|
||||
@@ -29,7 +29,7 @@ export type InjectedProps = {
|
||||
};
|
||||
|
||||
export default function createTabNavigator(TabView: React.ComponentType<*>) {
|
||||
class NavigationView extends React.Component<*> {
|
||||
class NavigationView extends React.Component<*, State> {
|
||||
_renderScene = ({ route }) => {
|
||||
const { screenProps, descriptors } = this.props;
|
||||
const descriptor = descriptors[route.key];
|
||||
@@ -145,6 +145,14 @@ export default function createTabNavigator(TabView: React.ComponentType<*>) {
|
||||
this._jumpTo(this.props.navigation.state.routes[index].routeName);
|
||||
};
|
||||
|
||||
_handleSwipeStart = () => {
|
||||
this.setState({ isSwiping: true });
|
||||
};
|
||||
|
||||
_handleSwipeEnd = () => {
|
||||
this.setState({ isSwiping: false });
|
||||
};
|
||||
|
||||
_jumpTo = routeName =>
|
||||
this.props.navigation.dispatch(NavigationActions.navigate({ routeName }));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
|
||||
type Props = {
|
||||
isFocused: boolean,
|
||||
isVisible: boolean,
|
||||
children: React.Node,
|
||||
style?: any,
|
||||
};
|
||||
@@ -13,7 +13,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
const { isFocused, children, style, ...rest } = this.props;
|
||||
const { isVisible, children, style, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -22,12 +22,12 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
// This is an workaround for a bug where the clipped view never re-appears
|
||||
Platform.OS === 'ios' ? !isFocused : true
|
||||
Platform.OS === 'ios' ? !isVisible : true
|
||||
}
|
||||
pointerEvents={isFocused ? 'auto' : 'none'}
|
||||
pointerEvents={isVisible ? 'auto' : 'none'}
|
||||
{...rest}
|
||||
>
|
||||
<View style={isFocused ? styles.attached : styles.detached}>
|
||||
<View style={isVisible ? styles.attached : styles.detached}>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user