FlatList 无限循环 - React Native

问题描述 投票:0回答:4

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

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

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

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

enter image description here

react-native react-native-flatlist flatlist
4个回答
0
投票

我们可以调整 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>
  )

以上产生以下结果。

enter image description here


0
投票

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

反应本机选择器

npm 我反应原生选择器

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();

或者

反应本机轮子选择器

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

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

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

这是演示链接:

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

工作演示,可能会因为吃零食而有点延迟。 但有效: infinite Scroll Library Demo


0
投票

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


0
投票

修复 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}
          />

© www.soinside.com 2019 - 2024. All rights reserved.