-
Notifications
You must be signed in to change notification settings - Fork 167
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #353 from gkartalis/gkartalis/masonry-flashlist-su…
…pport feat: masonry flashlist support
- Loading branch information
Showing
13 changed files
with
477 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import React from 'react' | ||
import { StyleSheet, View } from 'react-native' | ||
import { useHeaderMeasurements } from 'react-native-collapsible-tab-view' | ||
import Animated, { | ||
interpolate, | ||
useAnimatedStyle, | ||
useDerivedValue, | ||
} from 'react-native-reanimated' | ||
|
||
import { useCurrentTabScrollY } from '../../src/hooks' | ||
import ExampleComponent from './Shared/ExampleComponentMasonryFlashList' | ||
import ReText from './Shared/ReText' | ||
import { ExampleComponentType } from './types' | ||
|
||
const title = 'MasonryFlashList' | ||
|
||
const MIN_HEADER_HEIGHT = 48 | ||
|
||
export const Header = () => { | ||
const { top, height } = useHeaderMeasurements() | ||
const scrollY = useCurrentTabScrollY() | ||
|
||
const scrollYText = useDerivedValue( | ||
() => `Scroll Y is: ${scrollY.value.toFixed(2)}` | ||
) | ||
|
||
const stylez = useAnimatedStyle(() => { | ||
return { | ||
transform: [ | ||
{ | ||
translateY: interpolate( | ||
top.value, | ||
[0, -(height.value || 0 - MIN_HEADER_HEIGHT)], | ||
[0, (height.value || 0 - MIN_HEADER_HEIGHT) / 2] | ||
), | ||
}, | ||
], | ||
} | ||
}) | ||
|
||
return ( | ||
<View style={[styles.root]}> | ||
<Animated.View style={[styles.container, stylez]}> | ||
<ReText style={styles.text} text={scrollYText} /> | ||
</Animated.View> | ||
</View> | ||
) | ||
} | ||
|
||
const Example: ExampleComponentType = () => { | ||
return ( | ||
<ExampleComponent | ||
allowHeaderOverscroll | ||
renderHeader={() => <Header />} | ||
minHeaderHeight={MIN_HEADER_HEIGHT} | ||
/> | ||
) | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
root: { | ||
backgroundColor: '#2196f3', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
padding: 16, | ||
height: 250, | ||
}, | ||
container: { | ||
height: MIN_HEADER_HEIGHT, | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
width: '100%', | ||
}, | ||
text: { | ||
position: 'absolute', | ||
color: 'white', | ||
fontSize: 24, | ||
textAlign: 'center', | ||
}, | ||
}) | ||
|
||
Example.title = title | ||
|
||
export default Example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React from 'react' | ||
import { | ||
Tabs, | ||
CollapsibleRef, | ||
CollapsibleProps, | ||
} from 'react-native-collapsible-tab-view' | ||
|
||
import Albums from './Albums' | ||
import Article from './Article' | ||
import ContactsFlashList from './ContactsFlashList' | ||
import ExampleMasonry from './ExampleMasonry' | ||
import { HEADER_HEIGHT } from './Header' | ||
|
||
type Props = { | ||
emptyContacts?: boolean | ||
hideArticleTab?: boolean | ||
} & Partial<CollapsibleProps> | ||
|
||
const Example = React.forwardRef<CollapsibleRef, Props>( | ||
({ emptyContacts, ...props }, ref) => { | ||
return ( | ||
<Tabs.Container ref={ref} headerHeight={HEADER_HEIGHT} {...props}> | ||
{props.hideArticleTab ? ( | ||
<Tabs.Tab name="article" label="Article"> | ||
<Article /> | ||
</Tabs.Tab> | ||
) : null} | ||
<Tabs.Tab name="albums" label="Albums"> | ||
<Albums /> | ||
</Tabs.Tab> | ||
<Tabs.Tab name="contacts" label="MasonryFlashList"> | ||
{/* | ||
TODO: masonry has the same issue unfortunatelly when not having a lot of elements | ||
// see: https://github.com/PedroBern/react-native-collapsible-tab-view/issues/335 | ||
<ExampleMasonry emptyList={emptyContacts} limit={5} /> | ||
*/} | ||
<ExampleMasonry emptyList={emptyContacts} /> | ||
</Tabs.Tab> | ||
<Tabs.Tab name="ordered" label="FlashList"> | ||
<ContactsFlashList emptyContacts={emptyContacts} /> | ||
</Tabs.Tab> | ||
</Tabs.Container> | ||
) | ||
} | ||
) | ||
|
||
export default Example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import * as React from 'react' | ||
import { | ||
Alert, | ||
Platform, | ||
StyleSheet, | ||
Text, | ||
TouchableOpacity, | ||
View, | ||
} from 'react-native' | ||
import * as Tabs from 'react-native-collapsible-tab-view' | ||
import Animated, { | ||
interpolate, | ||
useAnimatedStyle, | ||
useDerivedValue, | ||
} from 'react-native-reanimated' | ||
|
||
import { useRefresh } from './useRefresh' | ||
|
||
type Item = { id: string; aspectRatio: number; color: string } | ||
|
||
export function getItems(count = 20) { | ||
return Array.from({ length: count }, (v, k) => { | ||
const r = Math.random() + 0.5 | ||
const t = Date.now() | ||
return { | ||
id: `${t}-${k}-${r}`, | ||
aspectRatio: r, | ||
color: `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor( | ||
Math.random() * 256 | ||
)}, ${Math.floor(Math.random() * 256)})`, | ||
} | ||
}) | ||
} | ||
|
||
export async function asyncGetItems(count = 20, delay = 1000) { | ||
await new Promise<void>((resolve) => { | ||
setTimeout(() => { | ||
resolve() | ||
}, delay) | ||
}) | ||
|
||
return getItems(count) | ||
} | ||
|
||
interface MasonryItemProps { | ||
item: Item | ||
index: number | ||
} | ||
|
||
const MasonryItem: React.FC<MasonryItemProps> = ({ item, index }) => { | ||
return ( | ||
<TouchableOpacity | ||
style={{ | ||
backgroundColor: item.color, | ||
aspectRatio: item.aspectRatio, | ||
...styles.item, | ||
}} | ||
onPress={() => Alert.alert(item.id)} | ||
> | ||
<Text> | ||
{index} - {item.aspectRatio} | ||
</Text> | ||
</TouchableOpacity> | ||
) | ||
} | ||
|
||
const ItemSeparator = () => <View style={styles.separator} /> | ||
|
||
const ListEmptyComponent = () => { | ||
const { top, height } = Tabs.useHeaderMeasurements() | ||
const translateY = useDerivedValue(() => { | ||
return interpolate( | ||
-top.value, | ||
[0, height.value || 0], | ||
[-(height.value || 0) / 2, 0] | ||
) | ||
}, [height]) | ||
|
||
const stylez = useAnimatedStyle(() => { | ||
return { | ||
transform: [ | ||
{ | ||
translateY: translateY.value, | ||
}, | ||
], | ||
} | ||
}) | ||
|
||
return ( | ||
<Animated.View style={[styles.listEmpty, stylez]}> | ||
<Text>Centered Empty List!</Text> | ||
</Animated.View> | ||
) | ||
} | ||
|
||
const ExampleMasonry: React.FC<{ | ||
emptyList?: boolean | ||
nestedScrollEnabled?: boolean | ||
limit?: number | ||
}> = ({ emptyList, nestedScrollEnabled, limit }) => { | ||
const [isRefreshing, startRefreshing] = useRefresh() | ||
const [refreshing, setRefreshing] = React.useState(false) | ||
const [data, setData] = React.useState<Item[]>([]) | ||
|
||
const refresh = React.useCallback(async () => { | ||
if (refreshing) { | ||
return | ||
} | ||
setRefreshing(true) | ||
const res = await asyncGetItems() | ||
setData(res) | ||
setRefreshing(false) | ||
}, [refreshing]) | ||
|
||
React.useEffect(() => { | ||
refresh() | ||
}, []) | ||
|
||
return ( | ||
<Tabs.MasonryFlashList | ||
data={emptyList ? [] : limit ? data.slice(0, limit) : data} | ||
numColumns={2} | ||
estimatedItemSize={60} | ||
keyExtractor={(_, i) => String(i)} | ||
renderItem={MasonryItem} | ||
ItemSeparatorComponent={ItemSeparator} | ||
ListEmptyComponent={ListEmptyComponent} | ||
// see https://github.com/software-mansion/react-native-reanimated/issues/1703 | ||
onRefresh={Platform.OS === 'ios' ? startRefreshing : undefined} | ||
refreshing={Platform.OS === 'ios' ? isRefreshing : undefined} | ||
nestedScrollEnabled={nestedScrollEnabled} | ||
/> | ||
) | ||
} | ||
|
||
export default ExampleMasonry | ||
|
||
const styles = StyleSheet.create({ | ||
item: { | ||
borderRadius: 10, | ||
}, | ||
listEmpty: { | ||
alignItems: 'center', | ||
flex: 1, | ||
justifyContent: 'center', | ||
borderColor: 'black', | ||
borderWidth: 10, | ||
}, | ||
separator: { | ||
height: StyleSheet.hairlineWidth, | ||
backgroundColor: 'rgba(0, 0, 0, .08)', | ||
}, | ||
}) |
Oops, something went wrong.