reactnative下拉选择

引用

<FilterDropdown
                        label={""}
                        options={this.state.recordListType}
                        value={this.state.recordType}
                        onChange={(item) => this.handleRecordTypeChange(item)}
                        leftIcon={recordSimg}
                        width={'100%'}
                        style={{ width: "100%", color: 'red' }}
                        labelKey="label"
                        valueKey="value"
                    />

代码组件

import React, { useState, useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Image, Modal } from 'react-native';


/**
 * 通用下拉筛选组件(横向筛选栏专用,菜单用Modal渲染)
 * @param {string} label - 按钮显示文字
 * @param {array} options - 选项数组(字符串数组或对象数组)
 * @param {string|object} value - 当前选中值
 * @param {function} onChange - 选中项变化回调
 * @param {number} width - 按钮宽度(可选)
 * @param {string} labelKey - 对象数组中用于显示的字段名(可选,默认为'label')
 * @param {string} valueKey - 对象数组中用于值的字段名(可选,默认为'value')
 * @param {string} displayKey - 按钮显示文字的字段名(可选,默认为'label')
 */
const FilterDropdown = ({
  label = '',
  options = [],
  value = '',
  onChange = () => {}, // 默认空函数
  width = zero.yWidth(100),
  leftIcon,
    disabled = false,
  style = {}, // 新增,接收外部style
  labelKey = 'label', // 对象数组中用于显示的字段名
  valueKey = 'value', // 对象数组中用于值的字段名
  displayKey = 'label', // 按钮显示文字的字段名
}) => {
  const [visible, setVisible] = useState(false);
  const btnRef = useRef();
  const [btnLayout, setBtnLayout] = useState({ x: 0, y: 0, width: width, height: zero.yHeight(32) });

  // 判断是否为对象数组
  const isObjectArray = options.length > 0 && typeof options[0] === 'object';

  // 获取显示文本
  const getDisplayText = (item) => {
    if (isObjectArray) {
      return item[displayKey] || item[labelKey] || '';
    }
    return item;
  };

  // 获取选项值
  const getOptionValue = (item) => {
    if (isObjectArray) {
      return item[valueKey] || item[labelKey] || '';
    }
    return item;
  };

  // 获取当前选中项的显示文本
  const getCurrentDisplayText = () => {
    if (!value) return label;

    if (isObjectArray) {
      const selectedOption = options.find(option => getOptionValue(option) === value);
      return selectedOption ? getDisplayText(selectedOption) : label;
    }

    return value;
  };

  // 记录按钮宽度和位置,菜单宽度与按钮对齐
  const handleBtnLayout = (e) => {
    if (btnRef.current) {
      btnRef.current.measureInWindow((x, y, width, height) => {
        setBtnLayout({ x, y, width, height });
      });
    }
  };

  return (
    <View style={[styles.wrap, style]}>
      <TouchableOpacity
        ref={btnRef}
        style={[styles.btn, value ? styles.btnActive : null, { width }]}
        activeOpacity={0.8}
        onPress={() => {
          if (disabled) return
          handleBtnLayout();
          setVisible(!visible);
        }}
      >
        <View style={{ flexDirection: 'row', alignItems: 'center' }}>
            {leftIcon && (
            <Image
                source={leftIcon}
                style={[styles.leftIcon]}
            />
            )}
            <Text style={[styles.btnText, value ? styles.btnTextActive : null]}>{getCurrentDisplayText()}</Text>
        </View>
        
        <Image
          source={visible ? require('../res/arrowup.png') : require('../res/arrowdown.png')}
          style={[styles.arrowIcon]}
        />
      </TouchableOpacity>

      <Modal
        visible={visible}
        transparent
        animationType="fade"

        onRequestClose={() => setVisible(false)}
      >
        <TouchableOpacity
          style={styles.modalMask}
          activeOpacity={1}
          onPress={() => setVisible(false)}
        >
          <View style={[
            styles.dropdown,
            {
              position: 'absolute',
              left: btnLayout.x,
              top: btnLayout.y + btnLayout.height,
              width: btnLayout.width,
            }
          ]}>
            {options.map((opt, idx) => {
              const optionValue = getOptionValue(opt);
              const optionText = getDisplayText(opt);
              const isSelected = isObjectArray ? optionValue === value : opt === value;

              return (
                <React.Fragment key={optionValue || idx}>
                  <TouchableOpacity
                    style={styles.option}
                    onPress={() => {
                      setVisible(false);
                      onChange && onChange(optionValue, opt);
                    }}
                  >
                    <Text style={[styles.optionText, isSelected && styles.optionTextActive]}>{optionText}</Text>
                  </TouchableOpacity>
                  {idx !== options.length - 1 && <View style={styles.divider} />}
                </React.Fragment>
              );
            })}
          </View>
        </TouchableOpacity>
      </Modal>
    </View>
  );
};

const styles = StyleSheet.create({
  wrap: {
    position: 'relative',
    marginRight: zero.yWidth(8),
    justifyContent: 'space-between',
    zIndex: 10,
    // width: zero.yWidth(87), // 移除固定宽度
    // height: zero.yHeight(29.43), // 移除固定高度
  },
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#F5F7FA',
    borderRadius: zero.yHeight(10),
    paddingHorizontal: zero.yWidth(10),
    height: zero.yHeight(32),
    borderWidth: 1,
    borderColor: 'transparent',
    minWidth: zero.yWidth(70),
    justifyContent: 'space-between',
  },
  btnActive: {
    // borderColor: '#1890FF',
    // backgroundColor: '#E6F0FF',
  },
  btnText: {
    color: '#222',
    fontSize: zero.yFont(15),
  },
  btnTextActive: {
    color: '#000000',
    fontWeight: 'bold',
  },
  leftIcon:{
    width: zero.yWidth(18),
    height: zero.yHeight(18),
    marginRight:zero.yWidth(15),
    resizeMode: 'contain',
  },
  arrowIcon: {
    width: zero.yWidth(9.19),
    height: zero.yHeight(5.89),
    marginLeft: zero.yWidth(4),
    tintColor: '#1890FF',
    resizeMode: 'contain',
    transform: [{ rotate: '0deg' }],
  },
  arrowIconUp: {
    transform: [{ rotate: '180deg' }],
  },
  arrowActive: {
    tintColor: '#1890FF',
  },
  modalMask: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.1)',
  },
  dropdown: {
    backgroundColor: '#fff',
    borderRadius: zero.yHeight(10),
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: zero.yHeight(8),
    elevation: zero.yHeight(8),
    minWidth: zero.yWidth(80),
    overflow: 'hidden',
  },
  option: {
    paddingVertical: zero.yHeight(12),
    paddingHorizontal: zero.yWidth(20),
    backgroundColor: '#fff',
  },
  optionText: {
    color: '#222',
    fontSize: zero.yFont(15),
    textAlign: 'left',
  },
  optionTextActive: {
    color: '#1890FF',
    fontWeight: 'bold',
  },
  divider: {
    height: zero.yHeight(1),
    backgroundColor: '#F0F0F0',
    marginHorizontal: zero.yWidth(10),
  },
});

export default FilterDropdown;
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐