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.