React native Flatlist 不会在自定义动画底部滚动 sheet

React native Flatlist does not scroll inside the custom Animated Bottom sheet

我创建了一个自定义动画底部 sheet。用户可以上下移动底部 sheet 滚动条。在我的底部 sheet,我使用了 flatList 来获取数据并将项目呈现为卡片。到目前为止,一切都按预期工作,但我遇到了 Flatlist 滚动问题。在底部 sheet 内,平面列表不会滚动。我做了硬编码的高度值 2000px,这确实是实践,而且 FlatList 的 contentContainerStyle 添加了硬编码的 paddingBottom 2000(也是另一个不好的做法)。我想根据 Flex-box 滚动 FlatList。我不知道如何解决这个问题。

我在 expo-snacks

上分享我的代码

这是我的全部代码

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  useWindowDimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  Button,
  FlatList,
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
} from "react-native-gesture-handler";

const { width } = Dimensions.get("screen");

const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const { height } = useWindowDimensions();
  const [translateY] = useState(new Animated.Value(0));

  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(api);
      const data = await response.json();

      setEvent(data.data);
      setLoading(false);
    } catch (error) {
      console.log("erro", error);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  // Animation logic
  const bringUpActionSheet = () => {
    Animated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const closeDownBottomSheet = () => {
    Animated.timing(translateY, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const bottomSheetIntropolate = translateY.interpolate({
    inputRange: [0, 1],
    outputRange: [-height / 2.4 + 50, 0],
  });

  const animatedStyle = {
    transform: [
      {
        translateY: bottomSheetIntropolate,
      },
    ],
  };

  const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
    if (e.nativeEvent.translationY > 0) {
      closeDownBottomSheet();
    } else if (e.nativeEvent.translationY < 0) {
      bringUpActionSheet();
    }
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
          <SafeAreaView style={styles.wrapper}>
            <ContentConatiner>
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                data={event}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={({ item, index }) => {
                  const image = item?.description.images.map((img) => img.url);
                  const startDate = item?.event_dates?.starting_day;

                  return (
                    <EventContainer key={index}>
                      <EventImage
                        source={{
                          uri:
                            image[0] ||
                            "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
                        }}
                      />
                      <DescriptionContainer>
                        <Title ellipsizeMode="tail" numberOfLines={1}>
                          {item?.name?.en}
                        </Title>
                        <DescriptionText>
                          {item?.description?.intro ||
                            "No description available"}
                        </DescriptionText>
                        <DateText>{startDate}</DateText>
                      </DescriptionContainer>
                    </EventContainer>
                  );
                }}
              />
            </ContentConatiner>
          </SafeAreaView>
        </Animated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
    padding: 14,
    flexGrow: 1, // IT DOES NOT GROW
    paddingBottom: 2000, // BAD PRACTICE
  },
   height: 2000 /// BAD PRACTICE
})``; 


const ContentConatiner = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #fff;
`;
const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

在 scrollView 中使用 Hero FlatList。

<ScrollView>
  <HeroFlatList
    data={event}
    refreshControl={
    <RefreshControl
      enabled={true}
      refreshing={loading}
      onRefresh={fetchData}
     />
    }
    keyExtractor={(_, index) => index.toString()}
    renderItem={({ item, index }) => {
      const image = item?.description.images.map((img) => img.url);
      const startDate = item?.event_dates?.starting_day;
      return (
        <EventContainer key={index}>
          <EventImage
            source={{
              uri:
                image[0] || "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
             }}
          />
          <DescriptionContainer>
             <Title ellipsizeMode="tail" numberOfLines={1}>
                {item?.name?.en}
             </Title>
             <DescriptionText>
                {item?.description?.intro || "No description available"}
             </DescriptionText>
             <DateText>{startDate}</DateText>
          </DescriptionContainer>
       </EventContainer>
     );
   }}
 />
</ScrollView>

查看您的代码,

你需要改变

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
  FlatList as GFlatList
} from "react-native-gesture-handler";

从 react-native-gesture-handler 导入 FlatList 并更新您的代码,

const HeroFlatList = styled(GFlatList).attrs({
  contentContainerStyle: {
  paddingBottom: 50
  },
  height:510,
})``; 

我已经在你的代码上测试过它并且它有效。请检查。

如果您不反对使用 react-native-reanimated,那么我已经对您的代码进行了最低限度的修改,它应该完全符合您的要求。

我使用 Reanimated 的 v1 兼容性 API,因此您不必安装 babel 转译器或任何东西。它应该按原样工作。 https://snack.expo.dev/@switt/flatlist-scroll-reanimated

Reanimated 更适合这里,因为 React-Native 的原生 Animated 模块无法为 topbottomwidthheight 等属性设置动画,它可能需要将 useNativeDriver 设置为 false 才能实现您想要实现的目标。这将导致动画期间的某些性能 drops/choppy 帧。

为方便起见,这是您编辑的代码

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  useWindowDimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  Button,
  FlatList,
  ScrollView
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
} from "react-native-gesture-handler";

import Reanimated, { EasingNode } from 'react-native-reanimated';

const { width } = Dimensions.get("screen");

const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const { height } = useWindowDimensions();
  const translateY = React.useRef(new Reanimated.Value(0)).current;

  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(api);
      const data = await response.json();

      setEvent(data.data);
      setLoading(false);
    } catch (error) {
      console.log("erro", error);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  // Animation logic
  const bringUpActionSheet = () => {
    Reanimated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: true,
      easing: EasingNode.inOut(EasingNode.ease)
    }).start();
  };
  const closeDownBottomSheet = () => {
    Reanimated.timing(translateY, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
      easing: EasingNode.inOut(EasingNode.ease)
    }).start();
  };
  
  const bottomSheetTop = translateY.interpolate({
    inputRange: [0, 1],
    outputRange: [height * 0.7 - height / 2.4 + 50, height * 0.7]
  });
  const animatedStyle = {
    top: bottomSheetTop,
    bottom: 0
  };

  const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
    if (e.nativeEvent.translationY > 0) {
      closeDownBottomSheet();
    } else if (e.nativeEvent.translationY < 0) {
      bringUpActionSheet();
    }
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Reanimated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                data={event}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={({ item, index }) => {
                  const image = item?.description.images.map((img) => img.url);
                  const startDate = item?.event_dates?.starting_day;
                  return (
                    <EventContainer key={index}>
                      <EventImage
                        source={{
                          uri:
                            image[0] ||
                            "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
                        }}
                      />
                      <DescriptionContainer>
                        <Title ellipsizeMode="tail" numberOfLines={1}>
                          {item?.name?.en}
                        </Title>
                        <DescriptionText>
                          {item?.description?.intro ||
                            "No description available"}
                        </DescriptionText>
                        <DateText>{startDate}</DateText>
                      </DescriptionContainer>
                    </EventContainer>
                  );
                }}
              />
        </Reanimated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
  paddingBottom: 50
  },
  // height:510,
  // flex:1
})``; 



const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

终于找到了我想要的解决方案。谢谢 Stack-overflow 社区。没有你的帮助我做不到。

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  useWindowDimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  Platform
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
  FlatList
} from "react-native-gesture-handler";

const { width } = Dimensions.get("screen");
const IPHONE_DEVICE_START_HEIGHT = Platform.OS === 'ios' ? 0.4 : 0.6;
const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const { height } = useWindowDimensions();
  const [translateY] = useState(new Animated.Value(0));

  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(api);
      const data = await response.json();

      setEvent(data.data);
      setLoading(false);
    } catch (error) {
      console.log("erro", error);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  // Animation logic
  const bringUpActionSheet = () => {
    Animated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const closeDownBottomSheet = () => {
    Animated.timing(translateY, {
      toValue: 1,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const bottomSheetIntropolate = translateY.interpolate({
   inputRange: [0, 1],
    outputRange: [
      height * 0.5 - height / 2.4 + IPHONE_DEVICE_START_HEIGHT,
      height * IPHONE_DEVICE_START_HEIGHT,
    ],
    extrapolate: 'clamp',
  });

  const animatedStyle = {
    top: bottomSheetIntropolate,
    bottom: 0,
  };

  const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
    if (e.nativeEvent.translationY > 0) {
      closeDownBottomSheet();
    } else if (e.nativeEvent.translationY < 0) {
      bringUpActionSheet();
    }
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                data={event}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={({ item, index }) => {
                  const image = item?.description.images.map((img) => img.url);
                  const startDate = item?.event_dates?.starting_day;
                  return (
                    <EventContainer key={index}>
                      <EventImage
                        source={{
                          uri:
                            image[0] ||
                            "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
                        }}
                      />
                      <DescriptionContainer>
                        <Title ellipsizeMode="tail" numberOfLines={1}>
                          {item?.name?.en}
                        </Title>
                        <DescriptionText>
                          {item?.description?.intro ||
                            "No description available"}
                        </DescriptionText>
                        <DateText>{startDate}</DateText>
                      </DescriptionContainer>
                    </EventContainer>
                  );
                }}
              />
        
        </Animated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
   flexGrow:1
  },

})``; 



const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

这是您的代码的更新版本。在模拟器上工作正常

import React, { useState, useEffect, useRef } from "react";
import {
  StyleSheet,
  View,
  Dimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  LayoutAnimation,
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import { PanGestureHandler, FlatList } from "react-native-gesture-handler";

const { width, height } = Dimensions.get("screen");
const extendedHeight = height * 0.7;
const normalHeight = height * 0.4;
const bottomPadding = height * 0.15;

if (
  Platform.OS === "android" &&
  UIManager.setLayoutAnimationEnabledExperimental
) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const translateY = useRef(new Animated.Value(0)).current;
  const [flatListHeight, setFlatListHeight] = useState(extendedHeight);
  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    setLoading(true);
    try {
      const response = await fetch(api);
      const json = (await response.json()).data;
      setEvent(json);
    } catch (error) {
      console.log("erro", error);
    } finally {
      setLoading(false);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  const bottomSheetIntropolate = translateY.interpolate({
    inputRange: [0, 1],
    outputRange: [-normalHeight, 0],
  });

  const animatedStyle = {
    transform: [
      {
        translateY: bottomSheetIntropolate,
      },
    ],
  };

  const animate = (bringDown) => {
    setFlatListHeight(extendedHeight);
    Animated.timing(translateY, {
      toValue: bringDown ? 1 : 0,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const onGestureEnd = (e) => {
    if (e.nativeEvent.translationY > 0) {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
      setFlatListHeight(normalHeight);
    }
  };

  const gestureHandler = (e) => {
    if (e.nativeEvent.translationY > 0) {
      animate(true);
    } else if (e.nativeEvent.translationY < 0) {
      animate(false);
    }
  };

  const renderItem = ({ item, index }) => {
    const image = item?.description.images.map((img) => img.url);
    const startDate = item?.event_dates?.starting_day;
    return (
      <EventContainer key={index}>
        <EventImage
          source={{
            uri:
              image[0] ||
              "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
          }}
        />
        <DescriptionContainer>
          <Title ellipsizeMode="tail" numberOfLines={1}>
            {item?.name?.en}
          </Title>
          <DescriptionText>
            {item?.description?.intro || "No description available"}
          </DescriptionText>
          <DateText>{startDate}</DateText>
        </DescriptionContainer>
      </EventContainer>
    );
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler} onEnded={onGestureEnd}>
        <Animated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
          <SafeAreaView style={styles.wrapper}>
            <ContentConatiner>
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                style={{ height: flatListHeight }}
                data={event}
                ListFooterComponent={() => (
                  <View style={{ height: bottomPadding }} />
                )}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={renderItem}
              />
            </ContentConatiner>
          </SafeAreaView>
        </Animated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
    padding: 14,
    flexGrow: 1, // IT DOES NOT GROW
  },
})``;

const ContentConatiner = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #fff;
`;
const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

我在你的代码中更新的东西

  1. 使用 Ref 而不是 useState 来保存动画值
  2. 用 const 替换了 renderItem 的内联闭包(这可以简化 JSX 并提高性能)
  3. 将您的动画常量统一为一个。
  4. 利用 onEnded 属性在 LayoutAnimation 的帮助下优雅地降低 FlatList 的高度
  5. 由于您是从 Dimensions 访问宽度,高度也可以从同一资源获取,即删除钩子
  6. 将 translateY 逻辑计算和 Flatlist 高度更新为百分比基础
  7. listFooterItem 为最后一项提供一些填充
  8. 对您的抓取逻辑的小更新