轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

178 lines
5.5 KiB

import { Ionicons } from '@expo/vector-icons';
import { BlurView } from 'expo-blur';
import { Tabs } from 'expo-router/js-tabs';
import type { BottomTabBarProps } from 'expo-router/js-tabs';
import { useEffect, useRef } from 'react';
import type { ComponentProps, ReactNode } from 'react';
import { Animated, Pressable, StyleSheet, Text, View, type ColorValue } from 'react-native';
import { colors } from '../../src/theme';
type IconName = ComponentProps<typeof Ionicons>['name'];
type TabIconProps = { focused: boolean; color: ColorValue; size: number };
/** 内容底部预留高度:悬浮 Tab 栏高度 + 悬浮间距 + 余量 */
const TAB_BAR_FLOAT_SPACE = 84;
/**
* 选中/未选中切换实心与描边图标
*/
function tabIcon(filled: IconName, outline: IconName) {
return ({ color, size, focused }: TabIconProps) => (
<Ionicons name={focused ? filled : outline} size={size} color={color} />
);
}
/** 单个 Tab 项:选中时图标轻弹 + 浅蓝胶囊高亮 */
function TabItem({
focused,
label,
icon,
onPress,
onLongPress,
}: {
focused: boolean;
label: string;
icon?: (props: TabIconProps) => ReactNode;
onPress: () => void;
onLongPress: () => void;
}) {
// 图标弹跳动画:选中 1.15,未选中回到 1
const scale = useRef(new Animated.Value(1)).current;
useEffect(() => {
Animated.spring(scale, {
toValue: focused ? 1.15 : 1,
useNativeDriver: true,
friction: 6,
tension: 200,
}).start();
}, [focused, scale]);
const color = focused ? colors.primary : colors.textSecondary;
return (
<Pressable
style={styles.tabItem}
onPress={onPress}
onLongPress={onLongPress}
accessibilityRole="button"
accessibilityState={{ selected: focused }}
>
<View style={[styles.tabItemContent, focused && styles.tabItemContentActive]}>
<Animated.View style={{ transform: [{ scale }] }}>
{icon?.({ focused, color, size: 22 })}
</Animated.View>
<Text style={[styles.tabLabel, { color }]}>{label}</Text>
</View>
</Pressable>
);
}
/** 悬浮毛玻璃 Tab 栏 */
function FloatingTabBar({ state, descriptors, navigation, insets }: BottomTabBarProps) {
return (
<View style={styles.tabBarWrap} pointerEvents="box-none">
<View style={[styles.tabBarShadow, { marginBottom: insets.bottom + 12 }]}>
<BlurView intensity={80} tint="light" blurMethod="dimezisBlurViewSdk31Plus" style={styles.tabBar}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const focused = state.index === index;
const label =
typeof options.tabBarLabel === 'string' ? options.tabBarLabel : options.title ?? '';
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () =>
navigation.emit({ type: 'tabLongPress', target: route.key });
return (
<TabItem
key={route.key}
focused={focused}
label={label}
icon={options.tabBarIcon}
onPress={onPress}
onLongPress={onLongPress}
/>
);
})}
</BlurView>
</View>
</View>
);
}
/**
* 底部 Tab 导航:悬浮毛玻璃胶囊 + 选中高亮 + 图标弹跳
*/
export default function TabLayout() {
return (
<Tabs
screenOptions={{
headerShown: false,
// 悬浮 Tab 栏不占布局空间,给每个页面内容底部预留空间,避免被遮挡
sceneStyle: { paddingBottom: TAB_BAR_FLOAT_SPACE },
}}
tabBar={(props) => <FloatingTabBar {...props} />}
>
<Tabs.Screen name="index" options={{ title: '首页', tabBarIcon: tabIcon('home', 'home-outline') }} />
<Tabs.Screen name="jobs" options={{ title: '岗位', tabBarIcon: tabIcon('briefcase', 'briefcase-outline') }} />
<Tabs.Screen name="chat" options={{ title: '沟通', tabBarIcon: tabIcon('chatbubbles', 'chatbubbles-outline') }} />
<Tabs.Screen name="growth" options={{ title: '成长', tabBarIcon: tabIcon('trending-up', 'trending-up-outline') }} />
<Tabs.Screen name="profile" options={{ title: '我的', tabBarIcon: tabIcon('person', 'person-outline') }} />
</Tabs>
);
}
const styles = StyleSheet.create({
tabBarWrap: {
position: 'absolute',
left: 16,
right: 16,
bottom: 0,
},
// 外层负责 iOS 阴影(不裁剪,阴影在圆角外正常显示)
tabBarShadow: {
borderRadius: 24,
shadowColor: colors.textPrimary,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.15,
shadowRadius: 16,
},
// 内层 BlurView:真毛玻璃(iOS)/ 半透明(Android 31 以下降级)+ Android 阴影
tabBar: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 24,
paddingHorizontal: 6,
paddingVertical: 8,
overflow: 'hidden',
elevation: 10,
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
tabItemContent: {
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 18,
},
tabItemContentActive: {
backgroundColor: colors.primaryLight,
},
tabLabel: {
marginTop: 4,
fontSize: 11,
fontWeight: '600',
},
});