CustomTabBar.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { Images } from '@/constants/images';
  2. import { Image } from 'expo-image';
  3. import { usePathname, useRouter, useSegments } from 'expo-router';
  4. import React from 'react';
  5. import { Dimensions, ImageBackground, StyleSheet, TouchableOpacity, View } from 'react-native';
  6. import { useSafeAreaInsets } from 'react-native-safe-area-context';
  7. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  8. const tabList = [
  9. {
  10. name: '首页',
  11. route: '/',
  12. img: Images.tabs.home,
  13. active: Images.tabs.homeActive,
  14. },
  15. {
  16. name: '开箱',
  17. route: '/box',
  18. img: Images.tabs.box,
  19. active: Images.tabs.boxActive,
  20. },
  21. {
  22. name: '福利',
  23. route: '/welfare',
  24. img: Images.tabs.welfare,
  25. active: Images.tabs.welfareActive,
  26. },
  27. {
  28. name: '我的',
  29. route: '/mine',
  30. img: Images.tabs.mine,
  31. active: Images.tabs.mineActive,
  32. },
  33. ];
  34. export function CustomTabBar() {
  35. const router = useRouter();
  36. const segments = useSegments();
  37. const pathname = usePathname();
  38. const insets = useSafeAreaInsets();
  39. const getTabIndex = () => {
  40. // Check Box
  41. if (segments[1] === 'box' || pathname?.startsWith('/box')) return 1;
  42. // Check Welfare
  43. if (segments[1] === 'welfare' || pathname?.startsWith('/welfare')) return 2;
  44. // Check Mine
  45. if (segments[1] === 'mine' || pathname?.startsWith('/mine')) return 3;
  46. // Check Home (Explicit)
  47. // Home is usually index. or path /
  48. if (segments[1] === 'index' || pathname === '/' || pathname === '/index') return 0;
  49. // No valid tab match (e.g. navigating to detail page)
  50. return -1;
  51. };
  52. // Initialize with correct value to avoid mount flash
  53. const [currentIndex, setCurrentIndex] = React.useState(() => {
  54. const idx = getTabIndex();
  55. return idx === -1 ? 0 : idx;
  56. });
  57. // Update only when valid match found (avoids unmatch flash)
  58. React.useEffect(() => {
  59. const idx = getTabIndex();
  60. if (idx !== -1) {
  61. setCurrentIndex(idx);
  62. }
  63. }, [segments, pathname]);
  64. const handlePress = (index: number) => {
  65. const route = tabList[index].route;
  66. router.replace(route as any);
  67. };
  68. // 计算底部安全区域高度
  69. const bottomPadding = insets.bottom;
  70. return (
  71. <View style={[styles.wrapper, { paddingBottom: bottomPadding }]}>
  72. <ImageBackground
  73. style={styles.container}
  74. resizeMode="cover"
  75. >
  76. <View style={styles.center}>
  77. {tabList.map((item, index) => (
  78. <TouchableOpacity
  79. key={index}
  80. style={styles.item}
  81. activeOpacity={0.8}
  82. onPress={() => handlePress(index)}
  83. >
  84. <Image
  85. source={currentIndex === index ? item.active : item.img}
  86. style={styles.icon}
  87. contentFit="contain"
  88. />
  89. </TouchableOpacity>
  90. ))}
  91. </View>
  92. </ImageBackground>
  93. </View>
  94. );
  95. }
  96. const styles = StyleSheet.create({
  97. wrapper: {
  98. position: 'absolute',
  99. bottom: 0,
  100. left: 0,
  101. right: 0,
  102. zIndex: 999,
  103. },
  104. container: {
  105. width: SCREEN_WIDTH,
  106. height: 78,
  107. },
  108. center: {
  109. flex: 1,
  110. flexDirection: 'row',
  111. },
  112. item: {
  113. flex: 1,
  114. justifyContent: 'center',
  115. alignItems: 'center',
  116. },
  117. icon: {
  118. width: '100%',
  119. height: '100%',
  120. },
  121. });