Skip to content

Commit

Permalink
feat: add lazy support
Browse files Browse the repository at this point in the history
fix #19
  • Loading branch information
PedroBern committed Jan 25, 2021
1 parent db3ee96 commit 4bfa80b
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 7 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -257,6 +258,8 @@ const Example: React.FC<Props> = () => {
| `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`

Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -26,6 +27,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [
Snap,
DiffClamp,
DiffClampSnap,
Lazy,
// CenteredEmptyList,
ScrollOnHeader,
QuickStartDemo,
Expand Down
17 changes: 17 additions & 0 deletions example/src/Lazy.tsx
Original file line number Diff line number Diff line change
@@ -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 <ExampleComponent HeaderComponent={Header} lazy />
}

DefaultExample.title = title

export default DefaultExample
96 changes: 91 additions & 5 deletions src/createCollapsibleTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Animated, {
useAnimatedReaction,
scrollTo,
withTiming,
runOnJS,
} from 'react-native-reanimated'

import { Props, ContextType, ScrollViewProps, FlatListProps } from './types'
Expand All @@ -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 = <T extends string>() => {
const Context = React.createContext<ContextType<T> | undefined>(undefined)

Expand All @@ -45,6 +52,8 @@ const createCollapsibleTabs = <T extends string>() => {
headerContainerStyle,
cancelTranslation,
containerStyle,
lazy,
cancelLazyFazeIn,
}) => {
const [containerHeight, setContainerHeight] = React.useState<
number | undefined
Expand All @@ -64,7 +73,8 @@ const createCollapsibleTabs = <T extends string>() => {
const oldAccScrollY = useSharedValue(0)
const accDiffClamp = useSharedValue(0)
const index = useSharedValue(0)
const tabNames = useSharedValue(Object.keys(refMap))
// @ts-ignore
const tabNames = useSharedValue<T[]>(Object.keys(refMap))
// @ts-ignore
const focusedTab = useSharedValue<T>(tabNames.value[index.value])

Expand Down Expand Up @@ -115,10 +125,23 @@ const createCollapsibleTabs = <T extends string>() => {
)

const renderItem = React.useCallback(
({ index }) => {
return children[index]
({ index: i }) => {
return lazy ? (
<Lazy
name={tabNames.value[i]}
// todo:
// set startMounted to initial scene instead of 0,
// to support starting on specific tab
startMounted={i === 0}
cancelLazyFazeIn={cancelLazyFazeIn}
>
{children[i]}
</Lazy>
) : (
children[i]
)
},
[children]
[children, lazy, tabNames.value, cancelLazyFazeIn]
)

const stylez = useAnimatedStyle(() => {
Expand Down Expand Up @@ -235,12 +258,75 @@ const createCollapsibleTabs = <T extends string>() => {
pagingEnabled
onScroll={scrollHandlerX}
showsHorizontalScrollIndicator={false}
getItemLayout={getItemLayout}
/>
</View>
</Context.Provider>
)
}

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
) : (
<Animated.View
pointerEvents="box-none"
style={[styles.container, !cancelLazyFazeIn && stylez]}
>
{children}
</Animated.View>
)
) : (
<ScrollView name={name} />
)
}

const useScrollHandlerY = (name: T) => {
const {
accDiffClamp,
Expand Down Expand Up @@ -425,7 +511,7 @@ const createCollapsibleTabs = <T extends string>() => {
)
}

return { FlatList, ScrollView, Container, useTabsContext }
return { FlatList, ScrollView, Container, useTabsContext, Lazy }
}

const styles = StyleSheet.create({
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type Props<T extends string> = {
headerContainerStyle?: StyleProp<Animated.AnimateStyle<ViewStyle>>
containerStyle?: ViewStyle
cancelTranslation?: boolean
lazy?: boolean
cancelLazyFazeIn?: boolean
}

export type ContextType<T extends string> = {
Expand Down

0 comments on commit 4bfa80b

Please sign in to comment.