import React, { memo, useContext, useRef, useMemo, useEffect, useCallback } from 'react'; import { ScrollView, View, Text, Pressable, Animated } from 'react-native'; import type { ViewStyle } from 'react-native'; import isFunction from 'lodash-es/isFunction'; import isEmpty from 'lodash-es/isEmpty'; import { useUpdateEffect } from '../hooks'; import { TabsContext } from './TabsContext'; import { useThemeFactory } from '../Theme'; import Badge from '../Badge'; import createStyle from './style'; import type { TabPaneProps } from './type'; import useScrollItem, { ItemLayout } from './useScrollItem'; interface TabBarProps { navs: TabPaneProps[]; } const TabBar = ({ navs }: TabBarProps): JSX.Element => { const { selectedIndex, setCurrentIndex, props } = useContext(TabsContext); const { type = 'line', duration = 300, swipeThreshold = 5, ellipsis, shrink = false, lineWidth = 40, onClickTab, } = props; const targetPage = useRef(new Animated.Value(selectedIndex)).current; const { scrollViewRef, onItemContainerLayout, onItemLayout, itemsLayout, onContentSizeChange, onLayout, focusIndex, } = useScrollItem({ itemsCount: navs.length, }); useUpdateEffect(() => { Animated.timing(targetPage, { toValue: selectedIndex, duration, useNativeDriver: false, }).start(); }, [selectedIndex, duration]); useEffect(() => { focusIndex(selectedIndex, duration > 0); }, [focusIndex, selectedIndex, duration]); // 导航是否可以滚动 const scrollable = useMemo( () => navs.length > swipeThreshold || !ellipsis || shrink, [navs, swipeThreshold, ellipsis, shrink] ); const { styles } = useThemeFactory(createStyle, shrink, scrollable); const handleClickTab = useCallback( (item: TabPaneProps, idx: number) => { onClickTab?.(idx, !!item.disabled); !item.disabled && setCurrentIndex(idx); }, [setCurrentIndex] ); const renderText = (item: TabPaneProps, idx: number) => { const isActive = idx === selectedIndex; const text = ( {isFunction(item.title) ? item.title(isActive) : item.title} ); if (item.dot || !isEmpty(item.badge)) { return ( {text} ); } return text; }; const indicatorStyle = useMemo>(() => { const inputRange = itemsLayout.map((_v: ItemLayout, i: number) => i); let width: number | Animated.AnimatedInterpolation; let marginHorizontal: number | Animated.AnimatedInterpolation; const left = targetPage.interpolate({ inputRange, outputRange: itemsLayout.map((v: ItemLayout) => v.containerLeft), }); if (lineWidth === 'auto') { width = targetPage.interpolate({ inputRange, outputRange: itemsLayout.map((v: ItemLayout) => v.width), }); marginHorizontal = targetPage.interpolate({ inputRange, outputRange: itemsLayout.map((v: ItemLayout) => v.left), }); } else { width = lineWidth; marginHorizontal = targetPage.interpolate({ inputRange, outputRange: itemsLayout.map((v: ItemLayout) => (v.containerWidth - lineWidth) / 2), }); } return { width, left, marginHorizontal }; }, [itemsLayout, lineWidth]); return ( {navs.map((item, idx) => ( handleClickTab(item, idx)} onLayout={e => onItemContainerLayout(e, idx)} > onItemLayout(e, idx)}>{renderText(item, idx)} ))} {type === 'line' && ( )} ); }; export default memo(TabBar);