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)
我还需要使指示器适合文本大小、标签的动态宽度以及由于长标签而可滚动的顶部栏。结果如下:
如果您不关心指示器宽度适合标签,则可以简单地screenOptions.tabBarScrollEnabled: true与width: "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)
我设法让它与以下组合一起工作:
如果您找到更方便的解决方案,请告诉我。
您必须添加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)
| 归档时间: |
|
| 查看次数: |
9766 次 |
| 最近记录: |