Comments (30)
@blohamen you can use FlatList
from import { FlatList } from 'react-native-gesture-handler'
, it works for me π
from react-native-reanimated-bottom-sheet.
I have to switch to another component because of this.
from react-native-reanimated-bottom-sheet.
I was able to achieve this functionality with an adaptation of an older example in the react-native-gesture-handler
repo. It works, but it lacks the elegance/sophistication of react-native-reanimated-bottomsheet
. I will clean up and generalize my implementation and post back here with a gist.
from react-native-reanimated-bottom-sheet.
Thanks, I would like to have a list of images from camera roll that the user can scroll, then I need a FlatList to be performant, The list should expand like this bottom sheet when the user starts to scroll up, but when reaching the top of the Flatlist again it should go down, I guess its not possible whit this lib because I need to handle FlatList scroll position?
from react-native-reanimated-bottom-sheet.
@gorhom thank you very much for your input. I appreciate it.
I did the same but now we have a problem where if the bottom sheet is open and you reached the beginning of the list (after scrolling from the bottom) will not pull down to close.
from react-native-reanimated-bottom-sheet.
This is not perfect, but as good as it gets with simple implementation. First see the video:
http://wayshorter.com/down/bottomsheet2.mp4
Basic idea is that when the FlatList's offset's y value is less than like -30 (-40 works better for me), then I snap the bottom sheet to a different index and also reset the offset of the FlatList.
Here I post code that are relevant:
const NavigateScreen = ({ navigation }) => {
let snapPoints = [
screenHeight - topPositionFromTop,
(screenHeight - topPositionFromTop - bottomPosition) / 2 + bottomPosition,
bottomPosition
];
// bottom sheet index
const initialSnapIndex = 1;
const [ bottomSheetIndex, setBottomSheetIndex ] = useState(initialSnapIndex);
const drawerCallbackNode = useRef(new Animated.Value(0)).current;
const [ over30Disabled, setOver30Disabled ] = useState(false);
useCode(
onChange(
drawerCallbackNode,
block([
cond(
lessOrEq(drawerCallbackNode, 0.1),
call([], () => {
bottomSheetIndex === 0 ? null : setBottomSheetIndex(0);
})
),
cond(
and(greaterThan(drawerCallbackNode, 0.1), lessOrEq(drawerCallbackNode, 0.6)),
call([], () => {
bottomSheetIndex === 1 ? null : setBottomSheetIndex(1);
over30Disabled == true ? setOver30Disabled(false) : null;
})
),
cond(
greaterThan(drawerCallbackNode, 0.6),
call([], () => {
bottomSheetIndex === 2 ? null : setBottomSheetIndex(2);
})
)
])
),
null
);
const bottomSheetRef = useRef();
const flatListRef = useRef();
const renderContent = () => (
// bottom sheet inner area
<View
style={{
backgroundColor: 'white'
}}
>
{/* list in categories */}
<View style={{ height: flatListHeightTo }}>
<FlatList
ref={flatListRef}
onScroll={(event) => {
if (event.nativeEvent.contentOffset.y < -30) {
setOver30Disabled(true);
bottomSheetRef.current.snapTo(1);
flatListRef.current.scrollToOffset({ offset: 0, animated: true });
}
}}
scrollEnabled={bottomSheetIndex == 0 && over30Disabled == false ? true : false}
/>
</View>
</View>
);
return (
<React.Fragment style={{ flex: 1 }}>
<View
style={{
backgroundColor: 'rgb(15, 104, 186)',
flex: 1
}}
>
<SafeAreaView style={{ flex: 1 }}>...</SafeAreaView>
</View>
<BottomSheet
ref={bottomSheetRef}
snapPoints={snapPoints}
renderHeader={renderHeader}
renderContent={renderContent}
initialSnap={initialSnapIndex}
callbackNode={drawerCallbackNode}
/>
</React.Fragment>
);
};
export default NavigateScreen;
from react-native-reanimated-bottom-sheet.
@osdnk I was able to get the flat list working very close to apple maps. But the biggest issue I'm facing is the innerContent height when my flat list has no data or few rows.
My use case is my inner content is determined basing on a service call. There might no rows vs 20 rows.
With the manual calculations I'm not getting the smooth animations that you were able to achieve with your examples. :(
I can share the example I have if you think it will be useful to see the issue
from react-native-reanimated-bottom-sheet.
@gorhom Please, send an example, very interesting. I tried to do something similar, but I didnβt succeed. Your example will be very helpfull.
from react-native-reanimated-bottom-sheet.
@jeffreybello , yeah that part is tricky π , you would need to simulate the flatlist scrolling with bottomsheet pan gesture, i will give it a try this weekend
from react-native-reanimated-bottom-sheet.
hey all,
I'm trying to understand how to put a FlatList and make it fill up the screen. Do i need to explicitly set its height
?
I've made a (buggy) example with a FlatList that is visible as a PR here or snacked here. Basically the height of the FlatList
seems to always be the max height of the BottomSheet
Maybe this requires an issue of its own(or some documentation, if i'm missing smth).
@pontusab could you perhaps rename this issue to smth like Close sheet when top of FlatList is reached?
from react-native-reanimated-bottom-sheet.
Unfortunately, we were not able to find a viable solution for flatlist and have disabled scroll to dismiss for the flatlist area as of now.
Would be huge for us if we can solve it.
from react-native-reanimated-bottom-sheet.
@pontusab I think it possible to add to the container and it does behave like Apple maps. Here is my implementation
renderInner = () => (
<View style={[styles.panel, {height:Math.max(this.props.data.length*75,300)}]}>
<FlatList
showsVerticalScrollIndicator={false}
scrollEnabled={false}
data={this.props.data}
keyExtractor = {(item) => item.BusID}
renderItem={({item}) =>
<PlainBusCard data={item}></PlainBusCard>
}
contentContainerStyle={{ flexGrow: 1 }}
ItemSeparatorComponent={this.renderSeparator}
ListEmptyComponent={this.props.emptyView}
/>
</View>
)
renderHeader = () => (
<View style={styles.header}>
<View style={styles.panelHeader}>
<Text>Search Busstop</Text>
</View>
</View>
)
render() {
return (
<View style={styles.container}>
<BottomSheet
snapPoints = {[600, 300, 100]}
renderContent = {this.renderInner}
renderHeader = {this.renderHeader}
/>
<MapView
onRegionChangeComplete={() => this.props.mapMoved()}
initialRegion={{latitude: this.appDefaultLocation.latitude, longitude:this.appDefaultLocation.longitude, latitudeDelta:0.003, longitudeDelta:0.003}}
style={{ flex: 1 }}
/>
</View>
)
}
@osdnk Please let me know if I'm using this lib in a wrong way.
from react-native-reanimated-bottom-sheet.
Thanks! of course, it's possible to add it as a container, but I want the sheet to expand if the scroll position within Flatlist is 0 and when the user scrolls down it should stay. but when the user scrolls to top again the sheet should go down.
from react-native-reanimated-bottom-sheet.
@osdnk any comments or suggestion for us to add a FlatList in the content?
from react-native-reanimated-bottom-sheet.
Considering two examples I have added a few days ago I think I cover most cases of FlatList usage.
The only point is virtualization, but you see it's necessary for bottom-sheet?
from react-native-reanimated-bottom-sheet.
+1 for virtualization support for the the bottom sheet (maybe based on a generic react-native-gesture-handler based scrollview with virtualization support).
from react-native-reanimated-bottom-sheet.
I don't have a clear idea of how to do it neither enough time, but I'll happily see some solution.
Maybe you could build some own idea of virtualization basing on the position of content?
from react-native-reanimated-bottom-sheet.
Thanks, I would like to have a list of images from camera roll that the user can scroll, then I need a FlatList to be performant, The list should expand like this bottom sheet when the user starts to scroll up, but when reaching the top of the Flatlist again it should go down, I guess its not possible whit this lib because I need to handle FlatList scroll position?
@osdnk it is possible to handle this? Really needs this feature for my app
from react-native-reanimated-bottom-sheet.
My example with bottom sheet
import BottomSheet from 'reanimated-bottom-sheet'
import React from 'react';
import {Dimensions, Modal, Text, View} from 'react-native';
import StyleSheet from "react-native-extended-stylesheet";
import {colors} from "components/colors";
import Animated from "react-native-reanimated";
import {Header} from 'react-navigation';
import {getStatusBarHeight, isIphoneX} from "react-native-iphone-x-helper";
const {Value, onChange, call, cond, eq, abs, sub, min} = Animated;
const handleContainerHeight = 38;
const headerBottomPosition = Dimensions.get('window').height - Header.HEIGHT - (isIphoneX() ? getStatusBarHeight() : 0);
export class BottomSheetExample extends React.Component {
state = {
visible: false,
bottomSheetOpened: false,
contentHeight: null,
};
constructor(props) {
super(props);
this.position = new Value(1);
this.opacity = min(abs(sub(this.position, 1)), 0.8);
}
openModal () {
this.setState({visible: true});
}
handleInitBottomSheet = (bottomSheet) => {
if (!this.state.bottomSheetOpened && !!bottomSheet) {
this.setState({bottomSheetOpened: true}, () => {bottomSheet.snapTo(1)});
}
};
handleCloseModal = () => {
this.setState({visible: false, bottomSheetOpened: false});
};
renderHeader = () => {
return (
<View style={styles.handleContainer}>
<View style={styles.handle} />
<View style={styles.headerBorder} />
</View>
);
};
onLayoutContent = event => {
if (this.state.dimensions) return; // layout was already called
let {height} = event.nativeEvent.layout;
this.setState({contentHeight: height})
};
renderInner = () => {
const contentHeight = this.state.contentHeight;
let height = '100%';
if (!!contentHeight) {
height = headerBottomPosition - contentHeight - handleContainerHeight;
}
return (
<View>
{this.renderContent()}
<View style={{height, width: '100%', backgroundColor: colors.WHITE}} />
</View>
)
};
renderContent = () => {
return (
<View onLayout={this.onLayoutContent}>
{[...Array(3)].map((e, i) => (
<View
key={i}
style={{ height: 40, backgroundColor: `#${i % 10}88424` }}
>
<Text>computed</Text>
</View>
))}
</View>
)
};
render() {
return (
<Modal
animationType='fade'
transparent
visible={this.state.visible}
onRequestClose={this.state.onPressClose}
>
<View style={styles.container}>
<Animated.View
style={[styles.shadow, {opacity: this.opacity}]}
/>
<BottomSheet
callbackNode={this.position}
snapPoints={[0, headerBottomPosition]}
renderContent={this.renderInner}
renderHeader={this.renderHeader}
ref={ref => this.handleInitBottomSheet(ref)}
/>
<Animated.Code exec={onChange(this.position, [cond(eq(this.position, 1), call([], this.handleCloseModal))])}/>
</View>
</Modal>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
wrapper: {
height: headerBottomPosition
},
handleContainer: {
height: handleContainerHeight,
alignItems: 'center',
justifyContent: 'flex-end',
},
handle: {
height: 4,
width: 48,
borderRadius: 2,
marginBottom: 12,
backgroundColor: colors.WHITE,
},
headerBorder: {
height: 10,
width: '100%',
backgroundColor: colors.WHITE,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
},
shadow: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
flex: 1,
backgroundColor: colors.BLACK
},
});
from react-native-reanimated-bottom-sheet.
@osdnk I was able to get the flat list working very close to apple maps. But the biggest issue I'm facing is the innerContent height when my flat list has no data or few rows.
My use case is my inner content is determined basing on a service call. There might no rows vs 20 rows.
With the manual calculations I'm not getting the smooth animations that you were able to achieve with your examples. :(I can share the example I have if you think it will be useful to see the issue
Hello there! I'm close to reach a proper implementation of this animation. But I'm stuck on last part I think. Can you share your example with "apple maps alike" bottom sheet please? Thanks man!
from react-native-reanimated-bottom-sheet.
I really needed this feature as well.
I am basically recreating flatlist while retaining the bottomsheet behavior.
from react-native-reanimated-bottom-sheet.
Maybe @gorhom or @Priyatham51 can shed a light on how to use the Flatlist?
from react-native-reanimated-bottom-sheet.
@jeffreybello , @avegrv sorry i have been busy lately , however i setup this example to how to handle flatlist scrolling, hope it helps
https://gist.github.com/Gorhom/32db5ee4d0423c8f227fd3c48e3dd991
from react-native-reanimated-bottom-sheet.
Yes, ill try my best too but I just got started in RN. But please let us know if you have made progress.
from react-native-reanimated-bottom-sheet.
Great approaches and workarounds here, highly appreciated! π
We are also integrating RN Reanimated-Bottomsheet with a Flatlist into our app. Am I correct to assume that we have not found a solution to virtualization yet?
Thanks! ππΌ
from react-native-reanimated-bottom-sheet.
Very interested in the feature too, on big phones itβs not easy to close the bottom sheet by reaching the header, when itβs fully opened. I think this is also what user intuitively expects
from react-native-reanimated-bottom-sheet.
hey, did any one found a workaround for this?
from react-native-reanimated-bottom-sheet.
@southerneer It'd be super useful!
from react-native-reanimated-bottom-sheet.
@southerneer any update on your implementation? I'm running into the same issue where the sheet doesn't close when I reach the top of my flat list (works fine when I have I use a View instead of a FlatList). Here's my code:
/* BottomSheet */
import React, { ReactNode, useRef, useEffect, useState } from "react";
import { Dimensions, View, StyleSheet } from "react-native";
import { useDimensions } from "react-native-hooks";
import BottomSheetRN from "reanimated-bottom-sheet";
import size, { unit } from "../themes/size";
import Elevation from "../themes/elevation";
interface BottomSheetProps {
children?: ReactNode;
onClose?: () => void;
onOpen?: () => void;
renderHeader?: () => ReactNode;
isVisible?: boolean;
}
const BottomSheet = (props: BottomSheetProps) => {
const { children, renderHeader, onClose, onOpen } = props;
const [isVisible, setIsVisible] = useState(props.isVisible);
const ref = useRef<BottomSheetRN>();
const {
screen: { height },
} = useDimensions();
const [headerHeight, setHeaderHeight] = useState(0);
useEffect(() => setIsVisible(props.isVisible), [props.isVisible]);
useEffect(() => {
if (ref.current) {
if (isVisible) {
ref.current.snapTo(1);
} else {
ref.current.snapTo(0);
}
}
}, [ref.current, isVisible]);
return (
<View style={[styles.container, isVisible && styles.containerVisible]}>
<BottomSheetRN
ref={ref}
initialSnap={0}
snapPoints={[0, "80%"]}
onCloseEnd={() => {
setIsVisible(false);
onClose && onClose();
}}
onOpenEnd={() => {
setIsVisible(true);
onOpen && onOpen();
}}
enabledContentTapInteraction
enabledGestureInteraction
enabledContentGestureInteraction
enabledInnerScrolling
renderHeader={() => (
<View
style={styles.header}
onLayout={event => setHeaderHeight(event.nativeEvent.layout.height)}
>
<View style={styles.panelHeader}>
<View style={styles.panelHandle} />
</View>
{renderHeader()}
</View>
)}
renderContent={() => (
<View style={[styles.panel, { height: ((height - headerHeight) * 80) / 100 }]}>
{children}
</View>
)}
/>
</View>
);
};
export default BottomSheet;
const styles = StyleSheet.create({
container: {
flex: 1,
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
containerVisible: {
zIndex: 100,
...Elevation.tertiary,
},
panel: {
backgroundColor: "white",
},
header: {
backgroundColor: "white",
paddingTop: size.vertical.small,
borderTopLeftRadius: unit * 2,
borderTopRightRadius: unit * 2,
},
panelHeader: {
alignItems: "center",
},
panelHandle: {
width: 40,
height: unit / 2,
borderRadius: 4,
backgroundColor: "#00000040",
marginBottom: 20,
},
panelTitle: {
fontSize: 27,
height: 35,
},
panelSubtitle: {
fontSize: 14,
color: "gray",
height: 30,
marginBottom: 10,
},
panelButton: {
padding: 20,
borderRadius: 10,
backgroundColor: "#318bfb",
alignItems: "center",
marginVertical: 10,
},
panelButtonTitle: {
fontSize: 17,
fontWeight: "bold",
color: "white",
},
photo: {
width: "100%",
height: 225,
marginTop: 30,
},
map: {
height: "100%",
width: "100%",
},
});
/* AutocompleteSheet */
import React, { useState, useEffect } from "react";
import { View, StyleSheet, TextInput, ActivityIndicator } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import useDebounce from "../hooks/useDebounce";
import { ListItem } from "../types";
import AutocompleteRow from "./AutocompleteRow";
import BottomSheet from "./BottomSheet";
import size from "../themes/size";
import Elevation from "../themes/elevation";
import color from "../themes/color";
interface AutocompletePanelProps {
isVisible?: boolean;
onChange: (term: string) => Promise<void>;
onVisibilityChange: (isVisible: boolean) => void;
onSelect?: (item: any, index?: number) => void;
keyExtractor?: (item: any, index?: number) => string;
results: Array<ListItem>;
}
const AutocompletePanel = ({
isVisible,
onChange,
onSelect,
onVisibilityChange,
keyExtractor,
results,
}: AutocompletePanelProps) => {
const [searchTerm, onChangeSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 400);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
(async () => {
setIsLoading(true);
await onChange(debouncedSearchTerm);
setIsLoading(false);
})();
}, [debouncedSearchTerm]);
return (
<BottomSheet
isVisible={isVisible}
onClose={() => onVisibilityChange(false)}
onOpen={() => onVisibilityChange(true)}
renderHeader={() => (
<View style={[styles.container, styles.row]}>
<TextInput
style={styles.input}
placeholder={"Type text here"}
onChangeText={text => onChangeSearchTerm(text)}
value={searchTerm}
/>
{isLoading && <ActivityIndicator size='small' color='#4631EB' style={styles.loading} />}
</View>
)}
>
<FlatList
data={results}
scrollEnabled
keyExtractor={keyExtractor}
keyboardShouldPersistTaps='always'
style={styles.listView}
renderItem={({ item, index }) => (
<AutocompleteRow
id={item.id}
index={index}
onSelect={onSelect}
properties={item.properties}
/>
)}
/>
</BottomSheet>
);
};
export default AutocompletePanel;
const styles = StyleSheet.create({
container: {
paddingHorizontal: size.horizontal.large,
},
listView: {
backgroundColor: "red",
paddingHorizontal: 24,
},
loading: {
position: "absolute",
top: 8,
right: 32,
},
row: {
flexDirection: "row",
},
input: {
width: "100%",
paddingHorizontal: size.horizontal.regular,
paddingVertical: size.vertical.medium,
fontSize: 18,
borderRadius: 8,
borderWidth: 0.5,
borderColor: "#ddd",
...Elevation.tertiary,
backgroundColor: color.white,
},
});
from react-native-reanimated-bottom-sheet.
Hi guys, @gorhom solution is still way buggy and doesn't work properly , where first and last item gets cut off. Any solution regarding this is very very much appreciated.
Though, not using FlatList
and just mapping items inside a view supposedly should work fine, unfortunately likewise, a problem persists where I can't scroll to the end of the list
from react-native-reanimated-bottom-sheet.
Related Issues (20)
- Bottomsheet is under other components
- use in react js app HOT 1
- Bottomsheet automatically opening and closing on rotation HOT 1
- how to scroll list to the bottom? HOT 1
- Overlay HOT 2
- Property 'snapToIndex' does not exist on type 'never'.ts(2339) HOT 2
- Is the package still maintained? HOT 1
- BottomSheet is overlapping the Content
- can't run in RN-cli HOT 1
- enabledGestureInteraction Prop
- not working in RN 0.69 HOT 4
- FlatList wont Scroll Till end of screen HOT 4
- Is this community still active? HOT 1
- BottomSheet import from react-native-reanimated-bottom-sheet is not available in RN 0.69.1 HOT 1
- Drag not working at all
- Preventing keyboard from dismissing with FlatList in BottomSheet
- How to set content height dynamically based on content?
- export 'default'.'Code' (imported as 'Animated') was not found in 'react-native-reanimated' HOT 2
- reanimated-bottom-sheet & react-native-reanimated HOT 4
- Cannot read property 'prototype' of undefined HOT 18
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-native-reanimated-bottom-sheet.