PanResponder改变Card Y值和移出屏幕的问题

PanResponder to change Card Y value and issue of moving out of screen

我是 react-native 动画 API 和 PanResponder API 的新手。
我想创建 google 地图克隆 UI 和在结果后显示的卡片动画。
我想创建卡片,当用户向上滑动时,卡片会到达屏幕顶部,当用户向下滑动时,卡片应调整到原始位置。
Demo of Google Maps that i want to achieve


我已经为此实现了一些代码,但我面临的问题是卡片在顶部方向移出屏幕(在卡片到达顶部位置后向上滑动)。有时,向下滑动有效,但并非总是如此。


My demo

Expo Snack Link


App.js

import React, { useEffect, useState } from "react";
import {
  SafeAreaView,
  View,
  Text,
  Dimensions,
  PanResponder,
  Animated,
} from "react-native";
import { Searchbar, FAB } from "react-native-paper";
import MapView from "react-native-maps";
import * as Location from "expo-location";


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

const App = () => {
  

  const [latitute, setLatitute] = useState(0);
  const [longitute, setLongitute] = useState(0);
  const [isLoading, setIsLoading] = useState(true);


  const pan = useState(
    new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 200 })
  )[0];


  const panResponder = useState(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onPanResponderGrant: () => {
        pan.extractOffset();
        return true;
      },
      onPanResponderMove: (e, gestureState) => {
        pan.setValue({ x: 0, y: gestureState.dy });
      },
      onPanResponderRelease: (e, gestureState) => {
        
        if (gestureState.moveY > SCREEN_HEIGHT - 200) {
          Animated.spring(pan.y, {
            toValue: 0,
            tension: 1,
            useNativeDriver: true,
          }).start();
        } else if (gestureState.moveY < 200) {
          Animated.spring(pan.y, {
            toValue: 0,
            tension: 1,
            useNativeDriver: true,
          }).start();
        } else if (gestureState.dy < 0) {
          Animated.spring(pan.y, {
            toValue: -SCREEN_HEIGHT + 200,
            tension: 1,
            useNativeDriver: true,
          }).start();
        } else if (gestureState.dy > 0) {
          Animated.spring(pan.y, {
            toValue: SCREEN_HEIGHT - 200,
            tension: 1,
            useNativeDriver: true,
          }).start();
        }
      },
    })
  )[0];

  const animatedHeight = {
    transform: pan.getTranslateTransform(),
  };

  useEffect(() => {
    (async () => {
      let { status } = await Location.requestPermissionsAsync();

      let location = await Location.getCurrentPositionAsync({});
      console.log(location);
      setLatitute(location.coords.latitude);
      setLongitute(location.coords.longitude);
      setIsLoading(false);
    })();
  }, []);

  return (
    <SafeAreaView
      style={{
        display: "flex",
        flex: 1,
      }}
    >
      {!isLoading && (
        <View>
          <MapView
            style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}
            showsUserLocation
            initialRegion={{
              latitude: latitute,
              longitude: longitute,
              latitudeDelta: 0.01,
              longitudeDelta: 0.01,
            }}
          />

          <Searchbar
            placeholder="Search"
            style={{
              position: "absolute",
              top: 10,
              margin: 10,
            }}
            
            icon="menu"
            onIconPress={() => {}}
          />

          <FAB
            style={{
              position: "absolute",
              top: SCREEN_HEIGHT * 0.8,
              left: SCREEN_WIDTH * 0.8,
            }}
            icon="plus"
            onPress={() => console.log("Pressed")}
          />

          <Animated.View
            style={[
              animatedHeight,
              {
                position: "absolute",
                right: 0,
                left: 0,
                width: SCREEN_WIDTH,
                height: SCREEN_HEIGHT,
                backgroundColor: "#0af",
                borderTopLeftRadius: 25,
                borderTopRightRadius: 25,
                zIndex: 10,
              },
            ]}
            {...panResponder.panHandlers}
          >
            <View>
              <Text>Hi</Text>
            </View>
          </Animated.View>
        </View>
      )}
    </SafeAreaView>
  );
};

export default App;


如果你想做 bottomsheet 那么你可以使用 react-native-bottomsheet-reanimated

yarn add react-native-bottomsheet-reanimated
import React, { useEffect, useState } from "react";
import {
  SafeAreaView,
  View,
  Text,
  Dimensions,
  PanResponder,
  Animated,
  StyleSheet
} from "react-native";
import { Searchbar, FAB } from "react-native-paper";
import MapView from "react-native-maps";
import * as Location from "expo-location";
import BottomSheet from "react-native-bottomsheet-reanimated";



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

const App = () => {
  

  const [latitute, setLatitute] = useState(0);
  const [longitute, setLongitute] = useState(0);
  const [isLoading, setIsLoading] = useState(true);


  const pan = useState(
    new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 200 })
  )[0];

  useEffect(() => {
    (async () => {
      let { status } = await Location.requestPermissionsAsync();

      let location = await Location.getCurrentPositionAsync({});
      console.log(location);
      setLatitute(location.coords.latitude);
      setLongitute(location.coords.longitude);
      setIsLoading(false);
    })();
  }, []);

  return (
    <SafeAreaView
      style={{
        display: "flex",
        flex: 1,
      }}
    >
      {!isLoading && (
        <View>
          <MapView
            style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}
            showsUserLocation
            initialRegion={{
              latitude: latitute,
              longitude: longitute,
              latitudeDelta: 0.01,
              longitudeDelta: 0.01,
            }}
          />

          <Searchbar
            placeholder="Search"
            style={{
              position: "absolute",
              top: 10,
              margin: 10,
            }}
            
            icon="menu"
            onIconPress={() => {}}
          />

          <FAB
            style={{
              position: "absolute",
              top: SCREEN_HEIGHT * 0.8,
              left: SCREEN_WIDTH * 0.8,
            }}
            icon="plus"
            onPress={() => console.log("Pressed")}
          />

          <BottomSheet
          bottomSheerColor="#FFFFFF"
          initialPosition={"30%"}  //200, 300
          snapPoints={["30%","100%"]}
          isBackDropDismisByPress={true}
          isRoundBorderWithTipHeader={true}
          containerStyle={{backgroundColor:"#0af"}}
          header={
            <View>
              <Text style={styles.text}>Header</Text>
            </View>
          }
          body={
            <View style={styles.body}>
              <Text>Hi</Text>
            </View>
          }
        />

        </View>
      )}
    </SafeAreaView>
  );
};


export default App;

const styles = StyleSheet.create({
  body:{
    justifyContent:"center",
    alignItems:"center"
  },
  text:{
    fontSize:20,
    fontWeight:"bold"
  }
});