React Native系列之十八react-navigation加自定义背景导航

首先看下效果


react-navigation是不支持自定义背景的,但产品有需求没办法。只能扩展喽~~  注意那棵小树的处理,高度是透出来的,并且整个背景是在page之上的!只有后面两个按钮可在页面中进行单击,滑动跳转。单击游戏,则退出此业务模块。

修改如下:

1.node_modules/react-navigation/src/views/TabView/TabView.js

在外面将背景图和style传参进来,在_renderTabBar方法中实现渲染背景图,并根据传参是否支持背景图,红色部分为主要修改位置, 请注意_renderTabBar()方法的修改

/* @flow */

import * as React from 'react';
import {
    Image,
    Text,
} from 'react-native';
import {View, StyleSheet, Platform} from 'react-native';
import {TabViewAnimated, TabViewPagerPan} from 'react-native-tab-view';
import type {Layout} from 'react-native-tab-view/src/TabViewTypeDefinitions';
import SceneView from '../SceneView';
import withCachedChildNavigation from '../../withCachedChildNavigation';
import SafeAreaView from '../SafeAreaView';

import type {
    NavigationScreenProp,
    NavigationRoute,
    NavigationState,
    NavigationRouter,
    NavigationTabScreenOptions,
} from '../../TypeDefinition';

export
type
TabViewConfig = {
    tabBarComponent? : React.ComponentType < * >,
    tabBarPosition? : 'top' | 'bottom',
    tabBarOptions? : {},
    swipeEnabled? : boolean,
    animationEnabled? : boolean,
    configureTransition? : (currentTransitionProps: Object,
                            nextTransitionProps: Object) => Object,
    lazy? : boolean,
    initialLayout? : Layout,
};

export
type
TabScene = {
    route: NavigationRoute,
    focused: boolean,
    index: number,
    tintColor? : ? string,
};

type
Props = {
    tabBarComponent? : React.ComponentType < * >,
    tabBarPosition? : 'top' | 'bottom',
    tabBarOptions? : {},
    swipeEnabled? : boolean,
    animationEnabled? : boolean,
    configureTransition? : (currentTransitionProps: Object,
                            nextTransitionProps: Object) => Object,
    lazy? : boolean,
    initialLayout: Layout,

    screenProps? : {},
    navigation: NavigationScreenProp < NavigationState >,
    router: NavigationRouter < NavigationState, NavigationTabScreenOptions >,
    childNavigationProps
:
{
    [key
:
    string
]:
    NavigationScreenProp < NavigationRoute >,
}
,
}
;

class TabView extends React.PureComponent<Props> {
    static defaultProps = {
        // fix for https://github.com/react-native-community/react-native-tab-view/issues/312
        initialLayout: Platform.select({
            android: {width: 1, height: 0},
        }),
    };

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

    _renderScene = ({route}: any) => {
        const {screenProps} = this.props;
        const childNavigation = this.props.childNavigationProps[route.key];
        const TabComponent = this.props.router.getComponentForRouteName(
            route.routeName
        );
        return (
            <View style={styles.page}>
                <SceneView
                    screenProps={screenProps}
                    component={TabComponent}
                    navigation={childNavigation}
                />
            </View>
        );
    };

    _getLabel = ({route, tintColor, focused}: TabScene) => {
        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;
    };

    _getOnPress = (previousScene: TabScene, {route}: TabScene) => {
        const options = this.props.router.getScreenOptions(
            this.props.childNavigationProps[route.key],
            this.props.screenProps || {}
        );

        return options.tabBarOnPress;
    };

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

        return options.tabBarTestIDProps;
    };

    _renderIcon = ({focused, route, tintColor}: TabScene) => {
        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;
    };

    _renderTabBar = (props: *) => {
        const {
            tabBarOptions,
            tabBarComponent: TabBarComponent,
            animationEnabled,
        } = this.props;
        if (typeof TabBarComponent === 'undefined') {
            return null;
        }
        const bgImgOptions = tabBarOptions.bgImgOptions;
        const marginTopValue = bgImgOptions && bgImgOptions.showBgImg ? bgImgOptions.marginTop : 0;
        return (
            <View style={{marginTop: marginTopValue}}>
                {this._renderBgImg(bgImgOptions)}
                <TabBarComponent
                    {...props}
                    {...tabBarOptions}
                    screenProps={this.props.screenProps}
                    navigation={this.props.navigation}
                    getLabel={this._getLabel}
                    getTestIDProps={this._getTestIDProps}
                    getOnPress={this._getOnPress}
                    renderIcon={this._renderIcon}
                    animationEnabled={animationEnabled}
                />
            </View>
        );
    };

    _renderBgImg(bgImgOptions) {
        if (bgImgOptions && bgImgOptions.showBgImg) {
            const widthValue = bgImgOptions.width;
            const heightValue = bgImgOptions.height;
            return (
                <View>
                    <Image style={{width: widthValue, height: heightValue, position: 'absolute'}}
                           source={bgImgOptions.bgImg} resizeMode="cover"/>
                </View>
            );
        }
    }

    _renderPager = (props: *) => <TabViewPagerPan {...props} />;

    render() {
        const {
            router,
            tabBarComponent,
            tabBarPosition,
            animationEnabled,
            configureTransition,
            swipeEnabled,
            lazy,
            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 || {}
        );

        const tabBarVisible =
            options.tabBarVisible == null ? true : options.tabBarVisible;

        if (tabBarComponent !== undefined && tabBarVisible) {
            if (tabBarPosition === 'bottom') {
                renderFooter = this._renderTabBar;
            } else {
                renderHeader = this._renderTabBar;
            }
        }

        if (
            (animationEnabled === false && swipeEnabled === false) ||
            typeof configureTransition === 'function'
        ) {
            renderPager = this._renderPager;
        }

        const props = {
            lazy,
            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,
        };

        // $FlowFixMe: mismatch with react-native-tab-view type
        return <TabViewAnimated {...props} />;
    }
}

export default withCachedChildNavigation(TabView);

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

    page: {
        flex: 1,
        overflow: 'hidden',
        backgroundColor: 'transparent',
    },
});

2.如果有需求,第一个按钮直接返回到主界面,相对来说,当前模块只是一个子业务模块的时候,我们可以改变下面TabBottom的布局,android和iOS的平台不一致,如有需要只改变:

npde_modules/react-native-tab-view/TabBar.js 插入红色的部分,此为render方法中return实现// android平台下面的tabBar的实现

    return (
      <Animated.View style={[styles.tabBar, this.props.style]}>
        <Animated.View
          pointerEvents="none"
          style={[
            styles.indicatorContainer,
            scrollEnabled
              ? { width: tabBarWidth, transform: [{ translateX }] }
              : null,
          ]}
        >
          {this._renderIndicator({
            ...this.props,
            width: new Animated.Value(finalTabWidth),
          })}
        </Animated.View>
        <View style={styles.scroll}>
          <ScrollView
            horizontal
            keyboardShouldPersistTaps="always"
            scrollEnabled={scrollEnabled}
            bounces={false}
            alwaysBounceHorizontal={false}
            scrollsToTop={false}
            showsHorizontalScrollIndicator={false}
            automaticallyAdjustContentInsets={false}
            overScrollMode="never"
            contentContainerStyle={[
              styles.tabContent,
              scrollEnabled ? null : styles.container,
            ]}
            scrollEventThrottle={16}
            onScroll={this._handleScroll}
            onScrollBeginDrag={this._handleBeginDrag}
            onScrollEndDrag={this._handleEndDrag}
            onMomentumScrollBegin={this._handleMomentumScrollBegin}
            onMomentumScrollEnd={this._handleMomentumScrollEnd}
            contentOffset={this.state.initialOffset}
            ref={this._setRef}
          >
            {this.props.gameButton}
            {routes.map((route, i) => {
              const focused = index === i;
              const outputRange = inputRange.map(
                inputIndex => (inputIndex === i ? 1 : 0.7)
              );
              const opacity = Animated.multiply(
                this.state.visibility,
                position.interpolate({
                  inputRange,
                  outputRange,
                })
              );
              const scene = {
                route,
                focused,
                index: i,
              };
              const label = this._renderLabel(scene);
              const icon = this.props.renderIcon
                ? this.props.renderIcon(scene)
                : null;
              const badge = this.props.renderBadge
                ? this.props.renderBadge(scene)
                : null;

              const tabStyle = {};

              tabStyle.opacity = opacity;

              if (icon) {
                if (label) {
                  tabStyle.paddingTop = 8;
                } else {
                  tabStyle.padding = 12;
                }
              }

              const passedTabStyle = StyleSheet.flatten(this.props.tabStyle);
              const isWidthSet =
                (passedTabStyle &&
                  typeof passedTabStyle.width !== 'undefined') ||
                scrollEnabled === true;
              const tabContainerStyle = {};

              if (isWidthSet) {
                tabStyle.width = finalTabWidth;
              }

              if (passedTabStyle && typeof passedTabStyle.flex === 'number') {
                tabContainerStyle.flex = passedTabStyle.flex;
              } else if (!isWidthSet) {
                tabContainerStyle.flex = 1;
              }

              const accessibilityLabel =
                route.accessibilityLabel || route.title;

              return (
                <TouchableItem
                  borderless
                  key={route.key}
                  testID={route.testID}
                  accessible={route.accessible}
                  accessibilityLabel={accessibilityLabel}
                  accessibilityTraits="button"
                  pressColor={this.props.pressColor}
                  pressOpacity={this.props.pressOpacity}
                  delayPressIn={0}
                  onPress={() => {
                    // eslint-disable-line react/jsx-no-bind
                    const { onTabPress, jumpToIndex } = this.props;
                    jumpToIndex(i);
                    if (onTabPress) {
                      onTabPress(scene);
                    }
                  }}
                  style={tabContainerStyle}
                >
                  <View pointerEvents="none" style={styles.container}>
                    <Animated.View
                      style={[
                        styles.tabItem,
                        tabStyle,
                        passedTabStyle,
                        styles.container,
                      ]}
                    >
                      {icon}
                      {label}
                    </Animated.View>
                    {badge ? (
                      <Animated.View
                        style={[
                          styles.badge,
                          { opacity: this.state.visibility },
                        ]}
                      >
                        {badge}
                      </Animated.View>
                    ) : null}
                  </View>
                </TouchableItem>
              );
            })}
          </ScrollView>
        </View>
      </Animated.View>
    );

在node_modules/react-navigation/src/views/TabView/TabBarBottom.js下插入红色部分,代码为render方法中的的return部分// 针对iOS

    return (
      <Animated.View style={animateStyle}>
        <SafeAreaView
          style={tabBarStyle}
          forceInset={{ bottom: 'always', top: 'never' }}
        >
          {this.props.gameButton}
          {routes.map((route: NavigationRoute, index: number) => {
            const focused = index === navigation.state.index;
            const scene = { route, index, focused };
            const onPress = getOnPress(previousScene, scene);
            const outputRange = inputRange.map(
              (inputIndex: number) =>
                inputIndex === index
                  ? activeBackgroundColor
                  : inactiveBackgroundColor
            );
            const backgroundColor = position.interpolate({
              inputRange,
              outputRange: (outputRange: Array<string>),
            });

            const justifyContent = this.props.showIcon ? 'flex-end' : 'center';
            const extraProps = this._renderTestIDProps(scene) || {};
            const { testID, accessibilityLabel } = extraProps;

            return (
              <TouchableWithoutFeedback
                key={route.key}
                testID={testID}
                accessibilityLabel={accessibilityLabel}
                onPress={() =>
                  onPress
                    ? onPress({ previousScene, scene, jumpToIndex })
                    : jumpToIndex(index)}
              >
                <Animated.View
                  style={[
                    styles.tab,
                    isLandscape && useHorizontalTabs && styles.tabLandscape,
                    !isLandscape && useHorizontalTabs && styles.tabPortrait,
                    { backgroundColor },
                    tabStyle,
                  ]}
                >
                  {this._renderIcon(scene)}
                  {this._renderLabel(scene)}
                </Animated.View>
              </TouchableWithoutFeedback>
            );
          })}
        </SafeAreaView>
      </Animated.View>
    );

3.使用的地方tabBarOptions里增加参数bgImgOptions

    //设置Tab标签的属性
    tabBarOptions: {
        upperCaseLabel: false,
        showIcon: true,//是否显示图标,默认关闭
        showLabel: true,//是否显示label,默认开启
        activeTintColor: 'white',//label和icon的前景色 活跃状态下(选中)
        inactiveTintColor: '#FCFCFC',//label和icon的前景色 活跃状态下(未选中)
        allowFontScaling: false,
        style: Platform.OS === 'ios' ? iosTabBarStyle : androidTabBarStyle, //TabNavigator 的背景颜色
        indicatorStyle: {//标签指示器的样式对象(选项卡底部的行)。安卓底部会多出一条线,可以将height设置为0来暂时解决这个问题
            height: 0,
        },
        labelStyle: {//文字的样式
            fontSize: 13,
            marginTop: -5,
            marginBottom: 5,
        },
        iconStyle: {//图标的样式
            width: 48,
            height:48,
        },
        bgImgOptions: {
            showBgImg: "true",
            width: ScreenWidth,
            height: 80,
            marginTop: -30,
            bgImg: require('../img/tabicon/tab_bg.png'), // 此为TabBar添加背景图   
            gameButton:<Text style={{width:100}}>退出游戏</Text> // 此为在TabBar中增加额外的View元素进行退出子业务模块操作
    }

感谢大家的关注!

猜你喜欢

转载自blog.csdn.net/yeputi1015/article/details/80821292