React Native的Flatlist在自定义动画底部面板中无法滚动

5

我创建了一个自定义的动画底部表单。用户可以将底部表单上下滚动。在我的底部表单中,我使用了FlatList来获取数据并将项目呈现为卡片。到目前为止,一切都按预期工作,但是我遇到了FlatList滚动的问题。在底部表单内,FlatList无法滚动。我设置了硬编码高度值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;
`;

你真的想使用PanGestureHandler吗?你不能只是将onScroll属性传递给FlatList并在那里控制操作表动画吗? https://reactnative.dev/docs/scrollview#onscroll - Maksym Bezruchko
5个回答

5

这是你代码的更新版本,在模拟器上运行良好。

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. 使用 useRef 而非 useState 来保存动画值
  2. 用 const 替换 inline closure ,可以简化 JSX 并提高性能
  3. 把你的动画 const 统一了
  4. 利用 onEnded 属性和 LayoutAnimation 巧妙地降低 Flatlist 的高度
  5. 由于你从 Dimensions 中访问了宽度,因此高度也可以从同一个资源获取,即删除了 hook
  6. 更新了 translateY 逻辑计算以及 Flatlist 高度为百分比基础
  7. listFooterItem 提供给最后一项一些填充
  8. 对您的获取逻辑进行了小修补。

非常感谢您抽出时间为我解决这个问题。但是似乎在 Android 模拟器中 FlatList 的项目无法滚动。您可以在此处测试您的代码:https://snack.expo.dev/@alakdam/vigorous-cake - Krisna
@Krisna 你说得对,我只在iOS模拟器上测试了。不确定为什么在Android模拟器上无法滚动。周末会尝试找出代码的问题所在,哈哈。 - Sahil Manchanda
1
尝试使用来自react-native-gesture-handler的FlatList,然后它就可以工作了。我不知道为什么,但它起作用了。 - Krisna
@Krisna,感谢您的反馈,我已经相应地更新了代码片段。请问上述代码是否符合您的期望? - Sahil Manchanda
1
我已经测试过了,它的工作效果符合预期。我已经点赞了。非常感谢。 - Krisna

4

如果您不反对使用 react-native-reanimated,那么我已经对您的代码进行了最小修改,应该可以完全满足您的需求。

我使用了Reanimated的 v1 兼容API,因此您不需要安装babel转换器或其他内容。它应该能够直接工作。 https://snack.expo.dev/@switt/flatlist-scroll-reanimated

Example of working code

在这里, Reanimated 更为适用,因为 React-Native 的本地 Animated 模块不能动画化 top, bottom, width, height等属性,可能需要将 useNativeDriver 设置为 false 来实现您想要的效果,这会导致一些性能降低/帧率不稳定的情况。

这是您编辑后的代码,只是为了方便:

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;
`;

非常感谢您抽出时间为我解决这个问题。我并不反对使用 react-native-reanimated,但是他们的包有点烦人,因为他们每两三个月就会更改一次属性。我仍在努力学习 RN 自己的动画。如果您能给我提供我的代码中的解决方案,那就太棒了,对我来说也是一个学习机会。我遇到的问题是 Flatlist 中的某些部分无法滚动到屏幕外。我认为我的动画样式计算有误。仍在努力解决中。再次感谢您详细的回复。 - Krisna
我看到你已经发现了自己答案中的问题。但是为了完整起见,我还是在这里回答一下。 - switt
1
你原始代码中提供的 top 属性导致 Animated.View 的一部分被渲染到屏幕外,所以像你在答案中所做的那样,提供 bottom 属性有助于限制它。我可以向你保证,Reanimated非常稳定,我的代码库已经依赖Reanimated超过1年,并且已经经历了从v1到v2的跳跃,这是毫不费力的。 在v2处于Beta版本时更改属性很可能发生,但它已经稳定了很长时间。附言:如果您觉得这个答案有帮助,请考虑点赞 ;) - switt

4

最后我找到了我想要的解决方案。非常感谢 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;
`;

0

看了你的代码,

你需要改变

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,
})``; 

我已经在你的代码上进行了测试,它可以正常运行。请检查。


1
谢谢@Janak,但问题是当您向下滚动动画底部表并尝试滚动项目时。它不会显示flatlist的最后一个项目(我需要增加硬编码的“高度”和“paddingBottom”)。我想将此底部表格用作可重复使用的组件。是否有任何方法可以使用flexbox而不是硬编码的“高度”和“paddingBottom”。这个问题的主要目的是如何使这个flatlist样式比硬编码的高度和paddingBottom更有效。 https://snack.expo.dev/@alakdam/scroll - Krisna

-1

将 HeroFlatList 放在 scrollView 中。

<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>

你没有收到任何警告吗,像这样:VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead. - Krisna
不,我没有收到任何错误信息。请检查这里的完整 Expo 链接:https://snack.expo.dev/@john.ocean/001cbe - John Ocean
你能看到FlatList的最后一项吗?我看不到。 - Krisna
好的,我明白了。最后一条记录的标题是Unikuskit,对吗?这里是完整的代码:https://snack.expo.dev/@john.ocean/001cbe - John Ocean
非常感谢!但我认为这不是最优解决方案 https://dev59.com/n8Dqa4cB1Zd3GeqPXSlF - Krisna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接