CustomTabBar.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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] as string) === 'weal' || (segments[1] as string) === 'welfare' || pathname?.startsWith('/weal') || 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] as string) === '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}>
  72. <ImageBackground
  73. source={{ uri: Images.common.tabsBg }}
  74. style={styles.container}
  75. resizeMode="cover"
  76. >
  77. <View style={styles.center}>
  78. {tabList.map((item, index) => (
  79. <TouchableOpacity
  80. key={index}
  81. style={styles.item}
  82. activeOpacity={0.8}
  83. onPress={() => handlePress(index)}
  84. >
  85. <Image
  86. source={currentIndex === index ? item.active : item.img}
  87. style={styles.icon}
  88. contentFit="fill"
  89. />
  90. </TouchableOpacity>
  91. ))}
  92. </View>
  93. </ImageBackground>
  94. </View>
  95. );
  96. }
  97. const styles = StyleSheet.create({
  98. wrapper: {
  99. position: 'absolute',
  100. bottom: 0,
  101. left: 0,
  102. right: 0,
  103. zIndex: 999,
  104. },
  105. container: {
  106. width: SCREEN_WIDTH,
  107. height: 78,
  108. },
  109. center: {
  110. flex: 1,
  111. flexDirection: 'row',
  112. },
  113. item: {
  114. flex: 1,
  115. justifyContent: 'center',
  116. alignItems: 'center',
  117. },
  118. icon: {
  119. width: '100%',
  120. height: '100%',
  121. },
  122. });