简体   繁体   中英

Google Maps panel with React Native

I am trying to replicate the Google Maps Swipe Panel ("Explore City of London") or something similar. The Panel should open and close by swiping and when open, the inner content should scroll, ie within a ScrollView, and then when the ScrollView is at the top, the scroll is disabled and the panel can close.

在此处输入图片说明

At the moment I am using rn-sliding-up-panel plugin for my Panel swipe, with a child component of ScrollView. I then find the position of the ScrollView, if it is at 0 and the user is swiping down, I close the Panel.

However, this seems rather buggy on both Android and iOS devices. Some times it sticks to position 0 in the ScrollView.

Has anyone managed to build something similar to this? Maybe Im on the right track and it needs refining? Or theres a better plugin out there?

Any advice or examples would be greatly appriciated.

class ssss extends Component {
  constructor(props) {
    super(props);
    this.state = {
      position: new Animated.Value(props.isOpen ? 0 : height),
      opacity: new Animated.Value(0),
      height: defaultHeight,
      expanded: false,
      visible: props.isOpen
    };
  }

  // When user starts pulling popup previous height gets stored here
  // to help us calculate new height value during and after pulling
  _previousHeight = 0;

  componentDidMount() {
    // Initialize PanResponder to handle move gestures
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        const { dx, dy } = gestureState;
        // Ignore taps
        if (dx !== 0 && dy === 0) {
          return true;
        }
        return false;
      },
      onPanResponderGrant: (evt, gestureState) => {
        // Store previous height before user changed it
        this._previousHeight = this.state.height;
      },
      onPanResponderMove: (evt, gestureState) => {
        // Pull delta and velocity values for y axis from gestureState
        const { dy, vy } = gestureState;
        // Subtract delta y from previous height to get new height
        let newHeight = this._previousHeight - dy;

        // Animate heigh change so it looks smooth
        LayoutAnimation.easeInEaseOut();

        // Switch to expanded mode if popup pulled up above 80% mark
        if (newHeight > 600 - 600 / 5) {
          this.setState({ expanded: true });
        } else {
          this.setState({ expanded: false });
        }

        // Expand to full height if pulled up rapidly
        if (vy < -0.75) {
          this.setState({
            expanded: true,
            height: height * 0.65
          });
        }

        // Close if pulled down rapidly
        else if (vy > 0.75) {
          this.props.onClose();
        }
        // Close if pulled below 95% mark of default height
        else if (newHeight < defaultHeight * 0.95) {
          this.props.onClose();
        }
        // Limit max height to screen height
        else if (newHeight > 600) {
          this.setState({ height: height * 0.65 });
        } else {
          this.setState({ height: newHeight });
        }
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
        const { dy } = gestureState;
        const newHeight = this._previousHeight - dy;

        // Close if pulled below default height
        if (newHeight < defaultHeight) {
          this.props.onClose();
        }

        // Update previous height
        this._previousHeight = this.state.height;
      },
      onShouldBlockNativeResponder: (evt, gestureState) => {
        // Returns whether this component should block native components from becoming the JS
        // responder. Returns true by default. Is currently only supported on android.
        return true;
      }
    });
  }

  // Handle isOpen changes to either open or close popup
  componentWillReceiveProps(nextProps) {
    // isOpen prop changed to true from false
    if (!this.props.isOpen && nextProps.isOpen) {
      this.animateOpen();
    }
    // isOpen prop changed to false from true
    else if (this.props.isOpen && !nextProps.isOpen) {
      this.animateClose();
    }
  }

  // Open popup
  animateOpen() {
    // Update state first
    this.setState({ visible: true }, () => {
      Animated.parallel([
        // Animate opacity
        Animated.timing(
          this.state.opacity,
          { toValue: 0.5 } // semi-transparent
        ),
        // And slide up
        Animated.timing(
          this.state.position,
          { toValue: 0 } // top of the screen
        )
      ]).start();
    });
  }

  // Close popup
  animateClose() {
    Animated.parallel([
      // Animate opacity
      Animated.timing(
        this.state.opacity,
        { toValue: 0 } // transparent
      ),
      // Slide down
      Animated.timing(
        this.state.position,
        { toValue: height } // bottom of the screen
      )
    ]).start(() =>
      this.setState({
        // Reset to default values
        height: defaultHeight,
        expanded: false,
        visible: false
      })
    );
  }

  render() {
    // Render nothing if not visible
    if (!this.state.visible) {
      return null;
    }
    return (
      <View style={styles.container}>
        {/* Closes popup if user taps on semi-transparent backdrop */}
        <TouchableWithoutFeedback onPress={this.props.onClose}>
          <Animated.View
            style={[styles.backdrop, { opacity: this.state.opacity }]}
          />
        </TouchableWithoutFeedback>
        <Animated.View
          style={[
            styles.modal,
            {
              // Animates height
              height: this.state.height,
              // Animates position on the screen
              transform: [
                { translateY: this.state.position },
                { translateX: 0 }
              ]
            }
          ]}
        >
          {/* Content */}
          <View style={styles.content}>
            <View
              style={[styles.topUpContainer]}
              {...this._panResponder.panHandlers}
            >
              <View style={styles.hookerContainer}>
                <View style={styles.hooker} />
              </View>
              {/* Your content comes here */}
            </View>
          </View>
        </Animated.View>
      </View>
    );
  }
}

This might help you

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM