React Navigation源代码阅读 : views/TabView/TabView.js

import React from 'react';
import {View, StyleSheet, Platform} from 'react-native';
import {TabViewAnimated, TabViewPagerPan} from 'react-native-tab-view';
import SafeAreaView from 'react-native-safe-area-view';

import ResourceSavingSceneView from '../ResourceSavingSceneView';
import withCachedChildNavigation from '../../withCachedChildNavigation';

/**
 * 基于 react-native-tab-view 的 TabViewAnimated 的 Tab 视图组件,用于 react-navigation 的 TabNavigator
 */
class TabView extends React.PureComponent {
    static defaultProps = {
        lazy: true,
        removedClippedSubviews: true,
        // fix for https://github.com/react-native-community/react-native-tab-view/issues/312
        initialLayout: Platform.select({
            android: {width: 1, height: 0},
        }),
    };

    _handlePageChanged = index => {
        const {navigation} = this.props;
        navigation.navigate(navigation.state.routes[index].routeName);
    };

    /**
     * 渲染场景组件,场景组件一般由开发人员提供,通过导航器参数 routeConfigs 设置进来
     * @param route
     * @return {*}
     * @private
     */
    _renderScene = ({route}) => {
        const {screenProps, navigation} = this.props;
        // 获取当前聚焦的路由的 index , key
        const focusedIndex = navigation.state.index;
        const focusedKey = navigation.state.routes[focusedIndex].key;
        // 获取当前操作的路由 route 的 key
        const key = route.key;
        const childNavigation = this.props.childNavigationProps[route.key];
        // 获取所操作的路由 route 的屏幕组件
        const TabComponent = this.props.router.getComponentForRouteName(
            route.routeName
        );

        // 采用 ResourceSavingSceneView 渲染所操作路由对应的场景组件 TabComponent,
        // ResourceSavingSceneView 仅在所操作的route 是当前聚焦路由时才真正渲染它,
        // 这也正是它 resource saving (资源节省) 的意思所在
        return (
            <ResourceSavingSceneView
                lazy={this.props.lazy}
                isFocused={focusedKey === key}
                removeClippedSubViews={this.props.removeClippedSubviews}
                animationEnabled={this.props.animationEnabled}
                swipeEnabled={this.props.swipeEnabled}
                screenProps={screenProps}
                component={TabComponent}
                navigation={this.props.navigation}
                childNavigation={childNavigation}
            />
        );
    };

    /**
     * 获取 Tab 对应的标题
     * 1. 首先尝试通过 navigationOptions.tabBarLabel function 获取;
     * 2. 然后尝试通过 navigationOptions.tabBarLabel string 获取;
     * 3. 然后尝试通过 navigationOptions.title string 获取;
     * 3. 然后使用 routeName 作为 Tab 标题.
     * @param route
     * @param tintColor
     * @param focused
     * @return {*}
     * @private
     */
    _getLabel = ({route, tintColor, focused}) => {
        const options = this.props.router.getScreenOptions(
            this.props.childNavigationProps[route.key],
            this.props.screenProps || {}
        );

        if (options.tabBarLabel) {
            return typeof options.tabBarLabel === 'function'
                ? options.tabBarLabel({tintColor, focused})
                : options.tabBarLabel;
        }

        if (typeof options.title === 'string') {
            return options.title;
        }

        return route.routeName;
    };

    /**
     * 获取外部指定的 TabBar 点击回调,外部也可以不指定,
     * 外部不指定的时候所使用的 tabBarComponent 组件要
     * 自己处理点击事件,缺省的 tabBarComponent TabBarBottom
     * 就提供了自己的点击事件处理逻辑,一般情况下都符合我们的需求
     * @param previousScene
     * @param route
     * @return {*}
     * @private
     */
    _getOnPress = (previousScene, {route}) => {
        const options = this.props.router.getScreenOptions(
            this.props.childNavigationProps[route.key],
            this.props.screenProps || {}
        );

        return options.tabBarOnPress;
    };

    _getTestIDProps = ({route, focused}) => {
        const options = this.props.router.getScreenOptions(
            this.props.childNavigationProps[route.key],
            this.props.screenProps || {}
        );

        return typeof options.tabBarTestIDProps === 'function'
            ? options.tabBarTestIDProps({focused})
            : options.tabBarTestIDProps;
    };

    /**
     * 渲染 Tab 上的图标
     * 1. 首先尝试通过 navigationOptions.tabBarIcon function 获取;
     * 2. 然后尝试通过 navigationOptions.tabBarIcon 获取;
     * @param focused
     * @param route
     * @param tintColor
     * @return {*}
     * @private
     */
    _renderIcon = ({focused, route, tintColor}) => {
        const options = this.props.router.getScreenOptions(
            this.props.childNavigationProps[route.key],
            this.props.screenProps || {}
        );
        if (options.tabBarIcon) {
            return typeof options.tabBarIcon === 'function'
                ? options.tabBarIcon({tintColor, focused})
                : options.tabBarIcon;
        }
        return null;
    };

    /**
     * 渲染一个 TabBar, 主要是 Tab 上的标题,图标,以及点击回调
     * 1. 获取标题 _getLabel
     * 2. 渲染图标 _renderIcon
     * 3. 点击回调 _getOnPress
     * @param props
     * @return {*}
     * @private
     */
    _renderTabBar = props => {
        const {
            tabBarOptions,
            tabBarComponent: TabBarComponent,
            animationEnabled,
        } = this.props;
        if (typeof TabBarComponent === 'undefined') {
            return null;
        }

        return (
            <TabBarComponent
                {...props}
                {...tabBarOptions}
                tabBarPosition={this.props.tabBarPosition}
                screenProps={this.props.screenProps}
                navigation={this.props.navigation}
                getLabel={this._getLabel}
                getTestIDProps={this._getTestIDProps}
                getOnPress={this._getOnPress}
                renderIcon={this._renderIcon}
                animationEnabled={animationEnabled}
            />
        );
    };

    _renderPager = props => <TabViewPagerPan {...props} />;

    render() {
        const {
            router,
            tabBarComponent,
            tabBarPosition,
            animationEnabled,
            configureTransition,
            initialLayout,
            screenProps,
        } = this.props;

        let renderHeader;
        let renderFooter;
        let renderPager;

        const {state} = this.props.navigation;
        const options = router.getScreenOptions(
            this.props.childNavigationProps[state.routes[state.index].key],
            screenProps || {}
        );

        // 确定是否显示 Tab, 根据参数  navigationOptions.tabBarVisible
        const tabBarVisible =
            options.tabBarVisible == null ? true : options.tabBarVisible;


        // 确定是否允许滑动切换Tab页面
        let swipeEnabled =
            options.swipeEnabled == null
                ? this.props.swipeEnabled
                : options.swipeEnabled;

        if (typeof swipeEnabled === 'function') {
            swipeEnabled = swipeEnabled(state);
        }

        // 决定把TabBar放到头部还是底部
        if (tabBarComponent !== undefined && tabBarVisible) {
            if (tabBarPosition === 'bottom') {
                renderFooter = this._renderTabBar;
            } else {
                renderHeader = this._renderTabBar;
            }
        }

        // 确定是否使用 renderPager, 如果不指定,则 TabViewAnimated 会使用缺省的 renderPager
        if (
            (animationEnabled === false && swipeEnabled === false) ||
            typeof configureTransition === 'function'
        ) {
            renderPager = this._renderPager;
        }

        const props = {
            initialLayout,
            animationEnabled,
            configureTransition,
            swipeEnabled,
            renderPager,
            renderHeader,
            renderFooter,
            renderScene: this._renderScene,
            onIndexChange: this._handlePageChanged,
            navigationState: this.props.navigation.state,
            screenProps: this.props.screenProps,
            style: styles.container,
        };

        return <TabViewAnimated {...props} />;
    }
}

export default withCachedChildNavigation(TabView);

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
});

猜你喜欢

转载自blog.csdn.net/andy_zhang2007/article/details/80580838