Skip to content

Commit

Permalink
Merge pull request #876 from thundersdata-frontend/rn-issue
Browse files Browse the repository at this point in the history
fix: 修复快速滑动时的bug以及title过长时折行的bug
  • Loading branch information
chj-damon authored May 30, 2024
2 parents 2727f60 + 1e09833 commit 46244c5
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 90 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-crews-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@td-design/react-native-picker': minor
---

fix: 修复快速滑动时的bug以及title过长时折行的bug
89 changes: 50 additions & 39 deletions packages/react-native-picker/src/components/WheelPicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import React, { useEffect, useMemo, useRef } from 'react';
import {
Animated,
FlatList,
ListRenderItemInfo,
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
View,
} from 'react-native';
import { Animated, FlatList, NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native';

import { Theme, useTheme } from '@td-design/react-native';
import { useMemoizedFn } from '@td-design/rn-hooks';

import { OptionItem, WheelPickerProps } from './type';
import { WheelPickerProps } from './type';
import WheelPickerItem from './WheelPickerItem';

export default function WheelPicker({
Expand All @@ -27,8 +19,9 @@ export default function WheelPicker({
onChange,
}: WheelPickerProps) {
const theme = useTheme<Theme>();
const signal = useRef(false);
const flatListRef = useRef<FlatList>(null);
const flag = useRef(false);

const scrollY = useRef(new Animated.Value(0)).current;

const containerHeight = 5 * itemHeight;
Expand All @@ -52,19 +45,39 @@ export default function WheelPicker({

const currentScrollIndex = Animated.add(Animated.divide(scrollY, itemHeight), 2);

const handleMomentumScrollBegin = useMemoizedFn(() => {
signal.current = false;
/**
* 惯性滚动结束时触发
*/
const handleMomentumScrollEnd = useMemoizedFn((event: NativeSyntheticEvent<NativeScrollEvent>) => {
handleScrollEnd(event.nativeEvent.contentOffset.y);
flag.current = false;
});

const handleMomentumScrollEnd = useMemoizedFn((event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (signal.current) return;
signal.current = true;
/**
* 拖动结束时触发,实测下来, handleDragEnd 一定会触发,但是 handleMomentumScrollEnd 不一定会触发
* 所以使用 setTimeout 来延迟执行 handleScrollEnd,确保在 handleMomentumScrollEnd 之后执行
*/
const handleDragEnd = useMemoizedFn((event: NativeSyntheticEvent<NativeScrollEvent>) => {
event.persist();

setTimeout(() => {
if (!flag.current) {
handleScrollEnd(event.nativeEvent.contentOffset.y);
}
}, 10);
});

const handleScrollEnd = useMemoizedFn((y: number) => {
// Due to list bounciness when scrolling to the start or the end of the list
// the offset might be negative or over the last item.
// We therefore clamp the offset to the supported range.
const offsetY = Math.min(itemHeight * (data.length - 1), Math.max(event.nativeEvent.contentOffset.y, 0));
const _index = Math.ceil(offsetY / itemHeight);
const offsetY = Math.min(itemHeight * (data.length - 1), Math.max(y, 0));

let _index = Math.floor(Math.floor(offsetY) / itemHeight);
const last = Math.floor(offsetY % itemHeight);
if (last > itemHeight / 2) {
_index += 1;
}

const currentItem = data[_index];
if (currentItem) {
Expand Down Expand Up @@ -101,20 +114,6 @@ export default function WheelPicker({
}, 100);
}, [selectedIndex]);

const renderItem = useMemoizedFn(({ item: option, index }: ListRenderItemInfo<OptionItem>) => {
return (
<WheelPickerItem
index={index}
option={option}
style={itemStyle}
textStyle={itemTextStyle}
height={itemHeight}
currentIndex={currentScrollIndex}
visibleRest={2}
/>
);
});

return (
<View style={[styles.container, containerStyle]}>
<View style={styles.selectedIndicator} />
Expand All @@ -125,13 +124,15 @@ export default function WheelPicker({
scrollEventThrottle={16}
centerContent
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], { useNativeDriver: true })}
// onMomentumScrollBegin={handleMomentumScrollBegin}
onScrollBeginDrag={handleMomentumScrollBegin}
// onMomentumScrollEnd={handleMomentumScrollEnd}
// onMomentumScrollEnd 是惯性滚动结束,但是有时候不会被触发,所以换成 onScrollEndDrag
onScrollEndDrag={handleMomentumScrollEnd}
onMomentumScrollBegin={() => {
flag.current = true;
}}
onMomentumScrollEnd={handleMomentumScrollEnd}
onScrollEndDrag={handleDragEnd}
snapToOffsets={offsets}
decelerationRate={'fast'}
decelerationRate={'normal'}
disableIntervalMomentum
initialScrollIndex={selectedIndex}
getItemLayout={(_, index) => ({
length: itemHeight,
offset: itemHeight * index,
Expand All @@ -140,7 +141,17 @@ export default function WheelPicker({
bounces={false}
data={paddedOptions}
keyExtractor={(_, index) => index.toString()}
renderItem={renderItem}
renderItem={({ item: option, index }) => (
<WheelPickerItem
index={index}
option={option}
style={itemStyle}
textStyle={itemTextStyle}
height={itemHeight}
currentIndex={currentScrollIndex}
visibleRest={2}
/>
)}
maxToRenderPerBatch={3}
initialNumToRender={2}
/>
Expand Down
34 changes: 15 additions & 19 deletions packages/react-native-picker/src/date-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ const DatePicker = forwardRef<DatePickerRef, DatePickerProps>((props, ref) => {
};
});

const styles = StyleSheet.create({
cancel: { width: '100%', justifyContent: 'center', alignItems: 'flex-start' },
submit: { width: '100%', justifyContent: 'center', alignItems: 'flex-end' },
});

const DatePickerComp = useMemo(() => {
if (!visible) return null;

Expand All @@ -81,25 +76,21 @@ const DatePicker = forwardRef<DatePickerRef, DatePickerProps>((props, ref) => {
paddingVertical="x3"
paddingHorizontal="x3"
>
<Flex.Item alignItems="flex-start">
<Pressable activeOpacity={activeOpacity} onPress={handleClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={handleClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
<Flex.Item alignItems="center">
<Text variant="p0" color="text">
{title}
</Text>
</Flex.Item>
<Flex.Item alignItems="flex-end">
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex>
{DatePickerComp}
</Modal>
Expand All @@ -109,3 +100,8 @@ const DatePicker = forwardRef<DatePickerRef, DatePickerProps>((props, ref) => {
});

export default DatePicker;

const styles = StyleSheet.create({
cancel: { justifyContent: 'center', alignItems: 'flex-start' },
submit: { justifyContent: 'center', alignItems: 'flex-end' },
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,21 @@ const Cascader = ({
paddingVertical="x3"
paddingHorizontal="x3"
>
<Flex.Item alignItems="flex-start">
<Pressable activeOpacity={activeOpacity} onPress={onClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={onClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
<Flex.Item alignItems="center">
<Text variant="p0" color="text">
{title}
</Text>
</Flex.Item>
<Flex.Item alignItems="flex-end">
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex>
{PickerComp}
</Modal>
Expand All @@ -87,8 +83,8 @@ const Cascader = ({
};

const styles = StyleSheet.create({
cancel: { width: '100%', justifyContent: 'center', alignItems: 'flex-start' },
submit: { width: '100%', justifyContent: 'center', alignItems: 'flex-end' },
cancel: { justifyContent: 'center', alignItems: 'flex-start' },
submit: { justifyContent: 'center', alignItems: 'flex-end' },
});

export default Cascader;
28 changes: 12 additions & 16 deletions packages/react-native-picker/src/picker/components/Normal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,21 @@ const NormalPicker: FC<PickerProps & ModalPickerProps> = props => {
backgroundColor="white"
paddingHorizontal="x3"
>
<Flex.Item alignItems="flex-start">
<Pressable activeOpacity={activeOpacity} onPress={handleClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={handleClose} style={styles.cancel}>
<Text variant="p0" color="primary200">
{cancelText}
</Text>
</Pressable>
<Flex.Item alignItems="center">
<Text variant="p0" color="text">
{title}
</Text>
</Flex.Item>
<Flex.Item alignItems="flex-end">
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex.Item>
<Pressable activeOpacity={activeOpacity} onPress={handleOk} style={styles.submit}>
<Text variant="p0" color="primary200">
{okText}
</Text>
</Pressable>
</Flex>
}
{PickerComp}
Expand All @@ -103,6 +99,6 @@ const NormalPicker: FC<PickerProps & ModalPickerProps> = props => {
export default NormalPicker;

const styles = StyleSheet.create({
cancel: { width: '100%', justifyContent: 'center', alignItems: 'flex-start' },
submit: { width: '100%', justifyContent: 'center', alignItems: 'flex-end' },
cancel: { justifyContent: 'center', alignItems: 'flex-start' },
submit: { justifyContent: 'center', alignItems: 'flex-end' },
});

0 comments on commit 46244c5

Please sign in to comment.