Drag up a ScrollView then continue scroll in React Native
Asked Answered
Z

2

29

What I want (GIF)

Here is a (quality-reduced) GIF about what I'm want to achieve.

enter image description here

What I want (Text)

I have a scrollview, that is positioned at the half of my screen.

What I want is to drag that scrollview up, and then, when it reaches a certain position, send the drag touch event to scrollview itself, so it can continue scrolling.

My tries

  • Put the scrollview in fullscreen in the foreground, and add a half-screen padding-top to its contentContainerStyle. It worked well, but I couldn't click the view behind it. Is there a way to click through the empty area of a scrollview?
  • I also tried to detect the scroll position, in order to move the view up accordingly

    <ScrollView onScroll={event => moveScrollViewUpIfNeeded(event)}>
        ...
    </ScrollView>
    

    but didn't work when I tested it out on iOS simulator. Event is not even fired. React Native Docs states that onScroll needs scrollEventThrottle to work. However, scrollEventThrottle is only available on iOS. And in my case, I want it on Android too.
    And If I successfully achieve this, I should face another UI problem: When dragging the ScrollView up, how can I prevent it to scroll when the view is not yet at the wanted position?

So, can you give me some tips to achieve this please:)?

Thank you,

Zoara answered 28/11, 2018 at 16:5 Comment(11)
How about conditional rendering? you wrap your content in a <View /> first and you make it's position absolute. When dragging, you detect if it has gotten the full screen space. Then, you render your content but inside a <ScrollView /> instead, to have scrolling capability. Since padding is not involved in this solution, you can click the content behind as long as the view you're dragging hasn't taken up the whole screen height.Jacktar
Hey David. Did you end up resolving your issue? I am facing the same challenge right now. Link to my question: #55082789Leoleod
I have started a bounty on this question as I can't find anything on the web to achieve this. Can someone please provide a solution that recreates the interface shown in the Google Maps app, and functions properly with scroll? The gif included is still the current version so it might be worth downloading to have a closer look. Please refer to @GilbertNwaiwu's question for additional detail on the complexity/intricacies of trying to create this UI.Anthem
The new iOS 13 'Find My' app also features a scrollable overlay which is dragged down when you reach the top of the scoll view: imgur.com/a/gg84IPoAnthem
I found this library that looks like doing what you need octopitus.github.io/rn-sliding-up-panel. I'll build a project and do a demo of itArmpit
@Armpit That looks like it could be ideal. I'll wait for your answer and take a look myself!Anthem
@Armpit I had a look - the scroll example includes a physical handle to pull the view up/down but unfortunately that's not quite what we're after. The key with this question is how the view seamlessly scrolls the parent view when you reach the top of the child scroll view, in the same way it does in Google Maps.Anthem
Yeah I've tried it out for a bit but i'm not getting that behaviour,there is probably some hacky way to do it triggering scroll events on the scrollView with this library but it's a lot of workArmpit
did you check this? spectrum.chat/react-native/general/…Threnode
check this one as well hackernoon.com/…Threnode
@Anthem I've provided a solution for you. See my answer below.Galengalena
G
8

I've tried several options. This is my solution which works best performance-wise. Of course this is just a working example, you might need to optimize it a little bit further to perfectly match your needs.

Demo:

Demo Gif

Explanation:

The basic idea is to keep the map all the time in the background of the overlay. The overlay is absolutely positioned and contains two Views. One View is transparent, where we can still see and control the map, the other View contains the actual ScrollView. The trick is to set pointerEvents="box-none" of the parent overlay and disable pointerEvents with pointerEvents="none" of View where we want to interact with the map.

Basic render method:

  <SafeAreaView style={{flex: 1}}>
     <MapView
      initialRegion={region}
      style={{height: HEIGHT, width: WIDTH}}
      /> 
    <View  pointerEvents="box-none"style={{height: HEIGHT, width: WIDTH, position: 'absolute'}}>
      <View pointerEvents="none" style={{height: this.state.height, backgroundColor: 'transparent'}} />
      <View style={{ height: HEIGHT-this.state.height, backgroundColor: 'white'}}>
        <ScrollView onScroll={(e) => this._onScroll(e)} scrollEventThrottle={10} >
        -- content of scrollview goes here --- 
        </ScrollView>
      </View>
    </View>

  </SafeAreaView>

Scrolling functionality:

If we scroll down the ScrollView we want to shrink the empty View, so that the ScrollView becomes fullscreen. Therefore we listen to the onScroll method of the ScrollView. See code and comments below:

  _onScroll(e){
    // from the nativeEvent we can get the contentOffsett
    var offset_y = e.nativeEvent.contentOffset.y;
    if (offset_y > 0 ) {
     if (this.state.height>=0){
      // we are scrolling down the list, decrease height of the empty view
      this.setState({height: this.state.height-offset_y});
     }
    }
    if (offset_y <0){
      if (this.state.height <= this.state.mapHeight){
        // we are scrolling up the list, increase size of empty view/map view 
        this.setState({height: this.state.height-offset_y});
      }
    }
  }

With ScrollView's scrollEventThrottle prop we can control how often the _onScroll method should be called. (Here you probably have to fine tune)

Complete Code & Working Snack

https://snack.expo.io/@tim1717/rnmapwithoverlay

Galengalena answered 24/9, 2019 at 9:3 Comment(5)
The issue I have with this solution is that both scroll views receive touch events at the same time which can create unusually fast scrolling. This also means that the subview moves whilst you're pulling it up. For a scroll view solution this is a great effort and I'm thankful for the time you put into your answer, but unless there's a way around this that doesn't restrict the effect I think it's not quite there yet.Anthem
@Anthem there's only one ScrollView. The fast scrolling is due to how the _onScroll function is implemented. As I said, this is just a working example, not a perfect solution. If I have time, i will further optimize it.Galengalena
Thanks Tim, it's certainly a great starting point. I'll have a play later on and see if I can adapt the answer furtherAnthem
I understand, but I'm afraid I've spent a lot of time trying to adapt the answer to avoid scrolling the subview but it doesn't seem possible. On that basis the answer in its current form doesn't provide a good enough solution to the problem. I'm convinced the Google Maps example was completed using custom PanResponders.Anthem
The solution doesn't work. I know how the bounty system works, and I won't reward the full bounty until a solution that replicates the example on both platforms is provided. It's a disservice to the OP, and to others who find this answer in future. Edit: poster deleted comments - I wasn't replying to myselfAnthem
A
2

I think it is better to place both MapView and BottomView into one ScrollView.

It will save natural scrolling behaviour and prevent side effects like white stripes on scrolling down or top view disappearing on scrolling up. The main problem here is how to make "freeze" effect for the MapView.

It is nice to use parallax effect by transforming MapView inside Animated container relatively to a scroll event. I think Animated library with Animatable components will be good solution for problems like that:

   scroll = new Animated.Value(0)
   render() {
      return <Animated.ScrollView ...
          onScroll={Animated.event([{nativeEvent: {contentOffset: {y: this.scroll}}}], {useNativeDriver: true})}>

        <Animated.View 
              style={
                 { ...transform: 
                       [{translateY: Animated.multiply(this.scroll, 0.8)}]}}>
          <MapView />
        </Animated.View>
        <BottomView />
      </Animated.ScrollView>
    }

Actually if you don't want parallax effect you can replace Animated.multiply(this.scroll, 0.8) with just this.scroll

You can see full example here: Code example

Screen capture

Amalee answered 28/9, 2019 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.