FlatList 无限循环 - React Native

发布于 2025-01-14 12:09:53 字数 471 浏览 6 评论 0原文

我正在尝试制作一个在两个方向上无限滚动的平面列表。

已经有一个小的实施概要(https://snack.expo.dev/@ slam_ua/flatlist-loop),但我似乎无法做一些事情:

  1. 无缝列表。当数据更新时滚动停止。
  2. 无限向上滚动不起作用。
  3. 将初始坐标 (00) 置于中心(从带有示例的图片中可以清楚地看出我的意思)。

下面我展示了我想要实现的结果的示例:

在此处输入图像描述

I'm trying to make a Flatlist with infinite scrolling in both directions.

There is already a small outline of the implementation (https://snack.expo.dev/@slam_ua/flatlist-loop), but I can't seem to do a few things:

  1. Seamless list. Scrolling stops when data is updated.
  2. Infinite scroll up not working.
  3. Centering the initial coordinate (00) in the center (from the picture with an example it will be clear what I mean).

Below I have shown an example of the result I want to achieve:

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

安静被遗忘 2025-01-21 12:09:53

我们可以调整 react-native-circular-wheel-picker 来实现这。

import React, { useEffect, useRef, useState } from "react"
import {
  NativeScrollEvent,
  NativeSyntheticEvent,
  FlatList,
  Text,
  View,
  StyleProp,
  TextStyle,
  ViewStyle,
  StyleSheet,
} from "react-native"

type dataType = {
  value: number | string
  label: number | string
}

interface WheelNumberPickerProps {
  data: dataType[]
  height: number
  textStyle?: StyleProp<TextStyle>
  selectedTextStyle?: StyleProp<TextStyle>
  unselectedTextStyle?: StyleProp<TextStyle>
  dividerWidth?: ViewStyle["borderBottomWidth"]
  dividerColor?: ViewStyle["borderBottomColor"]
  selectedValue?: number | string
  onValueChange?: (value: number | string) => void
}

function WheelNumberPicker({
  height = 25,
  textStyle,
  selectedTextStyle,
  unselectedTextStyle,
  dividerWidth = 1,
  dividerColor,
  selectedValue = 0,
  onValueChange,
  data = [],
}: WheelNumberPickerProps) {
  const [dataArray] = useState<dataType[]>([...data, ...data, ...data])
  const [value, setValue] = useState<number | string>(selectedValue)

  const flatListRef = useRef<FlatList>()
  const currentYOffset = useRef<number>(0)
  const numberOfValue = useRef<number>(data.length)
  const initialOffset = useRef<number>((data.length - 0.5) * height)

  useEffect(() => {
    if (!onValueChange) {
      return
    }
    onValueChange(value)
  }, [value, onValueChange])

  const onScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
    const offsetY = nativeEvent.contentOffset.y
    let index = Math.ceil((offsetY % initialOffset.current) / height)
    index = index < numberOfValue.current ? index : numberOfValue.current - 1
    const selectedValue = data[index].value
    if (value !== selectedValue) {
      setValue(selectedValue)
    }

    if (offsetY < currentYOffset.current) {
      if (offsetY <= initialOffset.current - height) {
        flatListRef.current?.scrollToOffset({
          offset: offsetY + height * numberOfValue.current,
          animated: false,
        })
        currentYOffset.current = offsetY + height * numberOfValue.current
        return
      }
    }

    if (offsetY > currentYOffset.current) {
      if (offsetY > initialOffset.current + height) {
        flatListRef.current?.scrollToOffset({
          offset: offsetY - height * numberOfValue.current,
          animated: false,
        })
        currentYOffset.current = offsetY - height * numberOfValue.current
        return
      }
    }

    currentYOffset.current = offsetY
  }

  return (
    <View style={{ alignItems: "center", justifyContent: "center" }}>
      <View
        style={{
          position: "absolute",
          borderTopWidth: dividerWidth,
          borderBottomWidth: dividerWidth,
          borderColor: dividerColor,
          height,
          width: height * 1.2,
        }}
      />
      <View style={{ width: height * 1.2, height: height * 5 }}>
        <FlatList
          data={dataArray}
          onScroll={onScroll}
          ref={flatListRef}
          showsVerticalScrollIndicator={false}
          snapToAlignment="center"
          snapToInterval={height}
          scrollEventThrottle={12}
          decelerationRate="fast"
          keyExtractor={(_, index) => index.toString()}
          renderItem={({ item }) => {
            return (
              <View
                style={{
                  width: "100%",
                  height,
                  alignItems: "center",
                  justifyContent: "center",
                }}>
                <Text style={[textStyle, selectedTextStyle]}>{item.label}</Text>
              </View>
            )
          }}
        />
      </View>
    </View>
  )
}

export default WheelNumberPicker

我们按如下方式使用它。

const [data] = useState(
    Array(24)
      .fill(0)
      .map((_, index) => {
        return {
          value: index,
          label: index < 10 ? "0" + index : index,
        }
      })
  )
  return (
    <View style={{ marginTop: 250 }}>
      <WheelNumberPicker height={30} data={data} />
    </View>
  )

上述产生以下结果。

输入图片此处描述

We can tweak react-native-circular-wheel-picker to achieve this.

import React, { useEffect, useRef, useState } from "react"
import {
  NativeScrollEvent,
  NativeSyntheticEvent,
  FlatList,
  Text,
  View,
  StyleProp,
  TextStyle,
  ViewStyle,
  StyleSheet,
} from "react-native"

type dataType = {
  value: number | string
  label: number | string
}

interface WheelNumberPickerProps {
  data: dataType[]
  height: number
  textStyle?: StyleProp<TextStyle>
  selectedTextStyle?: StyleProp<TextStyle>
  unselectedTextStyle?: StyleProp<TextStyle>
  dividerWidth?: ViewStyle["borderBottomWidth"]
  dividerColor?: ViewStyle["borderBottomColor"]
  selectedValue?: number | string
  onValueChange?: (value: number | string) => void
}

function WheelNumberPicker({
  height = 25,
  textStyle,
  selectedTextStyle,
  unselectedTextStyle,
  dividerWidth = 1,
  dividerColor,
  selectedValue = 0,
  onValueChange,
  data = [],
}: WheelNumberPickerProps) {
  const [dataArray] = useState<dataType[]>([...data, ...data, ...data])
  const [value, setValue] = useState<number | string>(selectedValue)

  const flatListRef = useRef<FlatList>()
  const currentYOffset = useRef<number>(0)
  const numberOfValue = useRef<number>(data.length)
  const initialOffset = useRef<number>((data.length - 0.5) * height)

  useEffect(() => {
    if (!onValueChange) {
      return
    }
    onValueChange(value)
  }, [value, onValueChange])

  const onScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
    const offsetY = nativeEvent.contentOffset.y
    let index = Math.ceil((offsetY % initialOffset.current) / height)
    index = index < numberOfValue.current ? index : numberOfValue.current - 1
    const selectedValue = data[index].value
    if (value !== selectedValue) {
      setValue(selectedValue)
    }

    if (offsetY < currentYOffset.current) {
      if (offsetY <= initialOffset.current - height) {
        flatListRef.current?.scrollToOffset({
          offset: offsetY + height * numberOfValue.current,
          animated: false,
        })
        currentYOffset.current = offsetY + height * numberOfValue.current
        return
      }
    }

    if (offsetY > currentYOffset.current) {
      if (offsetY > initialOffset.current + height) {
        flatListRef.current?.scrollToOffset({
          offset: offsetY - height * numberOfValue.current,
          animated: false,
        })
        currentYOffset.current = offsetY - height * numberOfValue.current
        return
      }
    }

    currentYOffset.current = offsetY
  }

  return (
    <View style={{ alignItems: "center", justifyContent: "center" }}>
      <View
        style={{
          position: "absolute",
          borderTopWidth: dividerWidth,
          borderBottomWidth: dividerWidth,
          borderColor: dividerColor,
          height,
          width: height * 1.2,
        }}
      />
      <View style={{ width: height * 1.2, height: height * 5 }}>
        <FlatList
          data={dataArray}
          onScroll={onScroll}
          ref={flatListRef}
          showsVerticalScrollIndicator={false}
          snapToAlignment="center"
          snapToInterval={height}
          scrollEventThrottle={12}
          decelerationRate="fast"
          keyExtractor={(_, index) => index.toString()}
          renderItem={({ item }) => {
            return (
              <View
                style={{
                  width: "100%",
                  height,
                  alignItems: "center",
                  justifyContent: "center",
                }}>
                <Text style={[textStyle, selectedTextStyle]}>{item.label}</Text>
              </View>
            )
          }}
        />
      </View>
    </View>
  )
}

export default WheelNumberPicker

We use it as follows.

const [data] = useState(
    Array(24)
      .fill(0)
      .map((_, index) => {
        return {
          value: index,
          label: index < 10 ? "0" + index : index,
        }
      })
  )
  return (
    <View style={{ marginTop: 250 }}>
      <WheelNumberPicker height={30} data={data} />
    </View>
  )

The above yields to the following result.

enter image description here

如梦初醒的夏天 2025-01-21 12:09:53

您可以通过在 React Native 中使用简单的选择器库来实现这一点。
你想要的视图/UI,你必须为它们创建组件。
你可以使用这个库:

react-native-picker

npm i react-native-picker

import Picker from 'react-native-picker';
let data = [];
for(var i=0;i<100;i++){
    data.push(i);
}
 
Picker.init({
    pickerData: data,
    selectedValue: [59],
    onPickerConfirm: data => {
        console.log(data);
    },
    onPickerCancel: data => {
        console.log(data);
    },
    onPickerSelect: data => {
        console.log(data);
    }
});
Picker.show();

react-native-wheel-picker

https://www.npmjs.com/package/react-native-wheel-picker

编辑后:
如果有人需要的话,请不要删除上面的库。

对于无限滚动,您可以使用和调整此库:
https://www.npmjs.com/package/react-native-无限循环滚动

这是演示链接:

https://drive.google.com/uc?id=1re6VhBZ8NZIsPYvN5DMhgveA7ei87N9U

工作演示,可能是因为吃零食而有点懒惰。
但有效:
无限滚动库演示

You can achieve this by using simple picker libraries in React Native.
The View/UI You want, you have to create components for them.
you can use this library:

react-native-picker

npm i react-native-picker

import Picker from 'react-native-picker';
let data = [];
for(var i=0;i<100;i++){
    data.push(i);
}
 
Picker.init({
    pickerData: data,
    selectedValue: [59],
    onPickerConfirm: data => {
        console.log(data);
    },
    onPickerCancel: data => {
        console.log(data);
    },
    onPickerSelect: data => {
        console.log(data);
    }
});
Picker.show();

Or

react-native-wheel-picker

https://www.npmjs.com/package/react-native-wheel-picker

After Edit:
Not Removing Above libraries, if someone needs it ever.

For infinite scroll, you can use and tweak this library:
https://www.npmjs.com/package/react-native-infinite-looping-scroll

Here's the link of demo:

https://drive.google.com/uc?id=1re6VhBZ8NZIsPYvN5DMhgveA7ei87N9U

Working Demo, might be bit laggy because running on snack.
But works:
infinite Scroll Library Demo

生生漫 2025-01-21 12:09:53

onEndReached={()=>flatListRef.current.scrollToOffset({offset: 0,animated: true})}

onEndReached={()=>flatListRef.current.scrollToOffset({offset: 0, animated: true})}

深海里的那抹蓝 2025-01-21 12:09:53

修复 David Scholz 答案中的错误。

import React, { useEffect, useRef, useState, useCallback } from "react";
import { FlatList, Text, View } from "react-native";
import styles from "./WheelPickerStyles";

const WheelPicker = props => {
  const {
    height = 40,
    selectedTextStyle,
    unselectedTextStyle,
    infiniteScroll = true,
    selectedValue,
    onValueChange,
    data = []
  } = props;

  //makes array infinite(not truly infinite) (array.length >= 3 required)
  const [dataArray] = useState(
    infiniteScroll
      ? [...data.slice(data.length - 3), ...data, ...data.slice(0, 3)]
      : data
  );
  const [value, setValue] = useState(selectedValue);

  const flatListRef = useRef();

  useEffect(() => {
    if (!onValueChange) {
      return;
    }
    onValueChange(value);
  }, [value]);

  const onViewableItemsChanged = useCallback(({ viewableItems }) => {
    viewableItems[0]?.item && setValue(viewableItems[0].item.value);

    if (infiniteScroll) {
      if (viewableItems[0]?.index && viewableItems[0].index <= 2) {
        flatListRef.current.scrollToIndex({
          animated: false,
          index: dataArray.length - 4
        });
      } else if (
        viewableItems[0]?.index &&
        viewableItems[0].index >= dataArray.length - 2
      ) {
        flatListRef.current.scrollToIndex({
          animated: false,
          index: 4
        });
      }
    }
  }, []);
  const viewabilityConfigCallbackPairs = useRef([
    {
      viewabilityConfig: { viewAreaCoveragePercentThreshold: 50 },
      onViewableItemsChanged: onViewableItemsChanged
    }
  ]);

  return (
    <View style={styles.container}>
      <View style={{ width: height * 1.2, height: height }}>
        <FlatList
          data={dataArray}
          pagingEnabled
          initialScrollIndex={
            infiniteScroll ? selectedValue + 3 : selectedValue
          }
          getItemLayout={(data, index) => ({
            length: 40,
            offset: 40 * index,
            index
          })}
          ref={flatListRef}
          style={styles.flatlistStyle}
          showsVerticalScrollIndicator={false}
          snapToAlignment="center"
          snapToInterval={height}
          scrollEventThrottle={16}
          decelerationRate="normal"
          viewabilityConfigCallbackPairs={
            viewabilityConfigCallbackPairs?.current
          }
          keyExtractor={(_, index) => index.toString()}
          renderItem={({ item }) => {
            return (
              <View style={[styles.contentContainer, { height: height }]}>
                {item.value === value ? (
                  <Text style={[styles.textStyle, selectedTextStyle]}>
                    {item.label}
                  </Text>
                ) : (
                  <Text
                    style={[
                      styles.unselectedText,
                      styles.textStyle,
                      unselectedTextStyle
                    ]}
                  >
                    {item.label}
                  </Text>
                )}
              </View>
            );
          }}
        />
      </View>
    </View>
  );
};

export default WheelPicker;

还有你这样称呼它~

const HOURS = Array(12)
    .fill(0)
    .map((_, index) => {
      return {
        value: index,
        label: index < 10 ? "0" + index : index
      };
    });
    
<WheelPicker
            data={HOURS}
            height={ITEM_HEIGHT}
            infiniteScroll={true}
            onValueChange={val => {
              onValueChange({ ...value, hour: val });
            }}
            selectedValue={hour}
            selectedTextStyle={styles.selectedTextStyle}
            unselectedTextStyle={styles.unselectedTextStyle}
          />

Fixing the bugs in David Scholz's answer.

import React, { useEffect, useRef, useState, useCallback } from "react";
import { FlatList, Text, View } from "react-native";
import styles from "./WheelPickerStyles";

const WheelPicker = props => {
  const {
    height = 40,
    selectedTextStyle,
    unselectedTextStyle,
    infiniteScroll = true,
    selectedValue,
    onValueChange,
    data = []
  } = props;

  //makes array infinite(not truly infinite) (array.length >= 3 required)
  const [dataArray] = useState(
    infiniteScroll
      ? [...data.slice(data.length - 3), ...data, ...data.slice(0, 3)]
      : data
  );
  const [value, setValue] = useState(selectedValue);

  const flatListRef = useRef();

  useEffect(() => {
    if (!onValueChange) {
      return;
    }
    onValueChange(value);
  }, [value]);

  const onViewableItemsChanged = useCallback(({ viewableItems }) => {
    viewableItems[0]?.item && setValue(viewableItems[0].item.value);

    if (infiniteScroll) {
      if (viewableItems[0]?.index && viewableItems[0].index <= 2) {
        flatListRef.current.scrollToIndex({
          animated: false,
          index: dataArray.length - 4
        });
      } else if (
        viewableItems[0]?.index &&
        viewableItems[0].index >= dataArray.length - 2
      ) {
        flatListRef.current.scrollToIndex({
          animated: false,
          index: 4
        });
      }
    }
  }, []);
  const viewabilityConfigCallbackPairs = useRef([
    {
      viewabilityConfig: { viewAreaCoveragePercentThreshold: 50 },
      onViewableItemsChanged: onViewableItemsChanged
    }
  ]);

  return (
    <View style={styles.container}>
      <View style={{ width: height * 1.2, height: height }}>
        <FlatList
          data={dataArray}
          pagingEnabled
          initialScrollIndex={
            infiniteScroll ? selectedValue + 3 : selectedValue
          }
          getItemLayout={(data, index) => ({
            length: 40,
            offset: 40 * index,
            index
          })}
          ref={flatListRef}
          style={styles.flatlistStyle}
          showsVerticalScrollIndicator={false}
          snapToAlignment="center"
          snapToInterval={height}
          scrollEventThrottle={16}
          decelerationRate="normal"
          viewabilityConfigCallbackPairs={
            viewabilityConfigCallbackPairs?.current
          }
          keyExtractor={(_, index) => index.toString()}
          renderItem={({ item }) => {
            return (
              <View style={[styles.contentContainer, { height: height }]}>
                {item.value === value ? (
                  <Text style={[styles.textStyle, selectedTextStyle]}>
                    {item.label}
                  </Text>
                ) : (
                  <Text
                    style={[
                      styles.unselectedText,
                      styles.textStyle,
                      unselectedTextStyle
                    ]}
                  >
                    {item.label}
                  </Text>
                )}
              </View>
            );
          }}
        />
      </View>
    </View>
  );
};

export default WheelPicker;

And you call it like this~

const HOURS = Array(12)
    .fill(0)
    .map((_, index) => {
      return {
        value: index,
        label: index < 10 ? "0" + index : index
      };
    });
    
<WheelPicker
            data={HOURS}
            height={ITEM_HEIGHT}
            infiniteScroll={true}
            onValueChange={val => {
              onValueChange({ ...value, hour: val });
            }}
            selectedValue={hour}
            selectedTextStyle={styles.selectedTextStyle}
            unselectedTextStyle={styles.unselectedTextStyle}
          />

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文