我正在尝试制作一个在两个方向上无限滚动的平面列表。
已经有一个小的实现轮廓(https://snack.expo.dev/@slam_ua/flatlist-loop),但我似乎无法做一些事情:
下面我展示了我想要实现的结果的示例:
我们可以调整 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>
)
以上产生以下结果。
您可以通过在 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
onEndReached={()=>flatListRef.current.scrollToOffset({offset: 0,animated: true})}
修复 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}
/>