React Native Expo 模式内的自定义下拉菜单未正确显示

问题描述 投票:0回答:1

我最近按照 YouTube 教程创建了一个自定义下拉组件(下面的代码片段)。 下拉菜单效果很好,但有一个我无法解决的大样式问题。 每当我单击下拉组件时,下拉选项都会显示在屏幕顶部,而不是像通常的下拉菜单一样直接显示在下方。

dropdown options should be below the dropdown component

下拉组件在注册模式内部呈现。我认为问题正在发生,因为我正在另一个模态(注册模态)内部渲染一个模态(下拉组件),但无法找到解决方法。

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
进行硬编码。 通过对其进行硬编码,我可以让它正确地查找其中一个下拉列表,但它不是动态的,因此在上面或下面添加的其他下拉列表仍然看起来不正确。

css react-native drop-down-menu expo
1个回答
0
投票

我认为 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,其中有两个下拉组件

© www.soinside.com 2019 - 2024. All rights reserved.