Skip to content
This repository has been archived by the owner on Nov 27, 2022. It is now read-only.

feat: add animateOnIndexChange to enable/disable animation #1391

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,6 +44,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [
CustomTabBarExample,
CoverflowExample,
TabBarGapExample,
CustomAnimationExample,
];

const ExampleList = () => {
Expand Down
87 changes: 87 additions & 0 deletions example/src/CustomAnimationExample.tsx
Original file line number Diff line number Diff line change
@@ -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 }
) => (
<TabBar
{...props}
scrollEnabled
indicatorStyle={styles.indicator}
style={styles.tabbar}
tabStyle={styles.tab}
labelStyle={styles.label}
/>
);

const renderScene = SceneMap({
albums: Albums,
contacts: Contacts,
article: Article,
chat: Chat,
});

return (
<TabView
lazy
navigationState={{
index,
routes,
}}
renderScene={renderScene}
renderTabBar={renderTabBar}
onIndexChange={onIndexChange}
animateOnIndexChange={animateOnIndexChange}
/>
);
};

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',
},
});
36 changes: 27 additions & 9 deletions src/PagerViewAdapter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default function PagerViewAdapter<T extends Route>({
onSwipeStart,
onSwipeEnd,
children,
animateOnIndexChange,
style,
...rest
}: Props<T>) {
Expand All @@ -58,23 +59,40 @@ export default function PagerViewAdapter<T extends Route>({
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') {
Keyboard.dismiss();
}

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
Expand Down
36 changes: 22 additions & 14 deletions src/PanResponderAdapter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default function PanResponderAdapter<T extends Route>({
onSwipeStart,
onSwipeEnd,
children,
animateOnIndexChange,
style,
}: Props<T>) {
const { routes, index } = navigationState;
Expand All @@ -80,23 +81,30 @@ export default function PanResponderAdapter<T extends Route>({
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(() => {
Expand Down
2 changes: 2 additions & 0 deletions src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default function TabView<T extends Route>({
style,
swipeEnabled = true,
tabBarPosition = 'top',
animateOnIndexChange,
}: Props<T>) {
const [layout, setLayout] = React.useState({
width: 0,
Expand Down Expand Up @@ -87,6 +88,7 @@ export default function TabView<T extends Route>({
onSwipeEnd={onSwipeEnd}
onIndexChange={jumpToIndex}
style={pagerStyle}
animateOnIndexChange={animateOnIndexChange}
>
{({ position, render, addEnterListener, jumpTo }) => {
// All of the props here must not change between re-renders
Expand Down
1 change: 1 addition & 0 deletions src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ export type PagerProps = Omit<
swipeEnabled?: boolean;
onSwipeStart?: () => void;
onSwipeEnd?: () => void;
animateOnIndexChange?: (currentIndex: number, nextIndex: number) => boolean;
};