我最近按照 YouTube 教程创建了一个自定义下拉组件(下面的代码片段)。 下拉菜单效果很好,但有一个我无法解决的大样式问题。 每当我单击下拉组件时,下拉选项都会显示在屏幕顶部,而不是像通常的下拉菜单一样直接显示在下方。
下拉组件在注册模式内部呈现。我认为问题正在发生,因为我正在另一个模态(注册模态)内部渲染一个模态(下拉组件),但无法找到解决方法。
import { AntDesign } from '@expo/vector-icons'
import PropTypes from 'prop-types'
import { useCallback, useRef, useState } from 'react'
import {
Dimensions,
FlatList,
Modal,
Platform,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from 'react-native'
const Dropdown = ({ data, onChange, width = 300, isUsedInsideModal = false }) => {
const [expanded, setExpanded] = useState()
const [value, setValue] = useState('')
const toggleExpanded = useCallback(() => setExpanded(!expanded), [expanded])
const buttonRef = useRef(null)
const [top, setTop] = useState(0)
const onSelect = useCallback((item) => {
onChange(item)
setValue(item.value)
setExpanded(false)
}, [])
return (
<View
ref={buttonRef}
onLayout={(event) => {
const layout = event.nativeEvent.layout
const topOffset = layout.y
const heightOfComponent = layout.height
const finalValue =
topOffset + heightOfComponent + (Platform.OS === 'android' ? -32 : 3)
setTop(finalValue)
}}
style={styles.container}
>
<TouchableOpacity
style={styles.button(width)}
activeOpacity={0.8}
onPress={toggleExpanded}
>
<Text style={styles.text}>{value || 'Select Option'}</Text>
<AntDesign name={expanded ? 'caretup' : 'caretdown'} />
</TouchableOpacity>
{expanded ? (
<Modal visible={expanded} transparent>
<TouchableWithoutFeedback onPress={() => setExpanded(false)}>
<View style={styles.backdrop}>
<View
style={[
styles.options,
{
top,
width,
},
]}
>
<FlatList
keyExtractor={(item) => item.value}
data={data}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
activeOpacity={0.8}
style={styles.optionItem}
onPress={() => onSelect(item)}
>
<Text>{item.value}</Text>
</TouchableOpacity>
)
}}
ItemSeparatorComponent={() => (
<View style={styles.separator} />
)}
/>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
) : null}
</View>
)
}
const styles = StyleSheet.create({
button: (width) => ({
height: 50,
justifyContent: 'space-between',
flexDirection: 'row',
width,
alignItems: 'center',
paddingHorizontal: 15,
borderRadius: 8,
borderColor: '#9E9D9E',
borderWidth: 1,
}),
text: {
fontSize: 15,
opacity: 0.8,
},
options: {
position: 'absolute',
backgroundColor: '#fff',
padding: 10,
borderRadius: 6,
borderBottomLeftRadius: 6,
borderBottomRightRadius: 6,
maxHeight: 250,
border: '1px black',
overflow: 'hidden',
},
optionItem: {
height: 45,
justifyContent: 'center',
marginBottom: 10,
},
separator: {
height: 4,
},
backdrop: {
padding: 20,
justifyContent: 'center',
alignItems: 'center',
flex: 1,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
})
export default Dropdown
这又是在模态内部渲染,结构是这样的:
<View>
<Modal style={styles.modal} animationType="slide">
<ScrollView>
<View>
<Dropdown/>
</View>
</ScrollView>
</Modal>
</View>
我尝试过对设置顶部样式属性的
finalValue
进行硬编码。 通过对其进行硬编码,我可以让它正确地查找其中一个下拉列表,但它不是动态的,因此在上面或下面添加的其他下拉列表仍然看起来不正确。
我认为 y 定位关闭的原因是因为滚动视图中有模态;并且模态中的 onLayout 调用会忽略滚动视图。要解决此问题,只需获取包含下拉列表的视图的 y 位置并将其传递给下拉组件即可;由于您已经获得了 y 位置,因此您可以抓住 x 位置来整齐地对齐菜单。
因为布局被计算了不止一次,而且我手头上已经有了它,所以我创建了一个 useLayout 钩子(在打字稿中):
import * as React from 'react';
import type { LayoutChangeEvent, LayoutRectangle } from 'react-native';
export default function useLayout() {
const [layout, setLayout] = React.useState<LayoutRectangle>({
height: 0,
width: 0,
x: 0,
y: 0,
});
const onLayout = React.useCallback((e: LayoutChangeEvent) => {
setLayout(e.nativeEvent.layout);
}, []);
return [layout, onLayout] as const;
}
使用onLayout设置偏移量:
<View style={styles.dropdownContainer} onLayout={onLayout}>
<Dropdown
data={data}
yOffset={layout.y}
xOffset={layout.x}
onChange={setSelectedItem}
width={layout.width / 2.5}
/>
<Dropdown
data={data}
yOffset={layout.y}
xOffset={layout.x}
onChange={setSelectedItem}
width={layout.width / 2.5}
/>
</View>
下拉组件:
const Dropdown = ({
data,
onChange,
width = 300,
isUsedInsideModal = false,
yOffset = 0,
xOffset = 0,
}) => {
const [expanded, setExpanded] = useState();
const [value, setValue] = useState('');
const toggleExpanded = useCallback(() => setExpanded((prev) => !prev), []);
const [layout, onLayout] = useLayout();
const buttonRef = useRef(null);
const insets = useSafeAreaInsets();
const onSelect = useCallback(
(item) => {
onChange(item);
setValue(item.value);
setExpanded(false);
},
[onChange]
);
const top = useMemo(
() => layout.y + layout.height + yOffset,
[layout, yOffset]
);
const left = useMemo(
() => layout.x + xOffset,
[layout, xOffset]
);
console.log(layout.x)
return (
<View ref={buttonRef} style={styles.container} onLayout={onLayout}>
<TouchableOpacity
style={styles.button(width)}
activeOpacity={0.8}
onPress={toggleExpanded}>
<Text style={styles.text}>{value || 'Select Option'}</Text>
<AntDesign name={expanded ? 'caretup' : 'caretdown'} />
</TouchableOpacity>
{expanded && (
<Modal visible={expanded} transparent>
<TouchableWithoutFeedback onPress={() => setExpanded(false)}>
<View style={styles.backdrop}>
<View style={[styles.options, { top, width,left }]}>
<FlatList
keyExtractor={(item) => item.value}
data={data}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
activeOpacity={0.8}
style={styles.optionItem}
onPress={() => onSelect(item)}>
<Text>{item.value}</Text>
</TouchableOpacity>
);
}}
ItemSeparatorComponent={() => (
<View style={styles.separator} />
)}
/>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
)}
</View>
);
};
A demo,其中 Modal 有一个 ScrollView,其中有两个下拉组件