u
This commit is contained in:
1 parent
737aa98d18
commit
3bbccd616d
95 files changed
+858
-22766
No files matched your search
@@ -1,199 +0,0 @@
|
||||
import { usePathname, useRouter } from 'expo-router'
|
||||
import { TabList, Tabs, TabSlot, TabTrigger } from 'expo-router/ui'
|
||||
import { icons } from 'lucide-react-native'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import Animated, { useAnimatedStyle, useSharedValue, withSequence, withTiming } from 'react-native-reanimated'
|
||||
import { scheduleOnRN } from 'react-native-worklets'
|
||||
|
||||
interface AppTabbarProps {
|
||||
data: TabbarItem[]
|
||||
}
|
||||
|
||||
export interface TabbarItem {
|
||||
icon: keyof typeof icons
|
||||
iconSize?: number
|
||||
activeIcon?: keyof typeof icons
|
||||
name: string
|
||||
href?: string
|
||||
onPress?: () => void
|
||||
}
|
||||
|
||||
function TabButton({
|
||||
icon,
|
||||
activeIcon,
|
||||
iconSize = 24,
|
||||
isFocused = false,
|
||||
onPress,
|
||||
}: {
|
||||
icon: keyof typeof icons
|
||||
activeIcon?: keyof typeof icons
|
||||
iconSize?: number
|
||||
isFocused?: boolean
|
||||
onPress?: () => void
|
||||
}) {
|
||||
const scale = useSharedValue(1)
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: scale.value }],
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
scale.value = withSequence(
|
||||
withTiming(0.9, { duration: 120 }),
|
||||
withTiming(1, { duration: 160 }),
|
||||
)
|
||||
}
|
||||
}, [isFocused])
|
||||
|
||||
const IconComponent = icons[isFocused && activeIcon ? activeIcon : icon]
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
style={{ flex: 1, alignItems: 'center', justifyContent: 'center', height: '100%' }}
|
||||
>
|
||||
<Animated.View style={animatedStyle}>
|
||||
<IconComponent width={iconSize} height={iconSize} stroke={isFocused ? '#333' : '#aaa'} />
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
export default function CustomTabbar({ data }: AppTabbarProps) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const [activeTab, setActiveTab] = useState<string>(() => {
|
||||
const match = data.find(item => item.href === pathname)
|
||||
return match?.name ?? data[0]?.name
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`pathname:${pathname}`)
|
||||
}, [pathname])
|
||||
|
||||
// 反转方向
|
||||
const [swipeDirection, setSwipeDirection] = useState<'left' | 'right'>('left')
|
||||
function updateSwipeDirection(tabName: string) {
|
||||
const currentIndex = data.findIndex(d => d.name === activeTab)
|
||||
const targetIndex = data.findIndex(d => d.name === tabName)
|
||||
|
||||
if (currentIndex === -1 || targetIndex === -1 || currentIndex === targetIndex)
|
||||
return
|
||||
|
||||
setSwipeDirection(targetIndex > currentIndex ? 'left' : 'right')
|
||||
setActiveTab(tabName)
|
||||
}
|
||||
|
||||
// 点击调用
|
||||
const handlPressTab = (item: TabbarItem) => {
|
||||
updateSwipeDirection(item.name)
|
||||
|
||||
setActiveTab(item.name)
|
||||
if (typeof item.onPress === 'function')
|
||||
item.onPress()
|
||||
}
|
||||
|
||||
// 定义 JS 线程安全函数
|
||||
const handlePanTab = (tabName: string) => {
|
||||
const tab = data.find(item => item.name === tabName)
|
||||
if (!tab)
|
||||
return
|
||||
|
||||
updateSwipeDirection(tabName)
|
||||
|
||||
setActiveTab(tab.name)
|
||||
|
||||
if (typeof tab.onPress === 'function') {
|
||||
tab.onPress()
|
||||
}
|
||||
|
||||
if (tab.href && tab.href !== pathname) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
}
|
||||
|
||||
// 手势滑动
|
||||
const panGesture = Gesture.Pan()
|
||||
.activeOffsetX([-20, 20])
|
||||
.failOffsetY([-10, 10])
|
||||
.onEnd((event) => {
|
||||
const threshold = 60
|
||||
const currentIndex = data.findIndex(item => item.name === activeTab)
|
||||
let targetIndex: number | undefined
|
||||
|
||||
if (event.translationX < -threshold && currentIndex < data.length - 1) {
|
||||
targetIndex = currentIndex + 1
|
||||
}
|
||||
else if (event.translationX > threshold && currentIndex > 0) {
|
||||
targetIndex = currentIndex - 1
|
||||
}
|
||||
|
||||
if (targetIndex !== undefined) {
|
||||
const tabName = data[targetIndex].name
|
||||
scheduleOnRN(handlePanTab, tabName)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<Tabs>
|
||||
<GestureDetector gesture={panGesture}>
|
||||
<View style={{ flex: 1 }}>
|
||||
{(() => {
|
||||
const currentTab = data.find(d => d.name === activeTab)
|
||||
if (!currentTab)
|
||||
return null
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={activeTab}
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
>
|
||||
<TabSlot />
|
||||
</Animated.View>
|
||||
)
|
||||
})()}
|
||||
</View>
|
||||
</GestureDetector>
|
||||
|
||||
<TabList asChild>
|
||||
<View style={{ flexDirection: 'row', backgroundColor: 'white', height: 48, alignItems: 'center' }}>
|
||||
{data.map((item) => {
|
||||
if (item.href) {
|
||||
return (
|
||||
<TabTrigger key={item.name} name={item.name} href={item.href} asChild>
|
||||
<TabButton
|
||||
icon={item.icon}
|
||||
activeIcon={item.activeIcon}
|
||||
iconSize={item.iconSize}
|
||||
isFocused={activeTab === item.name}
|
||||
onPress={() => handlPressTab(item)}
|
||||
/>
|
||||
</TabTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TabButton
|
||||
key={item.name}
|
||||
icon={item.icon}
|
||||
activeIcon={item.activeIcon}
|
||||
iconSize={item.iconSize}
|
||||
isFocused={activeTab === item.name}
|
||||
onPress={() => handlPressTab(item)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</GestureHandlerRootView>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Redirect, usePathname, useRouter, useSegments } from 'expo-router'
|
||||
import type { Href } from 'expo-router'
|
||||
import { usePathname, useRouter } from 'expo-router'
|
||||
import { TabList, Tabs, TabSlot, TabTrigger } from 'expo-router/ui'
|
||||
import { icons } from 'lucide-react-native'
|
||||
import { useEffect, useState } from 'react'
|
||||
@@ -16,7 +17,7 @@ export interface TabbarItem {
|
||||
iconSize?: number
|
||||
activeIcon?: keyof typeof icons
|
||||
name: string
|
||||
href?: string
|
||||
href?: Href
|
||||
onPress?: () => void
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user