反应本机顶部标签栏导航器:指示器宽度以匹配文本

shi*_*c40 5 react-native react-navigation

我在顶部标签栏导航中有三个具有不同宽度文本的选项卡。是否可以使指示器宽度与文本匹配?类似地,如何使选项卡也与文本的宽度匹配而不使其显示奇怪。我尝试过自动宽度,但它不保持居中。

这是使用自动宽度时的样子: 在此输入图像描述

    <Tab.Navigator 
                initialRouteName="Open"
                tabBarOptions={{
                    style: { 
                      backgroundColor: "white", 
                      paddingTop: 20, 
                      paddingHorizontal: 25
                      },
                    indicatorStyle: {
                      borderBottomColor: colorScheme.teal,
                      borderBottomWidth: 2,
                      width: '30%',
                      left:"9%"
                    },
                    tabStyle : {
                      justifyContent: "center",
                      width: tabBarWidth/3,
                    }
                  }}
            >
                <Tab.Screen 
                    name="Open" 
                    component={WriterRequestScreen} 
                    initialParams={{ screen: 'Open' }} 
                    options={{ 
                      tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> Open </Text>, 
                   }}
                    />
               <Tab.Screen 
                    name="In Progress" 
                    component={WriterRequestScreen} 
                    initialParams={{ screen: 'InProgress' }}
                    options={{ 
                      tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> In Progress </Text>}}
                    />
               <Tab.Screen 
                name="Completed" 
                component={WriterRequestScreen} 
                initialParams={{ screen: 'Completed' }}
                options={{ tabBarLabel: ({focused}) => <Text style = {{fontSize: 18, fontWeight: 'bold', color: focused? colorScheme.teal : colorScheme.grey}}> Completed </Text>}}
                />
            </Tab.Navigator>
Run Code Online (Sandbox Code Playgroud)

Cam*_* Hg 7

我还需要使指示器适合文本大小、标签的动态宽度以及由于长标签而可滚动的顶部栏。结果如下:

具有动态指示器宽度的标签栏

如果您不关心指示器宽度适合标签,则可以简单地screenOptions.tabBarScrollEnabled: truewidth: "auto"in结合使用screenOptions.tabBarIndicatorStyle

否则,您需要制作自己的选项卡栏组件并将其传递给tabBar您的<Tab.Navigator>. 我使用了 a,ScrollView但如果您只有几个带有短标签的选项卡,aView会更简单。以下是此自定义 TabBar 组件的 Typescript 代码:

import { MaterialTopTabBarProps } from "@react-navigation/material-top-tabs";
import { useEffect, useRef, useState } from "react";
import {
  Animated,
  Dimensions,
  View,
  TouchableOpacity,
  StyleSheet,
  ScrollView,
  I18nManager,
  LayoutChangeEvent,
} from "react-native";

const screenWidth = Dimensions.get("window").width;

const DISTANCE_BETWEEN_TABS = 20;

const TabBar = ({
  state,
  descriptors,
  navigation,
  position,
}: MaterialTopTabBarProps): JSX.Element => {
  const [widths, setWidths] = useState<(number | undefined)[]>([]);
  const scrollViewRef = useRef<ScrollView>(null);
  const transform = [];
  const inputRange = state.routes.map((_, i) => i);

  // keep a ref to easily scroll the tab bar to the focused label
  const outputRangeRef = useRef<number[]>([]);

  const getTranslateX = (
    position: Animated.AnimatedInterpolation,
    routes: never[],
    widths: number[]
  ) => {
    const outputRange = routes.reduce((acc, _, i: number) => {
      if (i === 0) return [DISTANCE_BETWEEN_TABS / 2 + widths[0] / 2];
      return [
        ...acc,
        acc[i - 1] + widths[i - 1] / 2 + widths[i] / 2 + DISTANCE_BETWEEN_TABS,
      ];
    }, [] as number[]);
    outputRangeRef.current = outputRange;
    const translateX = position.interpolate({
      inputRange,
      outputRange,
      extrapolate: "clamp",
    });
    return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
  };

  // compute translateX and scaleX because we cannot animate width directly
  if (
    state.routes.length > 1 &&
    widths.length === state.routes.length &&
    !widths.includes(undefined)
  ) {
    const translateX = getTranslateX(
      position,
      state.routes as never[],
      widths as number[]
    );
    transform.push({
      translateX,
    });
    const outputRange = inputRange.map((_, i) => widths[i]) as number[];
    transform.push({
      scaleX:
        state.routes.length > 1
          ? position.interpolate({
              inputRange,
              outputRange,
              extrapolate: "clamp",
            })
          : outputRange[0],
    });
  }

  // scrolls to the active tab label when a new tab is focused
  useEffect(() => {
    if (
      state.routes.length > 1 &&
      widths.length === state.routes.length &&
      !widths.includes(undefined)
    ) {
      if (state.index === 0) {
        scrollViewRef.current?.scrollTo({
          x: 0,
        });
      } else {
        // keep the focused label at the center of the screen
        scrollViewRef.current?.scrollTo({
          x: (outputRangeRef.current[state.index] as number) - screenWidth / 2,
        });
      }
    }
  }, [state.index, state.routes.length, widths]);

  // get the label widths on mount
  const onLayout = (event: LayoutChangeEvent, index: number) => {
    const { width } = event.nativeEvent.layout;
    const newWidths = [...widths];
    newWidths[index] = width - DISTANCE_BETWEEN_TABS;
    setWidths(newWidths);
  };

  // basic labels as suggested by react navigation
  const labels = state.routes.map((route, index) => {
    const { options } = descriptors[route.key];
    const label = route.name;
    const isFocused = state.index === index;

    const onPress = () => {
      const event = navigation.emit({
        type: "tabPress",
        target: route.key,
        canPreventDefault: true,
      });

      if (!isFocused && !event.defaultPrevented) {
        // The `merge: true` option makes sure that the params inside the tab screen are preserved
        // eslint-disable-next-line
        // @ts-ignore
        navigation.navigate({ name: route.name, merge: true });
      }
    };
    const inputRange = state.routes.map((_, i) => i);
    const opacity = position.interpolate({
      inputRange,
      outputRange: inputRange.map((i) => (i === index ? 1 : 0.5)),
    });

    return (
      <TouchableOpacity
        key={route.key}
        accessibilityRole="button"
        accessibilityState={isFocused ? { selected: true } : {}}
        accessibilityLabel={options.tabBarAccessibilityLabel}
        onPress={onPress}
        style={styles.button}
      >
        <View
          onLayout={(event) => onLayout(event, index)}
          style={styles.buttonContainer}
        >
          <Animated.Text style={[styles.text, { opacity }]}>
            {label}
          </Animated.Text>
        </View>
      </TouchableOpacity>
    );
  });

  return (
    <View style={styles.contentContainer}>
      <Animated.ScrollView
        horizontal
        ref={scrollViewRef}
        showsHorizontalScrollIndicator={false}
        style={styles.container}
      >
        {labels}
        <Animated.View style={[styles.indicator, { transform }]} />
      </Animated.ScrollView>
    </View>
  );
};

const styles = StyleSheet.create({
  button: {
    alignItems: "center",
    justifyContent: "center",
  },
  buttonContainer: {
    paddingHorizontal: DISTANCE_BETWEEN_TABS / 2,
  },
  container: {
    backgroundColor: "black",
    flexDirection: "row",
    height: 34,
  },
  contentContainer: {
    height: 34,
    marginTop: 30,
  },
  indicator: {
    backgroundColor: "white",
    bottom: 0,
    height: 3,
    left: 0,
    position: "absolute",
    right: 0,
    // this must be 1 for the scaleX animation to work properly
    width: 1,
  },
  text: {
    color: "white",
    fontSize: 14,
    textAlign: "center",
  },
});

export default TabBar;
Run Code Online (Sandbox Code Playgroud)

我设法让它与以下组合一起工作:

如果您找到更方便的解决方案,请告诉我。


Tad*_*tra 1

您必须添加width:auto到 tabStyle 以使选项卡宽度灵活。

然后在每个 tabBarLabel<Text>组件内添加 styletextAlign: "center"width: YOUR_WIDTH.

YOUR_WIDTH每个选项卡可以不同,并且可以是您的 text.length * 10 (如果您想让它取决于您的文本长度)或从 Dimensions 获取屏幕宽度并将其除以任何其他数字以使其在屏幕中的宽度相等。例子:

const win = Dimensions.get('window');
Run Code Online (Sandbox Code Playgroud)

...

bigTab: {
    fontFamily: "Mulish-Bold",
    fontSize: 11,
    color: "#fff",
    textAlign: "center",
    width: win.width/2 - 40
},
smallTab: {
    fontFamily: "Mulish-Bold",
    fontSize: 11,
    color: "#fff",
    textAlign: "center",
    width: win.width / 5 + 10
}
Run Code Online (Sandbox Code Playgroud)