From 460aeb05c9dddb5635f0006f4dfd0cd317513d8a Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Wed, 27 Nov 2024 19:29:58 +0800 Subject: [PATCH 01/23] feat: update initialOffset --- .../components/react/mpx-picker-view-column.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index ff9b72490..4da7b850a 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -71,7 +71,7 @@ const _PickerViewColumn = forwardRef, const initialOffset = useMemo(() => ({ x: 0, y: itemRawH * initialIndex - }), [itemRawH]) + }), [itemRawH, initialIndex]) const snapToOffsets = useMemo( () => columnData.map((_, i) => i * itemRawH), @@ -124,14 +124,6 @@ const _PickerViewColumn = forwardRef, onLayout: onScrollViewLayout }) - const onContentSizeChange = (w: number, h: number) => { - scrollViewRef.current?.scrollTo({ - x: 0, - y: itemRawH * initialIndex, - animated: false - }) - } - const onItemLayout = (e: LayoutChangeEvent) => { const { height: rawH } = e.nativeEvent.layout if (rawH && itemRawH !== rawH) { @@ -257,7 +249,6 @@ const _PickerViewColumn = forwardRef, contentContainerStyle={contentContainerStyle} contentOffset={initialOffset} snapToOffsets={snapToOffsets} - onContentSizeChange={onContentSizeChange} onScroll={onScroll} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} From 41015c2e1749aae50b427f01e300a7f6326208e7 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Fri, 29 Nov 2024 17:03:09 +0800 Subject: [PATCH 02/23] feat: add mask & remove animated value --- .../react/mpx-picker-view-column.tsx | 110 +++++++----------- .../components/react/mpx-picker-view.tsx | 10 +- .../components/react/pickerViewMask.tsx | 52 +++++++++ ...ickerOverlay.tsx => pickerViewOverlay.tsx} | 8 +- 4 files changed, 100 insertions(+), 80 deletions(-) create mode 100644 packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx rename packages/webpack-plugin/lib/runtime/components/react/{pickerOverlay.tsx => pickerViewOverlay.tsx} (74%) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 4da7b850a..2edd9241c 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,16 +1,15 @@ -import { View, Animated, SafeAreaView, NativeScrollEvent, NativeSyntheticEvent, LayoutChangeEvent, ScrollView } from 'react-native' -import React, { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from 'react' +import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, View } from 'react-native' +import React, { forwardRef, useRef, useState, useMemo, useEffect } from 'react' import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, usePrevious } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' -import { createFaces } from './pickerFaces' -import PickerOverlay from './pickerOverlay' +import PickerOverlay from './pickerViewOverlay' +import PickerMask from './pickerViewMask' interface ColumnProps { children?: React.ReactNode columnData: React.ReactNode[] initialIndex: number - onColumnItemRawHChange: Function getInnerLayout: Function onSelectChange: Function style: { @@ -26,10 +25,7 @@ interface ColumnProps { columnIndex: number } -// 默认的单个选项高度 const DefaultPickerItemH = 36 -// 默认一屏可见选项个数 -const visibleCount = 5 const _PickerViewColumn = forwardRef, ColumnProps>((props: ColumnProps, ref) => { const { @@ -37,7 +33,6 @@ const _PickerViewColumn = forwardRef, columnIndex, initialIndex, onSelectChange, - onColumnItemRawHChange, getInnerLayout, style, wrapperStyle, @@ -60,7 +55,7 @@ const _PickerViewColumn = forwardRef, useNodesRef(props, ref, scrollViewRef, {}) const { height: pickerH, itemHeight = DefaultPickerItemH } = wrapperStyle - const [itemRawH, setItemRawH] = useState(0) // 单个选项真实渲染高度 + const [itemRawH, setItemRawH] = useState(0) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) const touching = useRef(false) const scrolling = useRef(false) @@ -71,20 +66,32 @@ const _PickerViewColumn = forwardRef, const initialOffset = useMemo(() => ({ x: 0, y: itemRawH * initialIndex - }), [itemRawH, initialIndex]) + }), [itemRawH]) const snapToOffsets = useMemo( () => columnData.map((_, i) => i * itemRawH), [columnData, itemRawH] ) + const paddingHeight = useMemo( + () => Math.round(pickerH - itemRawH) / 2, + [pickerH, itemRawH] + ) + const contentContainerStyle = useMemo(() => { - return [ - { - paddingVertical: Math.round(pickerH - itemRawH) / 2 - } - ] - }, [pickerH, itemRawH]) + return [{ paddingVertical: paddingHeight }] + }, [paddingHeight]) + + const onContentSizeChange = (_w: number, h: number) => { + if (itemRawH * initialIndex > h) { + return + } + scrollViewRef.current?.scrollTo({ + x: 0, + y: itemRawH * initialIndex, + animated: false + }) + } useEffect(() => { if ( @@ -128,7 +135,6 @@ const _PickerViewColumn = forwardRef, const { height: rawH } = e.nativeEvent.layout if (rawH && itemRawH !== rawH) { setItemRawH(rawH) - onColumnItemRawHChange(rawH) } } @@ -162,61 +168,18 @@ const _PickerViewColumn = forwardRef, } } - const offsetY = useRef(new Animated.Value(0)).current - - const onScroll = useMemo( - () => - Animated.event([{ nativeEvent: { contentOffset: { y: offsetY } } }], { - useNativeDriver: true - }), - [offsetY] - ) - - const faces = useMemo(() => createFaces(itemRawH, visibleCount), [itemRawH]) - - const getTransform = useCallback( - (index: number) => { - const inputRange = faces.map((f) => itemRawH * (index + f.index)) - return { - opacity: offsetY.interpolate({ - inputRange: inputRange, - outputRange: faces.map((x) => x.opacity), - extrapolate: 'clamp' - }), - rotateX: offsetY.interpolate({ - inputRange: inputRange, - outputRange: faces.map((x) => `${x.deg}deg`), - extrapolate: 'extend' - }), - translateY: offsetY.interpolate({ - inputRange: inputRange, - outputRange: faces.map((x) => x.offsetY), - extrapolate: 'extend' - }) - } - }, - [offsetY, faces, itemRawH] - ) - const renderInnerchild = () => columnData.map((item: React.ReactNode, index: number) => { const InnerProps = index === 0 ? { onLayout: onItemLayout } : {} const strKey = `picker-column-${columnIndex}-${index}` - const { opacity, rotateX, translateY } = getTransform(index) return ( - @@ -229,13 +192,13 @@ const _PickerViewColumn = forwardRef, textProps } )} - + ) }) const renderScollView = () => { return ( - , {...layoutProps} scrollEventThrottle={16} contentContainerStyle={contentContainerStyle} + onContentSizeChange={onContentSizeChange} contentOffset={initialOffset} snapToOffsets={snapToOffsets} - onScroll={onScroll} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchCancel} @@ -257,17 +220,28 @@ const _PickerViewColumn = forwardRef, onMomentumScrollEnd={onMomentumScrollEnd} > {renderInnerchild()} - + ) } const renderOverlay = () => ( - + + ) + + const renderMask = () => ( + ) return ( {renderScollView()} + {renderMask()} {renderOverlay()} ) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 67c6ed89c..9a8aee6af 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -1,5 +1,5 @@ import { View } from 'react-native' -import React, { forwardRef, useState, useRef } from 'react' +import React, { forwardRef, useRef } from 'react' import useInnerProps, { getCustomEvent } from './getInnerListeners' import useNodesRef, { HandlerRef } from './useNodesRef' import { @@ -76,7 +76,6 @@ const _PickerView = forwardRef, PickerViewProp // 微信设置到pick-view上上设置的normalStyle如border等需要转换成RN的style然后进行透传 const indicatorStyle = parseInlineStyle(props['indicator-style']) const { height: indicatorH, ...pickerOverlayStyle } = indicatorStyle - const [pickMaxH, setPickMaxH] = useState(0) const nodeRef = useRef(null) const cloneRef = useRef(null) const activeValueRef = useRef(value) @@ -100,12 +99,6 @@ const _PickerView = forwardRef, PickerViewProp const { textProps } = splitProps(props) const { textStyle } = splitStyle(normalStyle) - const onColumnItemRawHChange = (height: number) => { - if (height > pickMaxH) { - setPickMaxH(height) - } - } - const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 300) const onSelectChange = (columnIndex: number, selectedIndex: number) => { @@ -161,7 +154,6 @@ const _PickerView = forwardRef, PickerViewProp height: normalStyle?.height || 0, itemHeight: indicatorH || 0 }, - onColumnItemRawHChange, onSelectChange: onSelectChange.bind(null, index), initialIndex, pickerOverlayStyle diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx b/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx new file mode 100644 index 000000000..6a815c429 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' +import LinearGradient from 'react-native-linear-gradient' + +type OverlayProps = { + height: number; + itemHeight: number; + maskContainerStyle?: StyleProp; +}; + +const _PickerViewMask = ({ + height, + itemHeight, + maskContainerStyle +}: OverlayProps) => { + return ( + + + + + ) +} +const styles = StyleSheet.create({ + overlayContainer: { + ...StyleSheet.absoluteFillObject, + zIndex: 100 + } +}) + +_PickerViewMask.displayName = 'MpxPickerViewMask' +export default _PickerViewMask diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerOverlay.tsx b/packages/webpack-plugin/lib/runtime/components/react/pickerViewOverlay.tsx similarity index 74% rename from packages/webpack-plugin/lib/runtime/components/react/pickerOverlay.tsx rename to packages/webpack-plugin/lib/runtime/components/react/pickerViewOverlay.tsx index 4a11903d6..aeebec3f2 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerOverlay.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerViewOverlay.tsx @@ -7,7 +7,7 @@ type OverlayProps = { overlayContainerStyle?: StyleProp } -const Overlay = ({ itemHeight, overlayItemStyle, overlayContainerStyle }: OverlayProps) => { +const _PickerViewOverlay = ({ itemHeight, overlayItemStyle, overlayContainerStyle }: OverlayProps) => { return ( @@ -19,7 +19,8 @@ const styles = StyleSheet.create({ overlayContainer: { ...StyleSheet.absoluteFillObject, justifyContent: 'center', - alignItems: 'center' + alignItems: 'center', + zIndex: 200 }, selection: { borderTopWidth: 1, @@ -29,4 +30,5 @@ const styles = StyleSheet.create({ } }) -export default React.memo(Overlay) +_PickerViewOverlay.displayName = 'MpxPickerViewOverlay' +export default _PickerViewOverlay From 81d394c4b4fd1df4dd52a3a1de912f75da55d883 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Fri, 29 Nov 2024 17:34:02 +0800 Subject: [PATCH 03/23] feat: support mask-style prop --- .../runtime/components/react/mpx-picker-view-column.tsx | 3 +++ .../lib/runtime/components/react/mpx-picker-view.tsx | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 2edd9241c..1fbb58ba3 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -21,6 +21,7 @@ interface ColumnProps { height: number itemHeight: number } + pickerMaskStyle: Record pickerOverlayStyle: Record columnIndex: number } @@ -36,6 +37,7 @@ const _PickerViewColumn = forwardRef, getInnerLayout, style, wrapperStyle, + pickerMaskStyle, pickerOverlayStyle, 'enable-var': enableVar, 'external-var-context': externalVarContext @@ -235,6 +237,7 @@ const _PickerViewColumn = forwardRef, ) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 9a8aee6af..dcfcbece1 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -21,8 +21,9 @@ import type { AnyFunc } from './types/common' * ✘ bindpickend * ✘ mask-class * ✔ indicator-style: 优先级indicator-style.height > pick-view-column中的子元素设置的height + * WebView Only: * ✘ indicator-class - * ✘ mask-style + * ✔ mask-style * ✘ immediate-change */ @@ -36,6 +37,7 @@ interface PickerViewProps { [key: string]: any } 'indicator-style'?: string + 'mask-style'?: string 'enable-var': boolean 'external-var-context'?: Record, 'enable-offset': boolean @@ -75,6 +77,7 @@ const _PickerView = forwardRef, PickerViewProp // indicatorStyle 需要转换为rn的style // 微信设置到pick-view上上设置的normalStyle如border等需要转换成RN的style然后进行透传 const indicatorStyle = parseInlineStyle(props['indicator-style']) + const pickerMaskStyle = parseInlineStyle(props['mask-style']) const { height: indicatorH, ...pickerOverlayStyle } = indicatorStyle const nodeRef = useRef(null) const cloneRef = useRef(null) @@ -156,7 +159,8 @@ const _PickerView = forwardRef, PickerViewProp }, onSelectChange: onSelectChange.bind(null, index), initialIndex, - pickerOverlayStyle + pickerOverlayStyle, + pickerMaskStyle }, extraProps ) From 10ebd1a6fdccbb6b3bd02105900248ba9890add6 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Fri, 29 Nov 2024 18:39:57 +0800 Subject: [PATCH 04/23] fix: default picker item height --- .../react/mpx-picker-view-column.tsx | 9 ++--- .../components/react/mpx-picker-view.tsx | 6 ++-- .../components/react/pickerViewMask.tsx | 36 ++++--------------- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 1fbb58ba3..919ba27f4 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -26,8 +26,6 @@ interface ColumnProps { columnIndex: number } -const DefaultPickerItemH = 36 - const _PickerViewColumn = forwardRef, ColumnProps>((props: ColumnProps, ref) => { const { columnData, @@ -56,8 +54,8 @@ const _PickerViewColumn = forwardRef, const scrollViewRef = useRef(null) useNodesRef(props, ref, scrollViewRef, {}) - const { height: pickerH, itemHeight = DefaultPickerItemH } = wrapperStyle - const [itemRawH, setItemRawH] = useState(0) + const { height: pickerH, itemHeight } = wrapperStyle + const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) const touching = useRef(false) const scrolling = useRef(false) @@ -180,7 +178,7 @@ const _PickerViewColumn = forwardRef, {...InnerProps} style={[ { - height: itemHeight || DefaultPickerItemH, + height: itemHeight, width: '100%' } ]} @@ -235,7 +233,6 @@ const _PickerViewColumn = forwardRef, const renderMask = () => ( diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index dcfcbece1..28385b5a6 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -64,6 +64,8 @@ const styles: { [key: string]: Object } = { } } +const DefaultPickerItemH = 36 + const _PickerView = forwardRef, PickerViewProps>((props: PickerViewProps, ref) => { const { children, @@ -154,8 +156,8 @@ const _PickerView = forwardRef, PickerViewProp columnIndex: index, key: `pick-view-${index}`, wrapperStyle: { - height: normalStyle?.height || 0, - itemHeight: indicatorH || 0 + height: normalStyle?.height || DefaultPickerItemH, + itemHeight: indicatorH || DefaultPickerItemH }, onSelectChange: onSelectChange.bind(null, index), initialIndex, diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx b/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx index 6a815c429..3c8773fc3 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerViewMask.tsx @@ -3,41 +3,19 @@ import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' import LinearGradient from 'react-native-linear-gradient' type OverlayProps = { - height: number; - itemHeight: number; - maskContainerStyle?: StyleProp; -}; + itemHeight: number + maskContainerStyle?: StyleProp +} const _PickerViewMask = ({ - height, itemHeight, maskContainerStyle }: OverlayProps) => { return ( - - - + + + + ) } From 1b897d7b600e13537b0f2e94bd8eea10dfb93b18 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Mon, 2 Dec 2024 20:08:21 +0800 Subject: [PATCH 05/23] fix: inherit font-related styles from picker-view --- .../react/mpx-picker-view-column.tsx | 57 ++++++++++--------- .../components/react/mpx-picker-view.tsx | 7 +-- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 919ba27f4..4b77bc95f 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,7 +1,7 @@ import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, View } from 'react-native' -import React, { forwardRef, useRef, useState, useMemo, useEffect } from 'react' -import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, usePrevious } from './utils' +import React, { forwardRef, useRef, useState, useMemo, useEffect, cloneElement } from 'react' +import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, extendObject, wrapChildren } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' import PickerOverlay from './pickerViewOverlay' import PickerMask from './pickerViewMask' @@ -9,6 +9,7 @@ import PickerMask from './pickerViewMask' interface ColumnProps { children?: React.ReactNode columnData: React.ReactNode[] + columnStyle: Record initialIndex: number getInnerLayout: Function onSelectChange: Function @@ -30,6 +31,7 @@ const _PickerViewColumn = forwardRef, const { columnData, columnIndex, + columnStyle, initialIndex, onSelectChange, getInnerLayout, @@ -49,7 +51,8 @@ const _PickerViewColumn = forwardRef, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext }) - const { textStyle } = splitStyle(normalStyle) + const { textStyle: textStyleFromParent = {} } = splitStyle(columnStyle) + const { textStyle = {} } = splitStyle(normalStyle) const { textProps } = splitProps(props) const scrollViewRef = useRef(null) useNodesRef(props, ref, scrollViewRef, {}) @@ -169,30 +172,29 @@ const _PickerViewColumn = forwardRef, } const renderInnerchild = () => - columnData.map((item: React.ReactNode, index: number) => { - const InnerProps = index === 0 ? { onLayout: onItemLayout } : {} - const strKey = `picker-column-${columnIndex}-${index}` - return ( - - {wrapChildren( - { children: item }, - { - hasVarDec, - varContext: varContextRef.current, - textStyle, - textProps - } - )} - + columnData.map((item: React.ReactElement, index: number) => { + const restProps = index === 0 ? { onLayout: onItemLayout } : {} + const itemProps = extendObject( + { + key: `picker-column-item-${index}`, + style: extendObject( + { height: itemHeight, width: '100%' }, + textStyleFromParent, + textStyle, + item.props.style + ) + }, + restProps + ) + const realItem = cloneElement(item, itemProps) + return wrapChildren( + { children: realItem }, + { + hasVarDec, + varContext: varContextRef.current, + textStyle, + textProps + } ) }) @@ -208,6 +210,7 @@ const _PickerViewColumn = forwardRef, showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} {...layoutProps} + decelerationRate="fast" scrollEventThrottle={16} contentContainerStyle={contentContainerStyle} onContentSizeChange={onContentSizeChange} diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 28385b5a6..0a3c51c4d 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -104,7 +104,7 @@ const _PickerView = forwardRef, PickerViewProp const { textProps } = splitProps(props) const { textStyle } = splitStyle(normalStyle) - const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 300) + const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 200) const onSelectChange = (columnIndex: number, selectedIndex: number) => { bindchangeDebounce.clear() @@ -146,7 +146,6 @@ const _PickerView = forwardRef, PickerViewProp ) const renderColumn = (child: React.ReactElement, index: number, columnData: React.ReactNode[], initialIndex: number) => { - const extraProps = {} const childProps = child?.props || {} const wrappedProps = extendObject( childProps, @@ -159,12 +158,12 @@ const _PickerView = forwardRef, PickerViewProp height: normalStyle?.height || DefaultPickerItemH, itemHeight: indicatorH || DefaultPickerItemH }, + columnStyle: normalStyle, onSelectChange: onSelectChange.bind(null, index), initialIndex, pickerOverlayStyle, pickerMaskStyle - }, - extraProps + } ) const realElement = React.cloneElement(child, wrappedProps) return wrapChildren( From 4899726cb07310e8b7749f38318f9188a4863638 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Tue, 3 Dec 2024 19:20:08 +0800 Subject: [PATCH 06/23] fix: integerize ScrollView width to prevent snapToOffsets failure --- .../react/mpx-picker-view-column.tsx | 44 ++++++++++++------- .../components/react/mpx-picker-view.tsx | 2 +- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 4b77bc95f..eb3c66371 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,5 +1,5 @@ -import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, View } from 'react-native' +import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import React, { forwardRef, useRef, useState, useMemo, useEffect, cloneElement } from 'react' import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, extendObject, wrapChildren } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' @@ -58,6 +58,7 @@ const _PickerViewColumn = forwardRef, useNodesRef(props, ref, scrollViewRef, {}) const { height: pickerH, itemHeight } = wrapperStyle + const [scrollViewWidth, setScrollViewWidth] = useState('100%') const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) const touching = useRef(false) @@ -85,17 +86,6 @@ const _PickerViewColumn = forwardRef, return [{ paddingVertical: paddingHeight }] }, [paddingHeight]) - const onContentSizeChange = (_w: number, h: number) => { - if (itemRawH * initialIndex > h) { - return - } - scrollViewRef.current?.scrollTo({ - x: 0, - y: itemRawH * initialIndex, - animated: false - }) - } - useEffect(() => { if ( !scrollViewRef.current || @@ -118,7 +108,7 @@ const _PickerViewColumn = forwardRef, }) }, [itemRawH, initialIndex]) - const onScrollViewLayout = () => { + const _onLayout = () => { getInnerLayout && getInnerLayout(layoutRef) } @@ -131,9 +121,23 @@ const _PickerViewColumn = forwardRef, setWidth, setHeight, nodeRef: scrollViewRef, - onLayout: onScrollViewLayout + onLayout: _onLayout }) + const onScrollViewLayout = (e: LayoutChangeEvent) => { + const widthInt = Math.round(e.nativeEvent.layout.width) + if (widthInt !== scrollViewWidth) { + setScrollViewWidth(widthInt) + } + } + + const onContentSizeChange = (_w: number, h: number) => { + const y = itemRawH * initialIndex + if (y <= h) { + scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) + } + } + const onItemLayout = (e: LayoutChangeEvent) => { const { height: rawH } = e.nativeEvent.layout if (rawH && itemRawH !== rawH) { @@ -210,17 +214,19 @@ const _PickerViewColumn = forwardRef, showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} {...layoutProps} + style={[{ width: scrollViewWidth }]} decelerationRate="fast" scrollEventThrottle={16} - contentContainerStyle={contentContainerStyle} - onContentSizeChange={onContentSizeChange} contentOffset={initialOffset} snapToOffsets={snapToOffsets} + onLayout={onScrollViewLayout} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchCancel} onMomentumScrollBegin={onMomentumScrollBegin} onMomentumScrollEnd={onMomentumScrollEnd} + onContentSizeChange={onContentSizeChange} + contentContainerStyle={contentContainerStyle} > {renderInnerchild()} @@ -242,7 +248,7 @@ const _PickerViewColumn = forwardRef, ) return ( - + {renderScollView()} {renderMask()} {renderOverlay()} @@ -250,5 +256,9 @@ const _PickerViewColumn = forwardRef, ) }) +const styles = StyleSheet.create({ + wrapper: { display: 'flex', flex: 1 } +}) + _PickerViewColumn.displayName = 'MpxPickerViewColumn' export default _PickerViewColumn diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 0a3c51c4d..3f4cd2605 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -104,7 +104,7 @@ const _PickerView = forwardRef, PickerViewProp const { textProps } = splitProps(props) const { textStyle } = splitStyle(normalStyle) - const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 200) + const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 10) const onSelectChange = (columnIndex: number, selectedIndex: number) => { bindchangeDebounce.clear() From c4d3dd3959923d501248bdad75a46292768705bf Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Wed, 4 Dec 2024 14:23:15 +0800 Subject: [PATCH 07/23] perf: remove debounce --- .../runtime/components/react/mpx-picker-view.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 3f4cd2605..d69923b0c 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -9,8 +9,6 @@ import { wrapChildren, parseInlineStyle, useTransformStyle, - useDebounceCallback, - useStableCallback, extendObject } from './utils' import type { AnyFunc } from './types/common' @@ -29,8 +27,6 @@ import type { AnyFunc } from './types/common' interface PickerViewProps { children: React.ReactNode - // 初始的defaultValue数组中的数字依次表示 picker-view 内的 picker-view-column 选择的第几项(下标从 0 开始), - // 数字大于 picker-view-column 可选项长度时,选择最后一项。 value?: Array bindchange?: AnyFunc style: { @@ -75,9 +71,6 @@ const _PickerView = forwardRef, PickerViewProp 'enable-var': enableVar, 'external-var-context': externalVarContext } = props - - // indicatorStyle 需要转换为rn的style - // 微信设置到pick-view上上设置的normalStyle如border等需要转换成RN的style然后进行透传 const indicatorStyle = parseInlineStyle(props['indicator-style']) const pickerMaskStyle = parseInlineStyle(props['mask-style']) const { height: indicatorH, ...pickerOverlayStyle } = indicatorStyle @@ -96,7 +89,6 @@ const _PickerView = forwardRef, PickerViewProp } = useTransformStyle(style, { enableVar, externalVarContext }) useNodesRef(props, ref, nodeRef, {}) const { - // 存储layout布局信息 layoutRef, layoutProps, layoutStyle @@ -104,10 +96,7 @@ const _PickerView = forwardRef, PickerViewProp const { textProps } = splitProps(props) const { textStyle } = splitStyle(normalStyle) - const bindchangeDebounce = useDebounceCallback(useStableCallback(bindchange), 10) - const onSelectChange = (columnIndex: number, selectedIndex: number) => { - bindchangeDebounce.clear() const activeValue = activeValueRef.current activeValue[columnIndex] = selectedIndex const eventData = getCustomEvent( @@ -115,7 +104,7 @@ const _PickerView = forwardRef, PickerViewProp {}, { detail: { value: activeValue, source: 'change' }, layoutRef } ) - bindchangeDebounce(eventData) + bindchange?.(eventData) } const onInitialChange = (value: number[]) => { @@ -124,7 +113,7 @@ const _PickerView = forwardRef, PickerViewProp {}, { detail: { value, source: 'change' }, layoutRef } ) - bindchange?.(eventData) // immediate + bindchange?.(eventData) } const innerProps = useInnerProps( From e8936907d6c68625053af5a38987fa111fe4832f Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Wed, 4 Dec 2024 14:55:55 +0800 Subject: [PATCH 08/23] feat: update --- .../lib/runtime/components/react/mpx-picker-view-column.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index eb3c66371..20da12d38 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -125,8 +125,9 @@ const _PickerViewColumn = forwardRef, }) const onScrollViewLayout = (e: LayoutChangeEvent) => { - const widthInt = Math.round(e.nativeEvent.layout.width) - if (widthInt !== scrollViewWidth) { + const width = e.nativeEvent.layout.width + const widthInt = Math.round(width) + if (width !== widthInt && widthInt !== scrollViewWidth) { setScrollViewWidth(widthInt) } } From bc3b8093a24c9fb9181695f19d9ab49e3f480617 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Wed, 4 Dec 2024 14:58:54 +0800 Subject: [PATCH 09/23] feat: update --- .../lib/runtime/components/react/mpx-picker-view-column.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 20da12d38..79d250559 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -125,7 +125,7 @@ const _PickerViewColumn = forwardRef, }) const onScrollViewLayout = (e: LayoutChangeEvent) => { - const width = e.nativeEvent.layout.width + const { width } = e.nativeEvent.layout const widthInt = Math.round(width) if (width !== widthInt && widthInt !== scrollViewWidth) { setScrollViewWidth(widthInt) From 791488c4f66d008b1a823cc3e9d3a237184a52ea Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Sat, 7 Dec 2024 15:16:27 +0800 Subject: [PATCH 10/23] fix: add column normal style --- .../lib/runtime/components/react/mpx-picker-view-column.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 79d250559..82663f0a8 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -249,7 +249,7 @@ const _PickerViewColumn = forwardRef, ) return ( - + {renderScollView()} {renderMask()} {renderOverlay()} From 08efcef3a263627a500145f1caccd4423d030934 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Thu, 12 Dec 2024 20:37:59 +0800 Subject: [PATCH 11/23] fix: scrollTo animate --- .../components/react/mpx-picker-view-column.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 82663f0a8..5b7c5b4e6 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,4 +1,3 @@ - import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import React, { forwardRef, useRef, useState, useMemo, useEffect, cloneElement } from 'react' import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, extendObject, wrapChildren } from './utils' @@ -55,7 +54,10 @@ const _PickerViewColumn = forwardRef, const { textStyle = {} } = splitStyle(normalStyle) const { textProps } = splitProps(props) const scrollViewRef = useRef(null) - useNodesRef(props, ref, scrollViewRef, {}) + + useNodesRef(props, ref, scrollViewRef, { + style: normalStyle + }) const { height: pickerH, itemHeight } = wrapperStyle const [scrollViewWidth, setScrollViewWidth] = useState('100%') @@ -74,7 +76,7 @@ const _PickerViewColumn = forwardRef, const snapToOffsets = useMemo( () => columnData.map((_, i) => i * itemRawH), - [columnData, itemRawH] + [maxIndex, itemRawH] ) const paddingHeight = useMemo( @@ -104,7 +106,7 @@ const _PickerViewColumn = forwardRef, scrollViewRef.current.scrollTo({ x: 0, y: itemRawH * initialIndex, - animated: false + animated: true }) }, [itemRawH, initialIndex]) @@ -135,7 +137,7 @@ const _PickerViewColumn = forwardRef, const onContentSizeChange = (_w: number, h: number) => { const y = itemRawH * initialIndex if (y <= h) { - scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) + scrollViewRef.current?.scrollTo({ x: 0, y, animated: true }) } } From 99ffac9b68c50d1609d62946d1db150a1e66cec1 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Mon, 16 Dec 2024 15:59:57 +0800 Subject: [PATCH 12/23] feat: update --- .../lib/runtime/components/react/mpx-picker-view-column.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 5b7c5b4e6..18ffcef56 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -106,7 +106,7 @@ const _PickerViewColumn = forwardRef, scrollViewRef.current.scrollTo({ x: 0, y: itemRawH * initialIndex, - animated: true + animated: false }) }, [itemRawH, initialIndex]) @@ -137,7 +137,7 @@ const _PickerViewColumn = forwardRef, const onContentSizeChange = (_w: number, h: number) => { const y = itemRawH * initialIndex if (y <= h) { - scrollViewRef.current?.scrollTo({ x: 0, y, animated: true }) + scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) } } From 050475ff36078936c4c912780581bcad3ca59a45 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Tue, 17 Dec 2024 20:00:06 +0800 Subject: [PATCH 13/23] refactor: reanimated --- .../react/mpx-picker-view-column-item.tsx | 85 ++++++++++ .../react/mpx-picker-view-column.tsx | 154 ++++++++---------- .../components/react/mpx-picker-view.tsx | 26 ++- .../components/react/pickerVIewContext.ts | 18 ++ 4 files changed, 192 insertions(+), 91 deletions(-) create mode 100644 packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx create mode 100644 packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx new file mode 100644 index 000000000..4ee6eead4 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx @@ -0,0 +1,85 @@ +import React, { memo, useEffect } from 'react' +import { LayoutChangeEvent } from 'react-native' +import Reanimated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated' +import { wrapChildren, extendObject } from './utils' +import { createFaces } from './pickerFaces' +import { usePickerViewColumnAnimationContext } from './pickerVIewContext' + +interface PickerColumnItemProps { + item: React.ReactElement + index: number + itemHeight: number + textStyleFromParent: Record + textStyle: Record + hasVarDec: boolean + varContext: Record + visibleCount: number + textProps?: any + onItemLayout?: (e: LayoutChangeEvent) => void +} + +const _PickerViewColumnItem: React.FC = ({ + item, + index, + itemHeight, + textStyleFromParent, + textStyle, + hasVarDec, + varContext, + textProps, + visibleCount, + onItemLayout +}) => { + const offsetYShared = usePickerViewColumnAnimationContext() + const facesShared = useSharedValue(createFaces(itemHeight, visibleCount)) + // const indexShared = useSharedValue(index) + + useEffect(() => { + facesShared.value = createFaces(itemHeight, visibleCount) + }, [itemHeight]) + + const animatedStyles = useAnimatedStyle(() => { + const inputRange = facesShared.value.map((f) => itemHeight * (index + f.index)) + return { + transform: [ + { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.EXTEND) + 'deg' }, + { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) } + ] + } + }) + + const strKey = `picker-column-item-${index}` + const restProps = index === 0 ? { onLayout: onItemLayout } : {} + const itemProps = extendObject( + { + style: extendObject( + { height: itemHeight, width: '100%' }, + textStyleFromParent, + textStyle, + item.props.style + ) + }, + restProps + ) + const realItem = React.cloneElement(item, itemProps) + + return ( + + {wrapChildren( + { children: realItem }, + { + hasVarDec, + varContext, + textStyle, + textProps + } + )} + + ) +} + +_PickerViewColumnItem.displayName = 'MpxPickerViewColumnItem' +export default _PickerViewColumnItem diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 18ffcef56..d00d86237 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,16 +1,18 @@ +import React, { forwardRef, useRef, useState, useMemo, useEffect } from 'react' import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' -import React, { forwardRef, useRef, useState, useMemo, useEffect, cloneElement } from 'react' -import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, extendObject, wrapChildren } from './utils' +import Reanimated, { AnimatedRef, scrollTo, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useScrollViewOffset, useSharedValue } from 'react-native-reanimated' +import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' import PickerOverlay from './pickerViewOverlay' import PickerMask from './pickerViewMask' +import MpxPickerVIewColumnItem from './mpx-picker-view-column-item' +import { PickerViewColumnAnimationContext } from './pickerVIewContext' interface ColumnProps { children?: React.ReactNode columnData: React.ReactNode[] columnStyle: Record initialIndex: number - getInnerLayout: Function onSelectChange: Function style: { [key: string]: any @@ -26,6 +28,8 @@ interface ColumnProps { columnIndex: number } +const visibleCount = 5 + const _PickerViewColumn = forwardRef, ColumnProps>((props: ColumnProps, ref) => { const { columnData, @@ -33,7 +37,6 @@ const _PickerViewColumn = forwardRef, columnStyle, initialIndex, onSelectChange, - getInnerLayout, style, wrapperStyle, pickerMaskStyle, @@ -53,9 +56,11 @@ const _PickerViewColumn = forwardRef, const { textStyle: textStyleFromParent = {} } = splitStyle(columnStyle) const { textStyle = {} } = splitStyle(normalStyle) const { textProps } = splitProps(props) - const scrollViewRef = useRef(null) + // const scrollViewRef = useRef(null) + const scrollViewRef = useAnimatedRef() + const offsetYShared = useScrollViewOffset(scrollViewRef as AnimatedRef) - useNodesRef(props, ref, scrollViewRef, { + useNodesRef(props, ref, scrollViewRef as AnimatedRef, { style: normalStyle }) @@ -69,18 +74,28 @@ const _PickerViewColumn = forwardRef, const prevIndex = usePrevious(initialIndex) const prevMaxIndex = usePrevious(maxIndex) - const initialOffset = useMemo(() => ({ - x: 0, - y: itemRawH * initialIndex - }), [itemRawH]) + const { + layoutProps + } = useLayout({ + props, + hasSelfPercent, + setWidth, + setHeight, + nodeRef: scrollViewRef + }) + + // const initialOffset = useMemo(() => ({ + // x: 0, + // y: itemRawH * initialIndex + // }), [itemRawH]) const snapToOffsets = useMemo( () => columnData.map((_, i) => i * itemRawH), - [maxIndex, itemRawH] + [columnData, itemRawH] ) const paddingHeight = useMemo( - () => Math.round(pickerH - itemRawH) / 2, + () => Math.round((pickerH - itemRawH) / 2), [pickerH, itemRawH] ) @@ -101,31 +116,16 @@ const _PickerViewColumn = forwardRef, ) { return } - + setTimeout(() => { + scrollViewRef.current?.scrollTo({ + x: 0, + y: itemRawH * initialIndex, + animated: false + }) + }, 0) activeIndex.current = initialIndex - scrollViewRef.current.scrollTo({ - x: 0, - y: itemRawH * initialIndex, - animated: false - }) }, [itemRawH, initialIndex]) - const _onLayout = () => { - getInnerLayout && getInnerLayout(layoutRef) - } - - const { - layoutRef, - layoutProps - } = useLayout({ - props, - hasSelfPercent, - setWidth, - setHeight, - nodeRef: scrollViewRef, - onLayout: _onLayout - }) - const onScrollViewLayout = (e: LayoutChangeEvent) => { const { width } = e.nativeEvent.layout const widthInt = Math.round(width) @@ -180,59 +180,49 @@ const _PickerViewColumn = forwardRef, const renderInnerchild = () => columnData.map((item: React.ReactElement, index: number) => { - const restProps = index === 0 ? { onLayout: onItemLayout } : {} - const itemProps = extendObject( - { - key: `picker-column-item-${index}`, - style: extendObject( - { height: itemHeight, width: '100%' }, - textStyleFromParent, - textStyle, - item.props.style - ) - }, - restProps - ) - const realItem = cloneElement(item, itemProps) - return wrapChildren( - { children: realItem }, - { - hasVarDec, - varContext: varContextRef.current, - textStyle, - textProps - } + return ( + ) }) const renderScollView = () => { return ( - - {renderInnerchild()} - + + + {renderInnerchild()} + + ) } diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx index 4465b5482..acf76e0c0 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx @@ -78,6 +78,7 @@ const _PickerView = forwardRef, PickerViewProp const cloneRef = useRef(null) const activeValueRef = useRef(value) activeValueRef.current = value.slice() + const snapActiveValueRef = useRef(null) const { normalStyle, @@ -109,15 +110,23 @@ const _PickerView = forwardRef, PickerViewProp { detail: { value: activeValue, source: 'change' }, layoutRef } ) bindchange?.(eventData) + snapActiveValueRef.current = activeValueRef.current } - const onInitialChange = (value: number[]) => { - const eventData = getCustomEvent( - 'change', - {}, - { detail: { value, source: 'change' }, layoutRef } - ) - bindchange?.(eventData) + const hasDiff = (a: number[] = [], b: number[]) => { + return a.some((v, i) => v !== b[i]) + } + + const onInitialChange = (isInvalid: boolean, value: number[]) => { + if (isInvalid || !snapActiveValueRef.current || hasDiff(snapActiveValueRef.current, value)) { + const eventData = getCustomEvent( + 'change', + {}, + { detail: { value, source: 'change' }, layoutRef } + ) + bindchange?.(eventData) + snapActiveValueRef.current = value.slice() + } } const innerProps = useInnerProps( @@ -201,7 +210,7 @@ const _PickerView = forwardRef, PickerViewProp validValue.push(validIndex) renderColumns.push(renderColumn(item, index, columnData, validIndex)) }) - isInvalid && onInitialChange(validValue) + onInitialChange(isInvalid, validValue) return renderColumns } @@ -213,5 +222,4 @@ const _PickerView = forwardRef, PickerViewProp }) _PickerView.displayName = 'MpxPickerView' - export default _PickerView diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts b/packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts new file mode 100644 index 000000000..6733ebb73 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerVIewContext.ts @@ -0,0 +1,18 @@ +import { createContext, useContext } from 'react' +import { SharedValue } from 'react-native-reanimated' + +type ContextValue = SharedValue + +export const PickerViewColumnAnimationContext = createContext< + ContextValue | undefined +>(undefined) + +export const usePickerViewColumnAnimationContext = () => { + const value = useContext(PickerViewColumnAnimationContext) + if (value === undefined) { + throw new Error( + 'usePickerViewColumnAnimationContext must be called from within PickerViewColumnAnimationContext.Provider!' + ) + } + return value +} From d03eaf4d4e8cf9153845c06ae2532529c56c22c7 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Tue, 17 Dec 2024 21:08:49 +0800 Subject: [PATCH 14/23] fix: snapToOffsets --- .../components/react/mpx-picker-view-column.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index d00d86237..469c17b55 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,6 +1,6 @@ import React, { forwardRef, useRef, useState, useMemo, useEffect } from 'react' import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' -import Reanimated, { AnimatedRef, scrollTo, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useScrollViewOffset, useSharedValue } from 'react-native-reanimated' +import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' import PickerOverlay from './pickerViewOverlay' @@ -68,6 +68,7 @@ const _PickerViewColumn = forwardRef, const [scrollViewWidth, setScrollViewWidth] = useState('100%') const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) + const maxScrollViewWidth = useRef(-1) const touching = useRef(false) const scrolling = useRef(false) const activeIndex = useRef(initialIndex) @@ -128,8 +129,15 @@ const _PickerViewColumn = forwardRef, const onScrollViewLayout = (e: LayoutChangeEvent) => { const { width } = e.nativeEvent.layout - const widthInt = Math.round(width) - if (width !== widthInt && widthInt !== scrollViewWidth) { + const widthInt = Math.ceil(width) + if (widthInt !== scrollViewWidth) { + const maxW = maxScrollViewWidth.current + if (maxW !== -1 && widthInt > maxW) { + return + } + if (maxW === -1) { + maxScrollViewWidth.current = Math.ceil(widthInt * 1.5) + } setScrollViewWidth(widthInt) } } From 3a31d170b13719723c1a6c0c1cd663640398c3ac Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Wed, 18 Dec 2024 19:28:01 +0800 Subject: [PATCH 15/23] feat: update style && add vibrate --- .../react/mpx-picker-view-column-item.tsx | 5 +-- .../react/mpx-picker-view-column.tsx | 33 ++++++++++++++++++- .../runtime/components/react/pickerFaces.ts | 22 +++++++++---- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx index 4ee6eead4..6b4e2344c 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx @@ -32,7 +32,6 @@ const _PickerViewColumnItem: React.FC = ({ }) => { const offsetYShared = usePickerViewColumnAnimationContext() const facesShared = useSharedValue(createFaces(itemHeight, visibleCount)) - // const indexShared = useSharedValue(index) useEffect(() => { facesShared.value = createFaces(itemHeight, visibleCount) @@ -41,9 +40,11 @@ const _PickerViewColumnItem: React.FC = ({ const animatedStyles = useAnimatedStyle(() => { const inputRange = facesShared.value.map((f) => itemHeight * (index + f.index)) return { + opacity: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.opacity), Extrapolation.CLAMP), transform: [ { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.EXTEND) + 'deg' }, - { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) } + { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) }, + { scale: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.scale), Extrapolation.EXTEND) } ] } }) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 469c17b55..666706c3e 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,6 +1,8 @@ -import React, { forwardRef, useRef, useState, useMemo, useEffect } from 'react' +import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react' import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' +// @ts-expect-error ignore +import { vibrateShort } from '@mpxjs/api-proxy' import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' import PickerOverlay from './pickerViewOverlay' @@ -69,6 +71,7 @@ const _PickerViewColumn = forwardRef, const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) const maxScrollViewWidth = useRef(-1) + const prevScrollingInfo = useRef({ index: initialIndex, y: 0 }) const touching = useRef(false) const scrolling = useRef(false) const activeIndex = useRef(initialIndex) @@ -104,6 +107,11 @@ const _PickerViewColumn = forwardRef, return [{ paddingVertical: paddingHeight }] }, [paddingHeight]) + const getIndex = useCallback((y: number) => { + const calc = Math.round(y / itemRawH) + return Math.max(0, Math.min(calc, maxIndex)) + }, [itemRawH, maxIndex]) + useEffect(() => { if ( !scrollViewRef.current || @@ -158,6 +166,10 @@ const _PickerViewColumn = forwardRef, const onTouchStart = () => { touching.current = true + prevScrollingInfo.current = { + index: activeIndex.current, + y: activeIndex.current * itemRawH + } } const onTouchEnd = () => { @@ -186,6 +198,23 @@ const _PickerViewColumn = forwardRef, } } + const onScroll = (e: NativeSyntheticEvent) => { + const { y } = e.nativeEvent.contentOffset + const { index: prevIndex, y: _y } = prevScrollingInfo.current + if (touching.current || scrolling.current) { + if (Math.abs(y - _y) >= itemRawH) { + const currentId = getIndex(y) + if (currentId !== prevIndex) { + prevScrollingInfo.current = { + index: currentId, + y: currentId * itemRawH + } + vibrateShort() + } + } + } + } + const renderInnerchild = () => columnData.map((item: React.ReactElement, index: number) => { return ( @@ -215,10 +244,12 @@ const _PickerViewColumn = forwardRef, removeClippedSubviews={false} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} + scrollEventThrottle={16} {...layoutProps} style={[{ width: scrollViewWidth }]} decelerationRate="fast" snapToOffsets={snapToOffsets} + onScroll={onScroll} onLayout={onScrollViewLayout} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts index f25dc0313..b5e852e89 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts @@ -8,6 +8,7 @@ export type Faces = { deg: number offsetY: number opacity: number + scale: number screenHeight: number } @@ -54,6 +55,9 @@ export const createFaces = ( for (let i = 0; i < index; i++) { offset += freeSpaces[i] } + if (index === 0) { + offset *= 0.6 + } return offset }) as unknown as T return [screenHeights, offsets] @@ -62,17 +66,21 @@ export const createFaces = ( const getOpacity = (index: number) => { const map: Record = { 0: 0, - 1: 0.2, - 2: 0.35, - 3: 0.45, - 4: 0.5 + 1: 0.8, + 2: 0.9 // 0.35 + // 3: 0.45, // 0.45 + // 4: 0.5 // 0.5 } - return map[index] ?? Math.min(1, map[4] + index * 0.5) + return map[index] ?? Math.min(1, map[2] + index * 0.05) } const degrees = getDegreesRelativeCenter() const [screenHeight, offsets] = getScreenHeightsAndOffsets(degrees) + const scale = (index: number) => { + return 0.92 - index * 0.1 + } + return [ // top items ...degrees @@ -82,13 +90,14 @@ export const createFaces = ( deg: degree, opacity: getOpacity(degrees.length - 1 - index), offsetY: -1 * offsets[index], + scale: scale(index), screenHeight: screenHeight[index] } }) .reverse(), // center item - { index: 0, deg: 0, opacity: 1, offsetY: 0, screenHeight: itemHeight }, + { index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1, screenHeight: itemHeight }, // bottom items ...degrees.map((degree, index) => { @@ -97,6 +106,7 @@ export const createFaces = ( deg: -1 * degree, opacity: getOpacity(degrees.length - 1 - index), offsetY: offsets[index], + scale: scale(index), screenHeight: screenHeight[index] } }) From ad235ac110f751f606478862caa4edebd1b11f1d Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Wed, 18 Dec 2024 20:30:06 +0800 Subject: [PATCH 16/23] feat: update vibrate --- .../runtime/components/react/mpx-picker-view-column.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 666706c3e..dd8a7ea52 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,5 +1,5 @@ import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react' -import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' +import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Platform, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' // @ts-expect-error ignore import { vibrateShort } from '@mpxjs/api-proxy' @@ -199,6 +199,9 @@ const _PickerViewColumn = forwardRef, } const onScroll = (e: NativeSyntheticEvent) => { + if (Platform.OS === 'android') { + return + } const { y } = e.nativeEvent.contentOffset const { index: prevIndex, y: _y } = prevScrollingInfo.current if (touching.current || scrolling.current) { @@ -209,7 +212,7 @@ const _PickerViewColumn = forwardRef, index: currentId, y: currentId * itemRawH } - vibrateShort() + vibrateShort({ type: 'selection' }) } } } From 6513c608825516c58a5010d9e25101c00295dd2b Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Thu, 19 Dec 2024 19:12:40 +0800 Subject: [PATCH 17/23] feat: update style --- .../components/react/mpx-picker-view-column-item.tsx | 4 ++-- .../lib/runtime/components/react/pickerFaces.ts | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx index 6b4e2344c..12a8dda8d 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect } from 'react' +import React, { useEffect } from 'react' import { LayoutChangeEvent } from 'react-native' import Reanimated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated' import { wrapChildren, extendObject } from './utils' @@ -42,7 +42,7 @@ const _PickerViewColumnItem: React.FC = ({ return { opacity: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.opacity), Extrapolation.CLAMP), transform: [ - { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.EXTEND) + 'deg' }, + { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' }, { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) }, { scale: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.scale), Extrapolation.EXTEND) } ] diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts index b5e852e89..06f76cb43 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts @@ -34,7 +34,7 @@ export const createFaces = ( const maxStep = Math.trunc((visibleCount + 2) / 2) // + 2 because there are 2 more faces at 90 degrees const stepDegree = 90 / maxStep - const result = [] + const result: number[] = [] for (let i = 1; i <= maxStep; i++) { result.push(i * stepDegree) } @@ -77,9 +77,7 @@ export const createFaces = ( const degrees = getDegreesRelativeCenter() const [screenHeight, offsets] = getScreenHeightsAndOffsets(degrees) - const scale = (index: number) => { - return 0.92 - index * 0.1 - } + const scales = [1, 0.925, 0.8] return [ // top items @@ -90,14 +88,14 @@ export const createFaces = ( deg: degree, opacity: getOpacity(degrees.length - 1 - index), offsetY: -1 * offsets[index], - scale: scale(index), + scale: scales[index], screenHeight: screenHeight[index] } }) .reverse(), // center item - { index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1, screenHeight: itemHeight }, + { index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1.031, screenHeight: itemHeight }, // bottom items ...degrees.map((degree, index) => { @@ -106,7 +104,7 @@ export const createFaces = ( deg: -1 * degree, opacity: getOpacity(degrees.length - 1 - index), offsetY: offsets[index], - scale: scale(index), + scale: scales[index], screenHeight: screenHeight[index] } }) From 6fc089495caa239fecab8d89ea8104af538eff94 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Thu, 19 Dec 2024 19:19:58 +0800 Subject: [PATCH 18/23] fix: ts lint --- .../lib/runtime/components/react/mpx-picker-view-column.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index dd8a7ea52..18a4992c8 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,7 +1,6 @@ import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react' import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Platform, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' -// @ts-expect-error ignore import { vibrateShort } from '@mpxjs/api-proxy' import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' From 416aa72098fb15773b369c038ffd3eb73b760d1f Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Thu, 19 Dec 2024 20:46:24 +0800 Subject: [PATCH 19/23] feat: update --- .../lib/runtime/components/react/mpx-picker-view-column.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 18a4992c8..8ab6aed4d 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -152,7 +152,9 @@ const _PickerViewColumn = forwardRef, const onContentSizeChange = (_w: number, h: number) => { const y = itemRawH * initialIndex if (y <= h) { - scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) + setTimeout(() => { + scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) + }, 0) } } From 7f032a681fedc95da58a12272c1160ddc23cdf31 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Fri, 20 Dec 2024 17:49:21 +0800 Subject: [PATCH 20/23] fix: width style --- .../react/mpx-picker-view-column-item.tsx | 50 ++++++++++--------- .../react/mpx-picker-view-column.tsx | 25 ++++++---- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx index 12a8dda8d..43995331d 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx @@ -6,22 +6,24 @@ import { createFaces } from './pickerFaces' import { usePickerViewColumnAnimationContext } from './pickerVIewContext' interface PickerColumnItemProps { - item: React.ReactElement - index: number - itemHeight: number - textStyleFromParent: Record - textStyle: Record - hasVarDec: boolean - varContext: Record - visibleCount: number - textProps?: any - onItemLayout?: (e: LayoutChangeEvent) => void + item: React.ReactElement + index: number + itemHeight: number + itemWidth: number | '100%' + textStyleFromParent: Record + textStyle: Record + hasVarDec: boolean + varContext: Record + visibleCount: number + textProps?: any + onItemLayout?: (e: LayoutChangeEvent) => void } const _PickerViewColumnItem: React.FC = ({ item, index, itemHeight, + itemWidth, textStyleFromParent, textStyle, hasVarDec, @@ -65,20 +67,20 @@ const _PickerViewColumnItem: React.FC = ({ const realItem = React.cloneElement(item, itemProps) return ( - - {wrapChildren( - { children: realItem }, - { - hasVarDec, - varContext, - textStyle, - textProps - } - )} - + + {wrapChildren( + { children: realItem }, + { + hasVarDec, + varContext, + textStyle, + textProps + } + )} + ) } diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 8ab6aed4d..85cb594ce 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -57,7 +57,6 @@ const _PickerViewColumn = forwardRef, const { textStyle: textStyleFromParent = {} } = splitStyle(columnStyle) const { textStyle = {} } = splitStyle(normalStyle) const { textProps } = splitProps(props) - // const scrollViewRef = useRef(null) const scrollViewRef = useAnimatedRef() const offsetYShared = useScrollViewOffset(scrollViewRef as AnimatedRef) @@ -67,6 +66,7 @@ const _PickerViewColumn = forwardRef, const { height: pickerH, itemHeight } = wrapperStyle const [scrollViewWidth, setScrollViewWidth] = useState('100%') + const [itemRawW, setItemRawW] = useState('100%') const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) const maxScrollViewWidth = useRef(-1) @@ -87,6 +87,8 @@ const _PickerViewColumn = forwardRef, nodeRef: scrollViewRef }) + console.log('[mpx-picker-view-column], render ---> columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'columnData=', columnData.length) + // const initialOffset = useMemo(() => ({ // x: 0, // y: itemRawH * initialIndex @@ -134,6 +136,15 @@ const _PickerViewColumn = forwardRef, activeIndex.current = initialIndex }, [itemRawH, initialIndex]) + const onContentSizeChange = (_w: number, h: number) => { + const y = itemRawH * initialIndex + if (y <= h) { + setTimeout(() => { + scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) + }, 0) + } + } + const onScrollViewLayout = (e: LayoutChangeEvent) => { const { width } = e.nativeEvent.layout const widthInt = Math.ceil(width) @@ -147,14 +158,8 @@ const _PickerViewColumn = forwardRef, } setScrollViewWidth(widthInt) } - } - - const onContentSizeChange = (_w: number, h: number) => { - const y = itemRawH * initialIndex - if (y <= h) { - setTimeout(() => { - scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) - }, 0) + if (itemRawW === '100%') { + setItemRawW(widthInt) } } @@ -227,6 +232,7 @@ const _PickerViewColumn = forwardRef, item={item} index={index} itemHeight={itemHeight} + itemWidth={itemRawW} textStyleFromParent={textStyleFromParent} textStyle={textStyle} hasVarDec={hasVarDec} @@ -244,6 +250,7 @@ const _PickerViewColumn = forwardRef, Date: Mon, 23 Dec 2024 17:30:37 +0800 Subject: [PATCH 21/23] fix: Android config --- .../react/mpx-picker-view-column.tsx | 24 ++++++++++++------- .../lib/runtime/components/react/utils.tsx | 5 +++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 85cb594ce..75cdf51a6 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,8 +1,7 @@ import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react' -import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, Platform, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' +import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' -import { vibrateShort } from '@mpxjs/api-proxy' -import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious } from './utils' +import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' import PickerOverlay from './pickerViewOverlay' import PickerMask from './pickerViewMask' @@ -87,8 +86,6 @@ const _PickerViewColumn = forwardRef, nodeRef: scrollViewRef }) - console.log('[mpx-picker-view-column], render ---> columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'columnData=', columnData.length) - // const initialOffset = useMemo(() => ({ // x: 0, // y: itemRawH * initialIndex @@ -114,6 +111,7 @@ const _PickerViewColumn = forwardRef, }, [itemRawH, maxIndex]) useEffect(() => { + // console.log('[mpx-picker-view-column], useEffect000 --->', 'columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'prevIndex=', prevIndex, 'activeIndex=', activeIndex.current, 'maxIndex=', maxIndex, 'prevMaxIndex=', prevMaxIndex) if ( !scrollViewRef.current || !itemRawH || @@ -132,7 +130,7 @@ const _PickerViewColumn = forwardRef, y: itemRawH * initialIndex, animated: false }) - }, 0) + }, isAndroid ? 200 : 0) activeIndex.current = initialIndex }, [itemRawH, initialIndex]) @@ -146,6 +144,10 @@ const _PickerViewColumn = forwardRef, } const onScrollViewLayout = (e: LayoutChangeEvent) => { + if (isAndroid) { + return + } + // RN iOS bug: https://github.com/facebook/react-native/issues/36135 const { width } = e.nativeEvent.layout const widthInt = Math.ceil(width) if (widthInt !== scrollViewWidth) { @@ -205,7 +207,12 @@ const _PickerViewColumn = forwardRef, } const onScroll = (e: NativeSyntheticEvent) => { - if (Platform.OS === 'android') { + if (isAndroid) { + return + } + // 全局注册的震动触感 hook + const pickerVibrate = global.__mpx.config.rnConfig.pickerVibrate + if (typeof pickerVibrate !== 'function') { return } const { y } = e.nativeEvent.contentOffset @@ -218,7 +225,8 @@ const _PickerViewColumn = forwardRef, index: currentId, y: currentId * itemRawH } - vibrateShort({ type: 'selection' }) + // vibrateShort({ type: 'selection' }) + pickerVibrate() } } } diff --git a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx index a00bb771e..1e1b48fe4 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx @@ -1,5 +1,5 @@ import { useEffect, useCallback, useMemo, useRef, ReactNode, ReactElement, isValidElement, useContext, useState, Dispatch, SetStateAction, Children, cloneElement } from 'react' -import { LayoutChangeEvent, TextStyle, ImageProps, Image } from 'react-native' +import { LayoutChangeEvent, TextStyle, ImageProps, Image, Platform } from 'react-native' import { isObject, isFunction, isNumber, hasOwn, diffAndCloneA, error, warn, getFocusedNavigation } from '@mpxjs/utils' import { VarContext } from './context' import { ExpressionParser, parseFunc, ReplaceSource } from './parser' @@ -18,6 +18,9 @@ export const HIDDEN_STYLE = { opacity: 0 } +export const isIOS = Platform.OS === 'ios' +export const isAndroid = Platform.OS === 'android' + const varDecRegExp = /^--.*/ const varUseRegExp = /var\(/ const calcUseRegExp = /calc\(/ From 948cbf2599f90674c677f4aa8851f9402a1e2263 Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Mon, 23 Dec 2024 19:41:31 +0800 Subject: [PATCH 22/23] fix: padding height --- .../components/react/mpx-picker-view-column.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index 75cdf51a6..e01c9f823 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -91,16 +91,16 @@ const _PickerViewColumn = forwardRef, // y: itemRawH * initialIndex // }), [itemRawH]) + const paddingHeight = useMemo( + () => Math.round((pickerH - itemHeight) / 2), + [pickerH, itemHeight] + ) + const snapToOffsets = useMemo( () => columnData.map((_, i) => i * itemRawH), [columnData, itemRawH] ) - const paddingHeight = useMemo( - () => Math.round((pickerH - itemRawH) / 2), - [pickerH, itemRawH] - ) - const contentContainerStyle = useMemo(() => { return [{ paddingVertical: paddingHeight }] }, [paddingHeight]) @@ -111,7 +111,6 @@ const _PickerViewColumn = forwardRef, }, [itemRawH, maxIndex]) useEffect(() => { - // console.log('[mpx-picker-view-column], useEffect000 --->', 'columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'prevIndex=', prevIndex, 'activeIndex=', activeIndex.current, 'maxIndex=', maxIndex, 'prevMaxIndex=', prevMaxIndex) if ( !scrollViewRef.current || !itemRawH || From 52769298bbbc19fac065736b2d8e43b566dd3b3b Mon Sep 17 00:00:00 2001 From: wangshunnn Date: Thu, 26 Dec 2024 18:07:00 +0800 Subject: [PATCH 23/23] fix: reset scroll position --- .../react/mpx-picker-view-column-item.tsx | 4 +- .../react/mpx-picker-view-column.tsx | 98 +++++++++---------- .../runtime/components/react/pickerFaces.ts | 2 +- .../lib/runtime/components/react/utils.tsx | 5 +- 4 files changed, 51 insertions(+), 58 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx index 43995331d..ac111ffe8 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column-item.tsx @@ -9,7 +9,7 @@ interface PickerColumnItemProps { item: React.ReactElement index: number itemHeight: number - itemWidth: number | '100%' + itemWidth?: number | '100%' textStyleFromParent: Record textStyle: Record hasVarDec: boolean @@ -23,7 +23,7 @@ const _PickerViewColumnItem: React.FC = ({ item, index, itemHeight, - itemWidth, + itemWidth = '100%', textStyleFromParent, textStyle, hasVarDec, diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx index e01c9f823..3d56db860 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view-column.tsx @@ -1,7 +1,7 @@ import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react' import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, SafeAreaView, ScrollView, StyleSheet, View } from 'react-native' import Reanimated, { AnimatedRef, useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated' -import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid } from './utils' +import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid, isIOS, useDebounceCallback, useStableCallback } from './utils' import useNodesRef, { HandlerRef } from './useNodesRef' import PickerOverlay from './pickerViewOverlay' import PickerMask from './pickerViewMask' @@ -64,11 +64,8 @@ const _PickerViewColumn = forwardRef, }) const { height: pickerH, itemHeight } = wrapperStyle - const [scrollViewWidth, setScrollViewWidth] = useState('100%') - const [itemRawW, setItemRawW] = useState('100%') const [itemRawH, setItemRawH] = useState(itemHeight) const maxIndex = useMemo(() => columnData.length - 1, [columnData]) - const maxScrollViewWidth = useRef(-1) const prevScrollingInfo = useRef({ index: initialIndex, y: 0 }) const touching = useRef(false) const scrolling = useRef(false) @@ -86,10 +83,7 @@ const _PickerViewColumn = forwardRef, nodeRef: scrollViewRef }) - // const initialOffset = useMemo(() => ({ - // x: 0, - // y: itemRawH * initialIndex - // }), [itemRawH]) + // console.log('[mpx-picker-view-column], render ---> columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'columnData=', columnData.length, 'pickerH=', pickerH, 'itemRawH=', itemRawH, 'itemHeight=', itemHeight) const paddingHeight = useMemo( () => Math.round((pickerH - itemHeight) / 2), @@ -110,6 +104,27 @@ const _PickerViewColumn = forwardRef, return Math.max(0, Math.min(calc, maxIndex)) }, [itemRawH, maxIndex]) + const getYofIndex = useCallback((index: number) => { + return index * itemRawH + }, [itemRawH]) + + const stableResetScrollPosition = useStableCallback((y: number) => { + console.log('[mpx-picker-view-column], reset --->', 'columnIndex=', columnIndex, 'y=', y, touching.current, scrolling.current) + if (touching.current || scrolling.current) { + return + } + // needReset.current = true + if (y % itemRawH !== 0) { + scrolling.current = true + const targetIndex = getIndex(y) + const targetY = getYofIndex(targetIndex) + scrollViewRef.current?.scrollTo({ x: 0, y: targetY, animated: false }) + } else { + onMomentumScrollEnd({ nativeEvent: { contentOffset: { y } } }) + } + }) + const debounceResetScrollPosition = useDebounceCallback(stableResetScrollPosition, 10) + useEffect(() => { if ( !scrollViewRef.current || @@ -126,7 +141,7 @@ const _PickerViewColumn = forwardRef, setTimeout(() => { scrollViewRef.current?.scrollTo({ x: 0, - y: itemRawH * initialIndex, + y: getYofIndex(initialIndex), animated: false }) }, isAndroid ? 200 : 0) @@ -134,7 +149,7 @@ const _PickerViewColumn = forwardRef, }, [itemRawH, initialIndex]) const onContentSizeChange = (_w: number, h: number) => { - const y = itemRawH * initialIndex + const y = getYofIndex(initialIndex) if (y <= h) { setTimeout(() => { scrollViewRef.current?.scrollTo({ x: 0, y, animated: false }) @@ -142,28 +157,6 @@ const _PickerViewColumn = forwardRef, } } - const onScrollViewLayout = (e: LayoutChangeEvent) => { - if (isAndroid) { - return - } - // RN iOS bug: https://github.com/facebook/react-native/issues/36135 - const { width } = e.nativeEvent.layout - const widthInt = Math.ceil(width) - if (widthInt !== scrollViewWidth) { - const maxW = maxScrollViewWidth.current - if (maxW !== -1 && widthInt > maxW) { - return - } - if (maxW === -1) { - maxScrollViewWidth.current = Math.ceil(widthInt * 1.5) - } - setScrollViewWidth(widthInt) - } - if (itemRawW === '100%') { - setItemRawW(widthInt) - } - } - const onItemLayout = (e: LayoutChangeEvent) => { const { height: rawH } = e.nativeEvent.layout if (rawH && itemRawH !== rawH) { @@ -171,36 +164,39 @@ const _PickerViewColumn = forwardRef, } } - const onTouchStart = () => { + const onScrollBeginDrag = () => { + isIOS && debounceResetScrollPosition.clear() touching.current = true prevScrollingInfo.current = { index: activeIndex.current, - y: activeIndex.current * itemRawH + y: getYofIndex(activeIndex.current) } } - const onTouchEnd = () => { - touching.current = false - } - - const onTouchCancel = () => { + const onScrollEndDrag = (e: NativeSyntheticEvent) => { touching.current = false + const { y } = e.nativeEvent.contentOffset + if (isIOS) { + if (y > 0 && y < snapToOffsets[maxIndex]) { + debounceResetScrollPosition(y) + } + } } const onMomentumScrollBegin = () => { + isIOS && debounceResetScrollPosition.clear() scrolling.current = true } - const onMomentumScrollEnd = (e: NativeSyntheticEvent) => { + const onMomentumScrollEnd = (e: NativeSyntheticEvent | { nativeEvent: { contentOffset: { y: number } } }) => { scrolling.current = false - if (!itemRawH) { - return - } const { y: scrollY } = e.nativeEvent.contentOffset - let calcIndex = Math.round(scrollY / itemRawH) + if (isIOS && scrollY % itemRawH !== 0) { + return debounceResetScrollPosition(scrollY) + } + const calcIndex = getIndex(scrollY) activeIndex.current = calcIndex if (calcIndex !== initialIndex) { - calcIndex = Math.max(0, Math.min(calcIndex, maxIndex)) || 0 onSelectChange(calcIndex) } } @@ -222,9 +218,8 @@ const _PickerViewColumn = forwardRef, if (currentId !== prevIndex) { prevScrollingInfo.current = { index: currentId, - y: currentId * itemRawH + y: getYofIndex(currentId) } - // vibrateShort({ type: 'selection' }) pickerVibrate() } } @@ -239,7 +234,6 @@ const _PickerViewColumn = forwardRef, item={item} index={index} itemHeight={itemHeight} - itemWidth={itemRawW} textStyleFromParent={textStyleFromParent} textStyle={textStyle} hasVarDec={hasVarDec} @@ -264,14 +258,12 @@ const _PickerViewColumn = forwardRef, showsHorizontalScrollIndicator={false} scrollEventThrottle={16} {...layoutProps} - style={[{ width: scrollViewWidth }]} + style={[{ width: '100%' }]} decelerationRate="fast" snapToOffsets={snapToOffsets} onScroll={onScroll} - onLayout={onScrollViewLayout} - onTouchStart={onTouchStart} - onTouchEnd={onTouchEnd} - onTouchCancel={onTouchCancel} + onScrollBeginDrag={onScrollBeginDrag} + onScrollEndDrag={onScrollEndDrag} onMomentumScrollBegin={onMomentumScrollBegin} onMomentumScrollEnd={onMomentumScrollEnd} onContentSizeChange={onContentSizeChange} diff --git a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts index 06f76cb43..f8f7ac03f 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/pickerFaces.ts @@ -95,7 +95,7 @@ export const createFaces = ( .reverse(), // center item - { index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1.031, screenHeight: itemHeight }, + { index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1, screenHeight: itemHeight }, // bottom items ...degrees.map((degree, index) => { diff --git a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx index 1e1b48fe4..9c15d936f 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/utils.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/utils.tsx @@ -537,13 +537,14 @@ export const debounce = ( ): ((...args: Parameters) => void) & { clear: () => void } => { let timer: any const wrapper = (...args: ReadonlyArray) => { - clearTimeout(timer) + timer && clearTimeout(timer) timer = setTimeout(() => { func(...args) }, delay) } wrapper.clear = () => { - clearTimeout(timer) + timer && clearTimeout(timer) + timer = null } return wrapper }