wix / react-native-navigation

A complete native navigation solution for React Native
https://wix.github.io/react-native-navigation/
MIT License
13.04k stars 2.67k forks source link

PanResponder animation is restricted to 10px when sideMenu is implemented #4826

Closed barclayd closed 5 years ago

barclayd commented 5 years ago

Issue Description

This animation using PanResponder that involves swiping up on a bar at the bottom to reveal a full-page component does not work as intended/is not smooth whatsoever when run on a native device (or simulator) - but works just as you would expect on Expo.

As you can see from the gif, despite swiping up on the bar from the bottom a considerable distance - the height only increases by a small amount.

To Reproduce

How to reproduce: run code as 'npm react-native run-ios' in create-react-native-app on iOS simulator/run on native device through excode.

Expected Behavior

A smooth transition that bounces to top of page to expand in full and when swiped down to minimise smoothly transitions to bar at bottom.

The code works perfectly using expo on native device as demonstrated in this Gif and link on Expo.

Expected behaviour

Steps to Reproduce / Code Snippets / Screenshots

import React, { Component } from 'react';
import {
    View,
    Text,
    Dimensions,
    Animated,
    Image,
    PanResponder,
    Slider,
    ScrollView,
} from 'react-native';

import Ionicons from "react-native-vector-icons/Ionicons";

const SCREEN_HEIGHT = Dimensions.get('window').height;
const SCREEN_WIDTH = Dimensions.get('window').width;

export default class AppleMusicUI extends Component {
    state = {
        isScrollEnabled: false,
    };
    componentWillMount() {
        this.scrollOffset = 0;

        this.animation = new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 90 });

        this.PanResponder = PanResponder.create({
            onMoveShouldSetPanResponder: (evt, gestureState) => {
                if (
                    (this.state.isScrollEnabled &&
                        this.scrollOffset <= 0 &&
                        gestureState.dy > 0) ||
                    (!this.state.isScrollEnabled && gestureState.dy < 0)
                ) {
                    return true;
                } else {
                    return false;
                }
            },
            onPanResponderGrant: () => {
                this.animation.extractOffset();
            },
            onPanResponderMove: (evt, gestureState) => {
                this.animation.setValue({ x: 0, y: gestureState.dy });
            },
            onPanResponderRelease: (evt, gestureState) => {
                if (gestureState.moveY > SCREEN_HEIGHT - 110) {
                    Animated.spring(this.animation.y, {
                        toValue: 0,
                        tension: 1,
                    }).start();
                } else if (gestureState.moveY < 110) {
                    Animated.spring(this.animation.y, {
                        toValue: 0,
                        tension: 1,
                    }).start();
                } else if (gestureState.dy < 0) {
                    this.setState({ isScrollEnabled: true });
                    Animated.spring(this.animation.y, {
                        toValue: -SCREEN_HEIGHT + 110,
                        tension: 1,
                    }).start();
                } else if (gestureState.dy > 0) {
                    this.setState({ isScrollEnabled: false });
                    Animated.spring(this.animation.y, {
                        toValue: SCREEN_HEIGHT - 110,
                        tension: 1,
                    }).start();
                }
            },
        });
    }
    render() {
        const animatedHeight = {
            transform: this.animation.getTranslateTransform(),
        };

        const animatedImageHeight = this.animation.y.interpolate({
            inputRange: [0, SCREEN_HEIGHT - 90],
            outputRange: [200, 32],
            extrapolate: 'clamp',
        });

        const animatedSongTitleOpacity = this.animation.y.interpolate({
            inputRange: [0, SCREEN_HEIGHT - 500, SCREEN_HEIGHT - 90],
            outputRange: [0, 0, 1],
            extrapolate: 'clamp',
        });

        const animatedImageMarginLeft = this.animation.y.interpolate({
            inputRange: [0, SCREEN_HEIGHT - 90],
            outputRange: [SCREEN_WIDTH / 2 - 100, 10],
            extrapolate: 'clamp',
        });

        const animatedHeaderHeight = this.animation.y.interpolate({
            inputRange: [0, SCREEN_HEIGHT - 90],
            outputRange: [SCREEN_HEIGHT / 2, 90],
            extrapolate: 'clamp',
        });

        const animatedSongDetailsOpacity = this.animation.y.interpolate({
            inputRange: [0, SCREEN_HEIGHT - 500, SCREEN_HEIGHT - 90],
            outputRange: [1, 0, 0],
            extrapolate: 'clamp',
        });

        const animatedBackgroundColor = this.animation.y.interpolate({
            inputRange: [0, SCREEN_HEIGHT - 90],
            outputRange: ['rgba(0,0,0,0.5)', 'white'],
            extrapolate: 'clamp',
        });

        return (
            <Animated.View
                style={{ flex: 1, backgroundColor: animatedBackgroundColor }}>
                <Animated.View
                    {...this.PanResponder.panHandlers}
                    style={[
                        animatedHeight,
                        {
                            position: 'absolute',
                            left: 0,
                            right: 0,
                            zIndex: 10,
                            backgroundColor: 'white',
                            height: SCREEN_HEIGHT,
                        },
                    ]}>
                    <ScrollView
                        scrollEnabled={this.state.isScrollEnabled}
                        scrollEventThrottle={16}
                        onScroll={event => {
                            this.scrollOffset = event.nativeEvent.contentOffset.y;
                        }}>
                        <Animated.View
                            style={{
                                height: animatedHeaderHeight,
                                borderTopWidth: 1,
                                borderTopColor: '#ebe5e5',
                                flexDirection: 'row',
                                alignItems: 'center',
                            }}>
                            <View
                                style={{ flex: 4, flexDirection: 'row', alignItems: 'center' }}>
                                <Animated.View
                                    style={{
                                        height: animatedImageHeight,
                                        width: animatedImageHeight,
                                        marginLeft: animatedImageMarginLeft,
                                    }}>
                                    <Image
                                        style={{
                                            flex: 1,
                                            width: null,
                                            height: null,
                                        }}
                                        source={require('../../assets/Logo.png')}
                                    />
                                </Animated.View>
                                <Animated.Text
                                    style={{
                                        opacity: animatedSongTitleOpacity,
                                        fontSize: 18,
                                        paddingLeft: 10,
                                    }}>
                                    Hotel California(Live)
                                </Animated.Text>
                            </View>
                            <Animated.View
                                style={{
                                    opacity: animatedSongTitleOpacity,
                                    flex: 1,
                                    flexDirection: 'row',
                                    justifyContent: 'space-around',
                                }}>
                                <Ionicons name="md-pause" size={32} />
                                <Ionicons name="md-play" size={32} />
                            </Animated.View>
                        </Animated.View>

                        <Animated.View
                            style={{
                                height: animatedHeaderHeight,
                                opacity: animatedSongDetailsOpacity,
                            }}>

                            <View
                                style={{
                                    flex: 1,
                                    alignItems: 'center',
                                    justifyContent: 'flex-end',
                                }}>
                                <Text style={{ fontWeight: 'bold', fontSize: 22 }}>
                                    Hotel California (Live)
                                </Text>
                                <Text style={{ fontSize: 18, color: '#fa95ed' }}>
                                    Eagles - Hell Freezes Over
                                </Text>
                            </View>

                            <View
                                style={{
                                    height: 40,
                                    width: SCREEN_WIDTH,
                                    alignItems: 'center',
                                }}>
                                <Slider
                                    style={{ width: 300 }}
                                    step={1}
                                    minimumValue={18}
                                    maximumValue={71}
                                    value={18}
                                />
                            </View>

                            <View
                                style={{
                                    flex: 2,
                                    flexDirection: 'row',
                                    alignItems: 'center',
                                    justifyContent: 'space-around',
                                }}>
                                <Ionicons name="md-rewind" size={40} />
                                <Ionicons name="md-pause" size={50} />
                                <Ionicons name="md-fastforward" size={40} />
                            </View>
                            <View
                                style={{
                                    flexDirection: 'row',
                                    justifyContent: 'space-between',
                                    paddingHorizontal: 20,
                                    paddingBottom: 20,
                                }}>
                                <Ionicons
                                    name="md-add"
                                    size={32}
                                    style={{ color: '#fa95ed' }}
                                />
                                <Ionicons
                                    name="md-more"
                                    size={32}
                                    style={{ color: '#fa95ed' }}
                                />
                            </View>
                        </Animated.View>
                        <View style={{ height: 1000 }} />
                    </ScrollView>
                </Animated.View>
            </Animated.View>
        );
    }
}

Screenshot/Gif:

Bar

Environment

React Native Environment Info:
    System:
      OS: macOS 10.14.3
      CPU: (4) x64 Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
      Memory: 199.20 MB / 8.00 GB
      Shell: 3.2.57 - /bin/bash
    Binaries:
      Node: 10.5.0 - /usr/local/bin/node
      npm: 6.6.0 - /usr/local/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0
    IDEs:
      Xcode: 10.0/10A255 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.6.3 => 16.6.3 
      react-native: 0.58.4 => 0.58.4 
    npmGlobalPackages:
      react-native-cli: 2.0.1
      react-native-git-upgrade: 0.2.7
barclayd commented 5 years ago

After extensive research, I found a temporary fix as follows:

const setDefaultSettings = () => {
    Navigation.setDefaultOptions({
        topBar: {
            visible: false
        },
        statusBar: {
            style: "light"
        },
        sideMenu: {
            openGestureMode: 'bezel',
            left: {
                visible: true,
                enabled: true
            },
            right: {
                visible: false,
                enabled: false
            }
        }
    });
};

I believe to help future users of React Native Navigation, that openGestureModel should be set to 'bezel' by default or there should be a more informative degree of information provided on the docs regarding the impact of the use of 'sideMenu' in its native form upon PanResponder based animations

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe the issue is still relevant, please test on the latest Detox and report back. Thank you for your contributions.

stale[bot] commented 5 years ago

The issue has been closed for inactivity.