Search code examples
react-nativereact-native-listviewreact-native-flatlist

How to render absoulute element inside ListView or FlatList?


Here's a fun one i've been poking at for while:

I have a FlatList (same issue with ListView) and I want to render an element INSIDE the internal scrolling container with the following characteristics:

  • Absolutely Positioned (thus having no effect on position of list elements)
  • Position XX distance from top (translateY or top)
  • zIndex (above list elements)

The use case is i'm rendering a day view calendar grid with a horizontal bar at the current time position fixed at X distance from the beginning of the internal scrollview so it appears as the user scrolls pass that position.

So far i've tried wrapping wrapping FlatList/ListView with another ScrollView... also tried rendering this element as the header element which only works while the header/footer are visible (trashed when out of view).

Any and all ideas welcomed. :)

Thanks

Screenshot Below (red bar is what i'm trying to render):

Day View Calendar Grid


Solution

  • Here's a working demo of what it sounds like you're trying to achieve: https://sketch.expo.io/BkreW1che. You can click "preview" to see it in your browser.

    And here's the main code you need to measure the height of the ListView and place the indicator on top of it (visit the link above to see the full source):

      handleLayout(event) {
        const { y, height } = event.nativeEvent.layout;
    
        // Now we know how tall the ListView is; let's put the indicator in the middle.
        this.setState({ indicatorOffset: y + (height / 2) });
      }
    
      renderIndicator() {
        const { indicatorOffset } = this.state;
    
        // Once we know how tall the ListView is, put the indicator on top.
        return indicatorOffset ? (
          <View style={[{ position: 'absolute', left: 0, right: 0, top: indicatorOffset }]} />
        ) : null;
      }
    
      render() {
        return (
          <View style={styles.container}>
            <ListView
              onLayout={(event) => this.handleLayout(event)}
              dataSource={this.state.dataSource}
              renderRow={this.renderRow}
            />
            {this.renderIndicator()}
          </View>
        );
      }
    

    Edit: I now understand that you want the indicator to scroll along with the list. That's a simple change from above, just add an onScroll listener to the ListView: https://sketch.expo.io/HkEjDy92e

      handleScroll(event) {
        const { y } = event.nativeEvent.contentOffset;
    
        // Keep the indicator at the same position in the list using this offset.
        this.setState({ scrollOffset: y });
      },
    

    With this change, the indicator actually seems to lag behind a bit because of the delay in the onScroll callback.

    If you want better performance, you might consider rendering the indicator as part of your renderRow method instead. For example, if you know the indicator should appear at 10:30 am, then you would render it right in the middle of your 10am row.