Coder Social home page Coder Social logo

Comments (30)

gorhom avatar gorhom commented on September 2, 2024 18

@blohamen you can use FlatList from import { FlatList } from 'react-native-gesture-handler', it works for me πŸ‘

from react-native-reanimated-bottom-sheet.

wobsoriano avatar wobsoriano commented on September 2, 2024 9

I have to switch to another component because of this.

from react-native-reanimated-bottom-sheet.

southerneer avatar southerneer commented on September 2, 2024 7

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.

pontusab avatar pontusab commented on September 2, 2024 2

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.

jeffreybello avatar jeffreybello commented on September 2, 2024 2

@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.

brian-ws avatar brian-ws commented on September 2, 2024 2

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.

Priyatham51 avatar Priyatham51 commented on September 2, 2024 1

@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.

avegrv avatar avegrv commented on September 2, 2024 1

@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.

gorhom avatar gorhom commented on September 2, 2024 1

@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.

walidvb avatar walidvb commented on September 2, 2024 1

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.

L-U-C-K-Y avatar L-U-C-K-Y commented on September 2, 2024 1

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.

Priyatham51 avatar Priyatham51 commented on September 2, 2024

@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.

pontusab avatar pontusab commented on September 2, 2024

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.

Priyatham51 avatar Priyatham51 commented on September 2, 2024

@osdnk any comments or suggestion for us to add a FlatList in the content?

from react-native-reanimated-bottom-sheet.

osdnk avatar osdnk commented on September 2, 2024

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.

siderakis avatar siderakis commented on September 2, 2024

+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.

osdnk avatar osdnk commented on September 2, 2024

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.

blohamen avatar blohamen commented on September 2, 2024

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.

avegrv avatar avegrv commented on September 2, 2024

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.

FRizzonelli avatar FRizzonelli commented on September 2, 2024

@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.

jeffreybello avatar jeffreybello commented on September 2, 2024

I really needed this feature as well.

I am basically recreating flatlist while retaining the bottomsheet behavior.

from react-native-reanimated-bottom-sheet.

jeffreybello avatar jeffreybello commented on September 2, 2024

Maybe @gorhom or @Priyatham51 can shed a light on how to use the Flatlist?

from react-native-reanimated-bottom-sheet.

gorhom avatar gorhom commented on September 2, 2024

@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.

jeffreybello avatar jeffreybello commented on September 2, 2024

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.

L-U-C-K-Y avatar L-U-C-K-Y commented on September 2, 2024

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.

oferRounds avatar oferRounds commented on September 2, 2024

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.

oferRounds avatar oferRounds commented on September 2, 2024

hey, did any one found a workaround for this?

from react-native-reanimated-bottom-sheet.

FRizzonelli avatar FRizzonelli commented on September 2, 2024

@southerneer It'd be super useful!

from react-native-reanimated-bottom-sheet.

emroot avatar emroot commented on September 2, 2024

@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.

yasir-netlinks avatar yasir-netlinks commented on September 2, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.