diff --git a/README.md b/README.md index 1888d608..1b6c554f 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,10 @@ The [react-native-tab-view](https://github.com/satya164/react-native-tab-view) e - Animations and interactions on the UI thread - Highly customizable - Fully typed with [TypeScript](https://typescriptlang.org) +- Lazy support with fade-in animation - DiffClamp tabs -- Interpotated tabs -- Scroll snap (with interpotated tabs) +- Interpolated tabs +- Scroll snap (with interpolated tabs) - Animated snap (with diffClamp tabs) ## Installation @@ -257,6 +258,8 @@ const Example: React.FC = () => { | `headerContainerStyle?` | Styles applied to the header and tabbar container | | | `containerStyle?` | Styles applied to the view container. | | | `cancelTranslation?` | This will cancel the collapsible effect, and render a static tabbar / header. | `false` | +| `lazy?` | Mount the screen only when it's focused. It has a default fade in animation. | `false` | +| `cancelLazyFadeIn?` | Cancel the fade in animation if `lazy={true}` | `false` | ### `Tabs.ScrollView` and `Tabs.FlatList` diff --git a/example/src/App.tsx b/example/src/App.tsx index 391a4427..3bda6824 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -16,6 +16,7 @@ import { import Default from './Default' import DiffClamp from './DiffClamp' import DiffClampSnap from './DiffClampSnap' +import Lazy from './Lazy' import QuickStartDemo from './QuickStartDemo' import ScrollOnHeader from './ScrollOnHeader' import Snap from './Snap' @@ -26,6 +27,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [ Snap, DiffClamp, DiffClampSnap, + Lazy, // CenteredEmptyList, ScrollOnHeader, QuickStartDemo, diff --git a/example/src/Lazy.tsx b/example/src/Lazy.tsx new file mode 100644 index 00000000..3738d7d2 --- /dev/null +++ b/example/src/Lazy.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +import ExampleComponent from './Shared/ExampleComponent' +import { buildHeader } from './Shared/Header' +import { ExampleComponentType } from './types' + +const title = 'Lazy Example' + +const Header = buildHeader(title) + +const DefaultExample: ExampleComponentType = () => { + return +} + +DefaultExample.title = title + +export default DefaultExample diff --git a/src/createCollapsibleTabs.tsx b/src/createCollapsibleTabs.tsx index ac1a9343..27e45589 100644 --- a/src/createCollapsibleTabs.tsx +++ b/src/createCollapsibleTabs.tsx @@ -14,6 +14,7 @@ import Animated, { useAnimatedReaction, scrollTo, withTiming, + runOnJS, } from 'react-native-reanimated' import { Props, ContextType, ScrollViewProps, FlatListProps } from './types' @@ -22,6 +23,12 @@ const windowWidth = Dimensions.get('window').width const AnimatedFlatList = Animated.createAnimatedComponent(RNFlatList) +export const getItemLayout = (_: unknown, index: number) => ({ + length: windowWidth, + offset: windowWidth * index, + index, +}) + const createCollapsibleTabs = () => { const Context = React.createContext | undefined>(undefined) @@ -45,6 +52,8 @@ const createCollapsibleTabs = () => { headerContainerStyle, cancelTranslation, containerStyle, + lazy, + cancelLazyFazeIn, }) => { const [containerHeight, setContainerHeight] = React.useState< number | undefined @@ -64,7 +73,8 @@ const createCollapsibleTabs = () => { const oldAccScrollY = useSharedValue(0) const accDiffClamp = useSharedValue(0) const index = useSharedValue(0) - const tabNames = useSharedValue(Object.keys(refMap)) + // @ts-ignore + const tabNames = useSharedValue(Object.keys(refMap)) // @ts-ignore const focusedTab = useSharedValue(tabNames.value[index.value]) @@ -115,10 +125,23 @@ const createCollapsibleTabs = () => { ) const renderItem = React.useCallback( - ({ index }) => { - return children[index] + ({ index: i }) => { + return lazy ? ( + + {children[i]} + + ) : ( + children[i] + ) }, - [children] + [children, lazy, tabNames.value, cancelLazyFazeIn] ) const stylez = useAnimatedStyle(() => { @@ -235,12 +258,75 @@ const createCollapsibleTabs = () => { pagingEnabled onScroll={scrollHandlerX} showsHorizontalScrollIndicator={false} + getItemLayout={getItemLayout} /> ) } + const Lazy: React.FC<{ + name: T + startMounted?: boolean + cancelLazyFazeIn?: boolean + children: React.ReactElement + }> = ({ children, name, startMounted, cancelLazyFazeIn }) => { + const { focusedTab, refMap, scrollY, tabNames } = useTabsContext() + const [canMount, setCanMount] = React.useState(!!startMounted) + const opacity = useSharedValue(cancelLazyFazeIn ? 1 : 0) + + const allowToMount = React.useCallback(() => { + // wait the scene to be at least 50 ms focused, before mouting + setTimeout(() => { + if (focusedTab.value === name) { + setCanMount(true) + } + }, 50) + }, [focusedTab.value, name]) + + useAnimatedReaction( + () => { + return focusedTab.value === name + }, + (focused) => { + if (focused && !canMount) { + runOnJS(allowToMount)() + } + }, + [canMount, focusedTab] + ) + + useDerivedValue(() => { + if (canMount) { + const tabIndex = tabNames.value.findIndex((n) => n === name) + // @ts-ignore + scrollTo(refMap[name], 0, scrollY.value[tabIndex], false) + if (!cancelLazyFazeIn) opacity.value = withTiming(1) + } + }, [canMount, cancelLazyFazeIn]) + + const stylez = useAnimatedStyle(() => { + return { + opacity: opacity.value, + } + }, []) + + return canMount ? ( + cancelLazyFazeIn ? ( + children + ) : ( + + {children} + + ) + ) : ( + + ) + } + const useScrollHandlerY = (name: T) => { const { accDiffClamp, @@ -425,7 +511,7 @@ const createCollapsibleTabs = () => { ) } - return { FlatList, ScrollView, Container, useTabsContext } + return { FlatList, ScrollView, Container, useTabsContext, Lazy } } const styles = StyleSheet.create({ diff --git a/src/types.ts b/src/types.ts index 0d011a48..010e6cd7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,8 @@ export type Props = { headerContainerStyle?: StyleProp> containerStyle?: ViewStyle cancelTranslation?: boolean + lazy?: boolean + cancelLazyFazeIn?: boolean } export type ContextType = {