lelandrichardson / react-native-pan-controller

A react native component to help with common use cases for scrolling/panning/etc
MIT License
181 stars 20 forks source link

Scrollable within PanController #1

Open morgante opened 9 years ago

morgante commented 9 years ago

Thanks for making this little module!

I'm trying to implement some functionality but can't seem to get it to work correctly. What I essentially want is a scrollable pane which can be enlarged. When first opened, it would be 50% of the viewport height, but you can drag it upward and it will reach 100% of the viewport height. Once at 100% viewport height (ie. it is the entire viewport), you could scroll the contents. You could downsize it back to being 50% of the viewport by scrolling back to the top and dragging down.

Does that make sense? Is it possible to do this using PanController?

My trouble comes with the interaction between the ScrollView and the PanController—the ScrollView never takes over, so you can't scroll its contents.

lelandrichardson commented 9 years ago

Hi @morgante

Thanks for your question... I still haven't really "released" this component yet, so there are still some things that that I have left to implement and might not work correctly...

With that out of the way, let me see if I can understand what you are trying to do...

Using a ScrollView inside the PanController will likely never be possible. The PanController is meant to emulate things like the ScrollView (and additional behavior like you are wanting).

If I understand what you are trying to do, this should be pretty easy. Is there an app that already does this where you are trying to emulate the behavior? If you can post a gif of the UI, I can try and emulate it for you. If not, here is what I would recommend doing:

  1. Use the PanController to emulate the scrollview entirely. This can be done by simple enabling the vertical option and passing in an Animated.Value to the panY.
  2. Create a translateY interpolated value from your panY value like so:
var translateY = this.state.panY.interpolate({
  inputRange: [0, breakpoint, breakpoint+1],
  outputRange: [0,0,1]
})
  1. Apply this value to the translateY of the inner view
  2. Create a height interpolated value from your panY value like so:
var height = this.state.panY.interpolate({
  inputRange: [0, breakpoint, breakpoint+1],
  outputRange: [starting_height, screen_height, screen_height]
})
  1. Apply the height value to the height of the inner view.
  2. You'll probably also have to tweak some things like the height of the container using the measure API. This is one of the things I have left to abstract out into the PanController component, but for now you'll have to do this manually.

Note: my ScrollView implementation would be a good starting point: https://github.com/lelandrichardson/react-native-pan-controller/blob/master/lib/ScrollView.js

morgante commented 9 years ago

Thanks for the pointers. I think that makes sense, but how do you handle the transition from scrolling within the pane to scrolling the pane down (decreasing its size).

The effect I'm trying to achieve can be seen in the Facebook Paper app. The cards at the bottom of the screen can be dragged left and right, or up, which increases their size until they take over the screen and become scrollable.

lelandrichardson commented 9 years ago

@morgante can you record a gif? The transition should be able to be handled through proper use of interpolation

olivierlesnicki commented 9 years ago

@morgante is it something like this https://github.com/olivierlesnicki/react-native-switcher you're trying to achieve?

I've only just discovered react-native-pan-controller and I'm currently thinking about using it for the example above.

olivierlesnicki commented 9 years ago

The more I think of it, the less I think it's possible. You'd need a way to compute an Animated value based on the values of the panX and panY (looks like a good example for Animated.Formula)

I'm actually trying to achieve the same thing:

var React = require('react-native');
var PanController = require('react-native-pan-controller').PanController;

var Switcher = React.createClass({

  getDefaultProps() {

    var scroll = new React.Animated.Value(0);
    var zoom = new React.Animated.Value(0);

    return {
      scroll: scroll,
      zoom: zoom,
    };
  },

  getInitialState() {
    return {
      width: 0,
      height: 0,
    };
  },

  layout(e) {
    this.setState({
      width: e.nativeEvent.layout.width,
      height: e.nativeEvent.layout.height,
    });
  },

  render() {

    var children = this.props.children.map ? this.props.children : [this.props.children];

    return (
      <React.View
        onLayout={this.layout}
        style={{
          flex: 1,
        }}
      >
        <PanController
          momentumDecayConfig={{
            decceleration: 0.1,
          }}
          horizontal={true}
          vertical={true}
          overshootX='spring'
          overshootY='spring'
          lockDirection={true}
          snapSpacingX={this.state.width}
          snapSpacingY={this.state.height / 6}
          xBounds={[-this.state.width * (this.props.children.length - 1), 0]}
          yBounds={[0, this.state.height / 3]}
          panX={this.props.scroll}
          panY={this.props.zoom}
          style={{
            flex: 1,
          }}
        >
          {children.map((child, i) => {

            var delta = this.props.scroll.interpolate({
              inputRange: [-this.state.width * (i + 1), -this.state.width * i],
              outputRange: [-1, 0],
            });

            var translateX = delta.interpolate({
              inputRange: [-1, 0, 0.5, 1],
              outputRange: [-this.state.width / 8, 0, this.state.width / 4, 2 * this.state.width / 3],
            });

            var translateY = this.props.zoom;

            var scale = delta.interpolate({
              inputRange: [0, 1],
              outputRange: [0.73, 0.75],
            });

            return (
              <React.Animated.View
                key={i}
                style={[{
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  right: 0,
                  bottom: 0,
                  transform: [
                    {translateX},
                    {translateY},
                    {scale},
                  ],
                },]}>
                {child}
              </React.Animated.View>
            );
          })}
        </PanController>
      </React.View>
    );
  }
});

module.exports = Switcher;

I'd like to add the ability to zoom-in (instead of panY) the card and expand them full-width. Once they reach full-width, I want them to scroll without overlapping (like a normal scrollview).

morgante commented 9 years ago

@olivierlesnicki Yup, that's pretty much the same effect I was trying to go for.

I ended up writing the code myself directly using React Native.

smontlouis commented 9 years ago

@morgante Can you show me what you did please ? I'm stuck with the same issue.

lelandrichardson commented 9 years ago

@morgante @bulby97 @olivierlesnicki I'm going to start working on this project again, so I might be able to help you...

That react-native-switcher is pretty slick. i think this can be accomplished using the PanController.

Within the next week I hope to release some updates to this module which will include Android compatibility, and several working examples. I will also give a go at including a "Switcher" component like the react-native-switcher.

Hopefully with that example you may be able to extrapolate to your own needs?

olivierlesnicki commented 8 years ago

Good job! Look forward to it @lelandrichardson

gendronb commented 8 years ago

Nice work @lelandrichardson. Any progress regarding Android support?