From cc35270ecd121827dd9a756a3001e6a3fabd2a03 Mon Sep 17 00:00:00 2001 From: Mickael Lecoq Date: Wed, 28 Sep 2022 16:54:24 +0200 Subject: [PATCH] feat: add animateOnIndexChange to enable/disable animation --- README.md | 4 ++ example/src/App.tsx | 2 + example/src/CustomAnimationExample.tsx | 87 ++++++++++++++++++++++++++ src/PagerViewAdapter.tsx | 36 ++++++++--- src/PanResponderAdapter.tsx | 36 ++++++----- src/TabView.tsx | 2 + src/types.tsx | 1 + 7 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 example/src/CustomAnimationExample.tsx diff --git a/README.md b/README.md index d194a0e0..035ce5ea 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,10 @@ Callback which is called when the swipe gesture starts, i.e. the user touches th Callback which is called when the swipe gesture ends, i.e. the user lifts their finger from the screen after the swipe gesture. +##### `animateOnIndexChange` + +Function which determines whether the animation is executed on index change + ##### `initialLayout` Object containing the initial height and width of the screens. Passing this will improve the initial rendering performance. For most apps, this is a good default: diff --git a/example/src/App.tsx b/example/src/App.tsx index 8ce5347d..7eb707a4 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -22,6 +22,7 @@ import CustomIndicatorExample from './CustomIndicatorExample'; import CustomTabBarExample from './CustomTabBarExample'; import CoverflowExample from './CoverflowExample'; import TabBarGapExample from './TabBarGapExample'; +import CustomAnimationExample from './CustomAnimationExample'; type ExampleComponentType = React.ComponentType<{}> & { title: string; @@ -43,6 +44,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [ CustomTabBarExample, CoverflowExample, TabBarGapExample, + CustomAnimationExample, ]; const ExampleList = () => { diff --git a/example/src/CustomAnimationExample.tsx b/example/src/CustomAnimationExample.tsx new file mode 100644 index 00000000..5124cfb8 --- /dev/null +++ b/example/src/CustomAnimationExample.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { StyleSheet } from 'react-native'; +import { + TabView, + TabBar, + SceneMap, + NavigationState, + SceneRendererProps, +} from 'react-native-tab-view'; +import Article from './Shared/Article'; +import Albums from './Shared/Albums'; +import Chat from './Shared/Chat'; +import Contacts from './Shared/Contacts'; + +type State = NavigationState<{ + key: string; + title: string; +}>; + +const animateOnIndexChange = (currentIndex: number, nextIndex: number) => { + return Math.abs(currentIndex - nextIndex) === 1; +}; + +const CustomAnimationExample = () => { + const [index, onIndexChange] = React.useState(1); + const [routes] = React.useState([ + { key: 'article', title: 'Article' }, + { key: 'contacts', title: 'Contacts' }, + { key: 'albums', title: 'Albums' }, + { key: 'chat', title: 'Chat' }, + ]); + + const renderTabBar = ( + props: SceneRendererProps & { navigationState: State } + ) => ( + + ); + + const renderScene = SceneMap({ + albums: Albums, + contacts: Contacts, + article: Article, + chat: Chat, + }); + + return ( + + ); +}; + +CustomAnimationExample.title = 'Custom Animation'; +CustomAnimationExample.backgroundColor = '#3f51b5'; +CustomAnimationExample.appbarElevation = 0; + +export default CustomAnimationExample; + +const styles = StyleSheet.create({ + tabbar: { + backgroundColor: '#3f51b5', + }, + tab: { + width: 'auto', + }, + indicator: { + backgroundColor: '#ffeb3b', + }, + label: { + fontWeight: '400', + }, +}); diff --git a/src/PagerViewAdapter.tsx b/src/PagerViewAdapter.tsx index b858cb48..4a135188 100644 --- a/src/PagerViewAdapter.tsx +++ b/src/PagerViewAdapter.tsx @@ -40,6 +40,7 @@ export default function PagerViewAdapter({ onSwipeStart, onSwipeEnd, children, + animateOnIndexChange, style, ...rest }: Props) { @@ -58,13 +59,22 @@ export default function PagerViewAdapter({ navigationStateRef.current = navigationState; }); - const jumpTo = React.useCallback((key: string) => { - const index = navigationStateRef.current.routes.findIndex( - (route: { key: string }) => route.key === key - ); - - pagerRef.current?.setPage(index); - }, []); + const jumpTo = React.useCallback( + (key: string) => { + const index = navigationStateRef.current.routes.findIndex( + (route: { key: string }) => route.key === key + ); + if ( + animateOnIndexChange && + !animateOnIndexChange(indexRef.current, index) + ) { + pagerRef.current?.setPageWithoutAnimation(index); + } else { + pagerRef.current?.setPage(index); + } + }, + [animateOnIndexChange] + ); React.useEffect(() => { if (keyboardDismissMode === 'auto') { @@ -72,9 +82,17 @@ export default function PagerViewAdapter({ } if (indexRef.current !== index) { - pagerRef.current?.setPage(index); + if ( + animateOnIndexChange && + !animateOnIndexChange(indexRef.current, index) + ) { + pagerRef.current?.setPageWithoutAnimation(index); + position.setValue(index); + } else { + pagerRef.current?.setPage(index); + } } - }, [keyboardDismissMode, index]); + }, [keyboardDismissMode, index, animateOnIndexChange, position]); const onPageScrollStateChanged = ( state: PageScrollStateChangedNativeEvent diff --git a/src/PanResponderAdapter.tsx b/src/PanResponderAdapter.tsx index 3d1964a9..135ef95e 100644 --- a/src/PanResponderAdapter.tsx +++ b/src/PanResponderAdapter.tsx @@ -57,6 +57,7 @@ export default function PanResponderAdapter({ onSwipeStart, onSwipeEnd, children, + animateOnIndexChange, style, }: Props) { const { routes, index } = navigationState; @@ -80,23 +81,30 @@ export default function PanResponderAdapter({ const offset = -index * layoutRef.current.width; const { timing, ...transitionConfig } = DefaultTransitionSpec; - - Animated.parallel([ - timing(panX, { - ...transitionConfig, - toValue: offset, - useNativeDriver: false, - }), - ]).start(({ finished }) => { - if (finished) { - onIndexChangeRef.current(index); - pendingIndexRef.current = undefined; - } - }); + if ( + animateOnIndexChange && + !animateOnIndexChange(currentIndexRef.current, index) + ) { + panX.setValue(offset); + onIndexChangeRef.current(index); + } else { + Animated.parallel([ + timing(panX, { + ...transitionConfig, + toValue: offset, + useNativeDriver: false, + }), + ]).start(({ finished }) => { + if (finished) { + onIndexChangeRef.current(index); + pendingIndexRef.current = undefined; + } + }); + } pendingIndexRef.current = index; }, - [panX] + [animateOnIndexChange, panX] ); React.useEffect(() => { diff --git a/src/TabView.tsx b/src/TabView.tsx index 9734440f..63d45240 100644 --- a/src/TabView.tsx +++ b/src/TabView.tsx @@ -51,6 +51,7 @@ export default function TabView({ style, swipeEnabled = true, tabBarPosition = 'top', + animateOnIndexChange, }: Props) { const [layout, setLayout] = React.useState({ width: 0, @@ -87,6 +88,7 @@ export default function TabView({ onSwipeEnd={onSwipeEnd} onIndexChange={jumpToIndex} style={pagerStyle} + animateOnIndexChange={animateOnIndexChange} > {({ position, render, addEnterListener, jumpTo }) => { // All of the props here must not change between re-renders diff --git a/src/types.tsx b/src/types.tsx index 164ef37b..401d51dc 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -55,4 +55,5 @@ export type PagerProps = Omit< swipeEnabled?: boolean; onSwipeStart?: () => void; onSwipeEnd?: () => void; + animateOnIndexChange?: (currentIndex: number, nextIndex: number) => boolean; };