diff --git a/src/components/PageView/PageView.tsx b/src/components/PageView/PageView.tsx index 85de894..bcb1867 100644 --- a/src/components/PageView/PageView.tsx +++ b/src/components/PageView/PageView.tsx @@ -43,7 +43,7 @@ const PageView = forwardRef((props, ref) => { }; const pageScroll = (translate: number) => { - onPageScroll && onPageScroll(Math.abs(translate)); + onPageScroll && onPageScroll(-translate); }; const setPage = (index: number) => { diff --git a/src/components/TabBar/TabBar.tsx b/src/components/TabBar/TabBar.tsx index c094879..687e539 100644 --- a/src/components/TabBar/TabBar.tsx +++ b/src/components/TabBar/TabBar.tsx @@ -1,13 +1,16 @@ import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import { View, ScrollView, LayoutChangeEvent, StyleSheet } from 'react-native'; -import TabBarItem from './TabBarItem'; import Animated, { + interpolate, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; + +import TabBarItem from './TabBarItem'; import TabBarSlider from './TabBarSlider'; import Separator from './Separator'; + import { TabBarProps, TabBarItemLayout, TabBarRef } from './type'; import { useVerifyProps } from './hook'; @@ -38,18 +41,27 @@ const TabBar = forwardRef((props, ref) => { const scrollRef = useRef(null); const layouts = useRef([]).current; const sliderWidth = useRef(defalutSliderWidth); + const sliderOutput = useRef([]); + const intput = tabs.map((_, index) => index); useImperativeHandle( ref, () => ({ setTab: handleOnPress, getCurrent: () => currentIndex.value, + syncCurrentIndex, + keepScrollViewMiddle, }), [] ); + const syncCurrentIndex = (offset: number) => { + currentIndex.value = offset; + sliderOffset.value = interpolate(offset, intput, sliderOutput.current); + }; + const handleOnPress = (index: number) => { - currentIndex.value = index; + currentIndex.value = withTiming(index); calculateSliderOffset(index); onPress && onPress(index); }; @@ -59,6 +71,11 @@ const TabBar = forwardRef((props, ref) => { const length = layouts.filter((layout) => layout.width > 0).length; if (length === tabs.length) { + sliderOutput.current = layouts.map((layout: TabBarItemLayout) => { + const { x, y, width, height } = layout; + const toValue = x + (width - sliderWidth.current) / 2; + return toValue; + }); calculateSliderOffset(initialTab); } }; @@ -72,17 +89,8 @@ const TabBar = forwardRef((props, ref) => { sliderWidth.current = width; }; - const calculateSliderOffset = (index: number) => { - 'worklet'; - if (index < 0 || index >= tabs.length) { - throw new Error( - 'calculateSliderOffset can only handle index [0, tabs.length - 1]' - ); - } - - const { x, y, width, height } = layouts[index]; - - const toValue = x + (width - sliderWidth.current) / 2; + const keepScrollViewMiddle = (index: number) => { + const toValue = sliderOutput.current[index]; if (toValue > contentSize / 2) { scrollRef.current && scrollRef.current?.scrollTo({ @@ -94,6 +102,18 @@ const TabBar = forwardRef((props, ref) => { scrollRef.current && scrollRef.current?.scrollTo({ x: 0, y: 0, animated: true }); } + }; + + const calculateSliderOffset = (index: number) => { + 'worklet'; + if (index < 0 || index >= tabs.length) { + throw new Error( + 'calculateSliderOffset can only handle index [0, tabs.length - 1]' + ); + } + + keepScrollViewMiddle(index); + const toValue = sliderOutput.current[index]; sliderOffset.value = withTiming(toValue); }; @@ -134,6 +154,7 @@ const TabBar = forwardRef((props, ref) => { style={tabBarItemStyle} titleStyle={tabBarItemTitleStyle} index={index} + currentIndex={currentIndex} title={tab} onLayout={onTabBarItemLayout} onPress={handleOnPress} diff --git a/src/components/TabBar/TabBarItem.tsx b/src/components/TabBar/TabBarItem.tsx index 37eb8b2..d9cd14b 100644 --- a/src/components/TabBar/TabBarItem.tsx +++ b/src/components/TabBar/TabBarItem.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Text, TouchableOpacity, StyleSheet } from 'react-native'; import { TabBarItemProps } from './type'; +import Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated'; const TabBarItem: React.FC = (props) => { const { @@ -9,17 +10,39 @@ const TabBarItem: React.FC = (props) => { width = 'auto', style, titleStyle, + currentIndex, onLayout, onPress, } = props; + const animatedText = useAnimatedStyle(() => { + return { + opacity: interpolate( + currentIndex.value, + [index - 1, index, index + 1], + [0.8, 1, 0.8], + Extrapolation.CLAMP + ), + transform: [ + { + scale: interpolate( + currentIndex.value, + [index - 1, index, index + 1], + [0.9, 1.1, 0.9], + Extrapolation.CLAMP + ), + }, + ], + }; + }); + return ( onPress(index)} onLayout={(event) => onLayout(index, event.nativeEvent.layout)} style={[styles.container, { width: width }, style]} > - {title} + {title} ); }; diff --git a/src/components/TabBar/index.tsx b/src/components/TabBar/index.tsx index bf70ee0..d7c4377 100644 --- a/src/components/TabBar/index.tsx +++ b/src/components/TabBar/index.tsx @@ -1,3 +1,3 @@ import TabBar from './TabBar'; - +export { TabBarRef } from './type'; export { TabBar }; diff --git a/src/components/TabBar/type.ts b/src/components/TabBar/type.ts index e34dfa7..da64b7d 100644 --- a/src/components/TabBar/type.ts +++ b/src/components/TabBar/type.ts @@ -23,10 +23,13 @@ export interface TabBarProps { export interface TabBarRef { setTab: (index: number) => void; getCurrent: () => number; + syncCurrentIndex: (offset: number) => void; + keepScrollViewMiddle: (index: number) => void; } export interface TabBarItemProps { index: number; + currentIndex: SharedValue; title: string; style?: ViewStyle; titleStyle?: TextStyle; diff --git a/src/components/TabView/TabView.tsx b/src/components/TabView/TabView.tsx index a3c89cd..9a88758 100644 --- a/src/components/TabView/TabView.tsx +++ b/src/components/TabView/TabView.tsx @@ -1,6 +1,6 @@ import React, { useRef } from 'react'; import { View, Text, Dimensions } from 'react-native'; -import { TabBar } from '../TabBar'; +import { TabBar, TabBarRef } from '../TabBar'; import { PageView, PageViewRef } from '../PageView'; import Animated, { useSharedValue } from 'react-native-reanimated'; @@ -15,7 +15,7 @@ const TabView: React.FC = (props) => { // const contentSize = width; const currentIndex = useSharedValue(2); const pageRef = useRef(null); - const tabRef = useRef(null); + const tabRef = useRef(null); return ( @@ -35,9 +35,7 @@ const TabView: React.FC = (props) => { backgroundColor: 'orange', }} onPress={(index) => { - // pageRef.current && pageRef.current?.setPage(index); - console.log('onPress index', index); - // currentIndex.value = index; + pageRef.current && pageRef.current?.setPage(index); }} initialTab={currentIndex.value} /> @@ -46,17 +44,13 @@ const TabView: React.FC = (props) => { ref={pageRef} initialPage={currentIndex.value} onPageSelected={(currentPage) => { - // console.log('currentPage:', currentPage); - // tabRef.current && tabRef.current?.setTab(currentPage); + tabRef.current && tabRef.current?.keepScrollViewMiddle(currentPage); }} onPageScrollStateChanged={(state) => { // console.log('state:', state); }} onPageScroll={(translate) => { - // console.log('translate', translate); - // currentIndex.value = translate; - // const page = pageRef.current && pageRef.current?.getCurrentPage(); - // console.log('translate', page); + tabRef.current && tabRef.current?.syncCurrentIndex(translate / width); }} scrollEnabled={true} bounces={true} diff --git a/src/index.ts b/src/index.ts index 725b94c..33b9cc9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -88,4 +88,4 @@ export { Freeze } from './utils/Freeze'; export { TabView } from './components/TabView'; export { HeadTabView } from './components/HeadTabView'; export { PageView, PageViewRef } from './components/PageView'; -export { TabBar } from './components/TabBar'; +export { TabBar, TabBarRef } from './components/TabBar';