Skip to content

Commit

Permalink
Merge pull request #1642 from didi/feat-lyf-pickerview
Browse files Browse the repository at this point in the history
重构pickerView
  • Loading branch information
hiyuki authored Oct 11, 2024
2 parents 3ed8d82 + 68f05ec commit 16ed4fe
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 101 deletions.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"@docsearch/js": "^3.0.0",
"@testing-library/jest-dom": "^4.2.4",
"@types/jest": "^27.0.1",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"@vuepress/plugin-back-to-top": "^1.8.2",
"@vuepress/plugin-pwa": "^1.8.0",
"eslint": "^7.32.0",
Expand All @@ -30,8 +32,6 @@
"eslint-plugin-jest": "^27.0.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.2.0",
"lerna": "^8.1.8",
Expand All @@ -40,5 +40,9 @@
},
"workspaces": [
"packages/*"
]
],
"dependencies": {
"@types/react": "^18.3.11",
"react-native-linear-gradient": "^2.8.3"
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,128 @@

import { View } from 'react-native'
import React, { forwardRef, useRef } from 'react'
import useInnerProps from './getInnerListeners'
import { View, Animated, SafeAreaView, NativeScrollEvent, NativeSyntheticEvent, LayoutChangeEvent, ScrollView } from 'react-native'
import React, { forwardRef, useRef, useState, useEffect, ReactElement } from 'react'
// import { Reanimated } from 'react-native-reanimated';
import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数

interface ColumnProps {
children: React.ReactNode
children: React.ReactNode,
selectedIndex: number,
onColumnLayoutChange: Function,
getInnerLayout: Function,
onSelectChange: Function,
style: Object,
wrapperStyle: {
height?: number
},
prefix: number
}

const _PickerViewColumn = forwardRef<HandlerRef<View, ColumnProps>, ColumnProps>((props: ColumnProps, ref) => {
const { children } = props
// 每个Column 都有个外层的高度, 内部的元素高度
// 默认的高度
// const AnimatedScrollView = Reanimated.createAnimatedComponent(ScrollView);
const _PickerViewColumn = forwardRef<HandlerRef<ScrollView & View, ColumnProps>, ColumnProps>((props: ColumnProps, ref) => {
const { children, selectedIndex, onColumnLayoutChange, onSelectChange, getInnerLayout, wrapperStyle } = props
// scrollView的ref
const { nodeRef: scrollViewRef } = useNodesRef(props, ref, {})
// scrollView的布局存储
const layoutRef = useRef({})
const { nodeRef } = useNodesRef(props, ref, {})
const innerProps = useInnerProps(props, {}, [], { layoutRef })
return (
<View
ref={nodeRef}
{...innerProps}>
{children}
</View>
)
// 每个元素的高度
let [itemH, setItemH] = useState(0)
// scrollView内容的初始offset
let [offset, setOffset] = useState(0)

useEffect(() => {
if (selectedIndex && itemH) {
offset = (selectedIndex + 2) * itemH
setOffset(offset)
}
}, [selectedIndex, itemH])

const onScrollViewLayout = () => {
scrollViewRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => {
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop }
getInnerLayout && getInnerLayout(layoutRef)
})
}

const onItemLayout = (e: LayoutChangeEvent) => {
const layout = e.nativeEvent.layout
if (layout.height && itemH !== layout.height) {
itemH = layout.height
setItemH(layout.height)
onColumnLayoutChange && onColumnLayoutChange({ height: layout.height * 5 })
}
}

const onMomentumScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
if (scrollViewRef && itemH) {
const { y: scrollY } = e.nativeEvent.contentOffset
const selIndex = scrollY / itemH
onSelectChange(selIndex)
}
}

const renderInnerchild = () => {
// Fragment 节点
const getRealChilds = () => {
if (Array.isArray(children)) {
return children
} else {
const tempChild = children as ReactElement
if (tempChild.props.children && tempChild.props.children) {
return tempChild.props.children
} else {
return [children]
}
}
}
// const realChilds = Array.isArray(children) ? children : (children?.props?.children && Array.isArray(children.props?.children) ? children.props.children : [children])
const realChilds = getRealChilds()
const arrChild = realChilds.map((item: React.ReactNode, index: number) => {
const InnerProps = index === 0 ? { onLayout: onItemLayout } : {}
const strKey = 'picker' + props.prefix + '-column' + index
return <View key={strKey} {...InnerProps}>{item}</View>
})
const totalHeight = itemH * 5
if (wrapperStyle.height && totalHeight > wrapperStyle.height) {
const fix = Math.ceil((totalHeight - wrapperStyle.height) / 2)
arrChild.unshift(<View key="picker-column-0" style={[{ height: itemH - fix }]}></View>)
arrChild.unshift(<View key="picker-column-1" style={[{ height: itemH }]}></View>)
arrChild.push(<View key="picker-column-2" style={[{ height: itemH }]}></View>)
arrChild.push(<View key="picker-column-3" style={[{ height: itemH - fix }]}></View>)
} else {
arrChild.unshift(<View key="picker-column-0" style={[{ height: itemH }]}></View>)
arrChild.unshift(<View key="picker-column-1" style={[{ height: itemH }]}></View>)
arrChild.push(<View key="picker-column-2" style={[{ height: itemH }]}></View>)
arrChild.push(<View key="picker-column-3" style={[{ height: itemH }]}></View>)
}
return arrChild
}

const renderScollView = () => {
const contentContainerStyle = {
textAlign: 'center'
}

return (<Animated.ScrollView
horizontal={false}
ref={scrollViewRef}
bounces={false}
scrollsToTop={false}
removeClippedSubviews={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
pagingEnabled={false}
snapToInterval={itemH}
automaticallyAdjustContentInsets={false}
onLayout={onScrollViewLayout}
onMomentumScrollEnd={onMomentumScrollEnd}>
{renderInnerchild()}
</Animated.ScrollView>)
}

return (<SafeAreaView style={[{ display: 'flex', flex: 1 }]}>
{ renderScollView() }
</SafeAreaView>)
})

_PickerViewColumn.displayName = 'mpx-picker-view-column'

export default _PickerViewColumn
185 changes: 125 additions & 60 deletions packages/webpack-plugin/lib/runtime/components/react/mpx-picker-view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { View } from 'react-native'
import React, { forwardRef, useState, useRef, useEffect } from 'react'
import { PickerView } from '@ant-design/react-native'
import { LinearGradient, LinearGradientProps } from 'react-native-linear-gradient'
import React, { forwardRef, MutableRefObject, useState, useRef, ReactElement, JSX } from 'react'
import useInnerProps, { getCustomEvent } from './getInnerListeners'
import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
/**
Expand All @@ -20,81 +20,146 @@ interface PickerViewProps {
// 初始的defaultValue数组中的数字依次表示 picker-view 内的 picker-view-column 选择的第几项(下标从 0 开始),数字大于 picker-view-column 可选项长度时,选择最后一项。
value?: Array<number>
bindchange?: Function
style?: {
height?: number
}
}

const _PickerView = forwardRef<HandlerRef<View, PickerViewProps>, PickerViewProps>((props: PickerViewProps, ref) => {
const { children, ...restProps } = props
const layoutRef = useRef({})
const { nodeRef } = useNodesRef(props, ref, {})
const [value, setValue] = useState(props.value)
useEffect(() => {
// 确认这个是变化的props变化的时候才执行,还是初始化的时候就执行
setValue(props.value)
}, [props.value])
interface PickerLayout {
height: number,
itemHeight: number
}

const onLayout = () => {
nodeRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => {
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop }
})
interface PosType {
height?: number,
top?: number
}

const styles: { [key: string]: Object } = {
wrapper: {
display: 'flex',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
overflow: 'hidden',
alignItems: 'center'
},
maskTop: {
position: 'absolute',
width: 1000,
zIndex: 100
},
maskBottom: {
position: 'absolute',
width: 1000,
zIndex: 100
}
const innerProps = useInnerProps(props, {}, [], { layoutRef })
}
const _PickerView = forwardRef<HandlerRef<View, PickerViewProps>, PickerViewProps>((props: PickerViewProps, ref) => {
const { children, value = [], bindchange, style } = props
const innerLayout = useRef({})
const cloneRef = useRef(null)
const wrapRef = useRef(null)
const maskPos: PosType = {}
let [pickH, setPickH] = useState(0)
if (style?.height && pickH) {
maskPos.height = pickH / 5 * 2 + (style.height - pickH)
} else {
maskPos.height = pickH / 5 * 2
}

const { nodeRef } = useNodesRef<View, PickerViewProps>(props, ref, {})

// value 如何关联picker-view-column这几个slot的内容呢

const onChange = (val: Array<number>): void => {
const eventData = getCustomEvent('change', {}, { detail: { value: val, source: 'touch' }, layoutRef: layoutRef })
setValue(val)
props.bindchange && props.bindchange(eventData)
const onColumnLayoutChange = (layoutConfig: PickerLayout) => {
pickH = layoutConfig.height
setPickH(layoutConfig.height)
}

const joinString = (data: string | any[] | React.ReactElement): string => {
return (Array.isArray(data) ? data : [data]).join('')
const onSelectChange = (columnIndex: number, selIndex: number) => {
const changeValue = value.slice()
changeValue[columnIndex] = selIndex
const eventData = getCustomEvent('change', {}, { detail: { value: changeValue, source: 'change' }, layoutRef: innerLayout })
bindchange && bindchange(eventData)
}
/*
const onWrapperLayout = () => {
wrapRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => {
const a = { x, y, width, height, offsetLeft, offsetTop }
})
}
*/
const getInnerLayout = (layout: MutableRefObject<{}>) => {
innerLayout.current = layout.current
}

const getLabelFromChildren = (child: React.ReactElement): string => {
return child.props && child.props.children ? getLabelFromChildren(child.props.children) : joinString(child)
const innerProps = useInnerProps(props, { ref: nodeRef }, [], { layoutRef: innerLayout })

const cloneChild = (child: React.ReactNode, index: number) => {
const extraProps = index === 0 ? { getInnerLayout: getInnerLayout, innerProps } : {}
const childProps = {
...(child as ReactElement)?.props,
ref: cloneRef,
prefix: index,
key: 'pick-view' + index,
wrapperStyle: {
height: style?.height || 0
},
onColumnLayoutChange,
onSelectChange: onSelectChange.bind(null, index),
selectedIndex: value?.[index] || 0,
...extraProps
}
return React.cloneElement(child as ReactElement, childProps)
}

const handleChildren = (children: React.ReactNode[]): any[] => {
return children.map((child: any, index: number) => {
return {
label: getLabelFromChildren(child),
value: index
}
})
const renderTopMask = () => {
const linearProps: LinearGradientProps = {
colors: ['rgba(255,255,255,0.8)', 'rgba(255,255,255,0.2)'],
style: [
styles.maskTop,
{
height: maskPos.height,
top: 0,
pointerEvents: 'none'
}
]
}
return (<LinearGradient {...linearProps}/>)
}

const getDataFromChildren = (children: React.ReactNode): any[] => {
return (Array.isArray(children) ? children : [children]).map((child: any) => {
return handleChildren(child.props && child.props.children ? child.props.children : [child])
})
const renderBottomMask = () => {
const linearProps: LinearGradientProps = {
colors: ['rgba(255,255,255,0.2)', 'rgba(255,255,255,0.8)'],
style: [
styles.maskBottom,
{
height: maskPos.height,
bottom: 0,
pointerEvents: 'none'
}
]
}
return <LinearGradient {...linearProps}></LinearGradient>
}

const columns = Array.isArray(children) ? children.length : 1
const originData = getDataFromChildren(children)
// 子节点默认的序号,这里是更新默认值的
const subChildLength = originData.map((item) => {
return item.length
})
const defaultValue = (props.value || []).map((item, index) => {
if (item > subChildLength[index]) {
return subChildLength[index] - 1
const renderSubChild = () => {
if (Array.isArray(children)) {
return children.map((item, index) => {
return cloneChild(item, index)
})
} else {
return item
return cloneChild(children, 0)
}
})

return (
<PickerView
{...restProps}
cols={columns}
// 默认选中项
defaultValue={defaultValue}
// 内部维护选中项
value={value}
// data数据源column
data={originData}
onChange={onChange}
cascade={false}/>
)
}
return (<View style={[style, { position: 'relative', overflow: 'hidden' }]} ref={wrapRef}>
{renderTopMask()}
<View style={[styles.wrapper]}>
{renderSubChild()}
</View>
{renderBottomMask()}
</View>)
})

_PickerView.displayName = 'mpx-picker-view'
Expand Down
Loading

0 comments on commit 16ed4fe

Please sign in to comment.