ReactNative项目OpenHarmony三方库集成实战:react-native-dropdown-picker

欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net

📋 前言

下拉选择是移动应用中使用最频繁的交互组件之一,无论是筛选条件、表单输入还是配置选项,都离不开下拉选择器的身影。react-native-dropdown-picker 是一个功能强大的下拉选择器库,支持单选/多选、搜索过滤、分组显示等功能,让开发者能够轻松实现原生般的下拉选择体验。

🎯 库简介

基本信息

  • 库名称 : react-native-dropdown-picker
  • 版本信息 :
    • 5.4.6 + react-native-dropdown-picker: 支持 RN 0.72 / 0.77 版本
  • 官方仓库: https://github.com/hossein-zare/react-native-dropdown-picker
  • 主要功能 :
    • 📋 单选/多选模式
    • 🔍 搜索过滤功能
    • 📁 分组显示支持
    • 🎨 丰富的样式定制
    • 📱 跨平台一致体验

为什么需要下拉选择器?

特性 原生 Picker react-native-dropdown-picker
跨平台一致性 ❌ 表现各异 ✅ 统一体验
搜索功能 ❌ 无 ✅ 内置搜索
多选支持 ⚠️ 需自定义 ✅ 原生支持
分组显示 ❌ 无 ✅ 支持分类
样式定制 ⚠️ 受限 ✅ 完全可定制
HarmonyOS 支持 ❌ 无 ✅ 完善适配

核心功能

功能 说明 HarmonyOS 支持
单选模式 从列表中选择一项
多选模式 同时选择多项
搜索过滤 输入关键字过滤选项
分组显示 按分类展示选项
禁用状态 禁用选择器
加载状态 显示加载指示器
自定义图标 箭头/勾选图标定制

兼容性验证

在以下环境验证通过:

  • RNOH : 0.72.90; SDK : HarmonyOS 6.0.0; IDE : DevEco Studio 6.0.02; ROM: 6.0.0

📦 安装步骤

1. 安装依赖

bash 复制代码
npm install react-native-dropdown-picker@5.4.6

# 或者使用 yarn
yarn add react-native-dropdown-picker@5.4.6

2. 验证安装

安装完成后,检查 package.json 文件:

json 复制代码
{
  "dependencies": {
    "react-native-dropdown-picker": "^5.4.6"
  }
}

📖 API 详解

tsx 复制代码
import React, { useState } from 'react';
import { View } from 'react-native';
import DropDownPicker from 'react-native-dropdown-picker';

const App = () => {
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState(null);
  const [items, setItems] = useState([
    { label: '苹果', value: 'apple' },
    { label: '香蕉', value: 'banana' },
    { label: '橙子', value: 'orange' },
  ]);

  return (
    <View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
      <DropDownPicker
        open={open}
        value={value}
        items={items}
        setOpen={setOpen}
        setValue={setValue}
        setItems={setItems}
      />
    </View>
  );
};

基础属性

items - 选项列表
tsx 复制代码
const [items, setItems] = useState([
  { label: '苹果', value: 'apple' },
  { label: '香蕉', value: 'banana' },
  { label: '橙子', value: 'orange' },
]);

<DropDownPicker items={items} ... />
value - 当前选中值
tsx 复制代码
// 单选
const [value, setValue] = useState<string | null>(null);

// 多选
const [value, setValue] = useState<string[]>([]);

<DropDownPicker value={value} ... />
open / setOpen - 控制展开状态
tsx 复制代码
const [open, setOpen] = useState(false);

<DropDownPicker open={open} setOpen={setOpen} ... />
setValue - 值变化回调
tsx 复制代码
const [value, setValue] = useState(null);

<DropDownPicker
  value={value}
  setValue={(callback) => {
    // callback 可能是值或函数
    setValue(prev => callback(prev));
  }}
  ...
/>
multiple - 多选模式
tsx 复制代码
const [value, setValue] = useState<string[]>([]);

<DropDownPicker
  multiple={true}
  value={value}
  min={1}  // 最少选1个
  max={3}  // 最多选3个
  ...
/>
disabled - 禁用状态
tsx 复制代码
<DropDownPicker disabled={true} ... />

显示属性

placeholder - 占位文本
tsx 复制代码
<DropDownPicker
  placeholder="请选择水果"
  placeholderStyle={{ color: '#999' }}
  ...
/>
maxHeight - 下拉框最大高度
tsx 复制代码
<DropDownPicker maxHeight={200} ... />
zIndex - 堆叠顺序(多个下拉框时需要)
tsx 复制代码
// 第一个下拉框
<DropDownPicker zIndex={1000} zIndexInverse={3000} ... />

// 第二个下拉框
<DropDownPicker zIndex={2000} zIndexInverse={2000} ... />

// 第三个下拉框
<DropDownPicker zIndex={3000} zIndexInverse={1000} ... />
showArrowIcon / showTickIcon - 图标显示
tsx 复制代码
<DropDownPicker
  showArrowIcon={true}   // 显示下拉箭头
  showTickIcon={true}    // 显示选中勾选
  ...
/>
listMode - 列表渲染模式(重要)

DropDownPicker 嵌套在 ScrollView 中时,必须设置 listMode="SCROLLVIEW"listMode="MODAL",否则会报错 "VirtualizedLists should never be nested inside plain ScrollViews"。

tsx 复制代码
// 方式一:使用 SCROLLVIEW 模式(推荐)
<DropDownPicker
  listMode="SCROLLVIEW"
  ...
/>

// 方式二:使用 MODAL 模式(弹出模态框)
<DropDownPicker
  listMode="MODAL"
  ...
/>
listMode 值 说明
DEFAULT 默认模式,使用 FlatList(不能嵌套在 ScrollView 中)
SCROLLVIEW 使用 ScrollView 渲染,可嵌套在 ScrollView 中
MODAL 弹出模态框显示选项

控制下拉框的展开方向,默认为 AUTO(自动判断)。

tsx 复制代码
<DropDownPicker
  dropDownDirection="BOTTOM"  // 始终向下展开
  ...
/>
dropDownDirection 值 说明
AUTO 自动判断(默认),根据空间决定向上或向下
TOP 始终向上展开
BOTTOM 始终向下展开

样式属性

tsx 复制代码
<DropDownPicker
  style={{
    backgroundColor: '#fff',
    borderColor: '#ddd',
    borderRadius: 8,
  }}
  containerStyle={{ marginBottom: 20 }}
  textStyle={{ fontSize: 16, color: '#333' }}
  labelStyle={{ fontWeight: 'bold' }}
  ...
/>

搜索属性

searchable - 启用搜索
tsx 复制代码
<DropDownPicker
  searchable={true}
  searchPlaceholder="搜索城市..."
  searchTextInputProps={{
    maxLength: 25,
  }}
  onChangeSearchText={(text) => {
    console.log('搜索:', text);
  }}
  ...
/>
addCustomItem - 添加自定义选项
tsx 复制代码
<DropDownPicker
  searchable={true}
  addCustomItem={true}  // 搜索无结果时可添加
  ...
/>

图标属性

自定义图标组件
tsx 复制代码
import { Text } from 'react-native';

<DropDownPicker
  ArrowUpIconComponent={() => <Text>▲</Text>}
  ArrowDownIconComponent={() => <Text>▼</Text>}
  TickIconComponent={() => <Text>✓</Text>}
  ...
/>

加载属性

tsx 复制代码
import { ActivityIndicator } from 'react-native';

<DropDownPicker
  loading={isLoading}
  activityIndicatorColor="#007AFF"
  activityIndicatorSize={20}
  ActivityIndicatorComponent={() => (
    <ActivityIndicator size="small" color="#007AFF" />
  )}
  ...
/>

事件属性

tsx 复制代码
<DropDownPicker
  onChangeValue={(value) => {
    console.log('值变化:', value);
  }}
  onSelectItem={(item) => {
    console.log('选中项:', item);
  }}
  onOpen={() => {
    console.log('下拉框展开');
  }}
  onClose={() => {
    console.log('下拉框关闭');
  }}
  ...
/>

分组功能

tsx 复制代码
const [items, setItems] = useState([
  // 分组标题(不可选)
  { label: '苹果手机', value: 'apple_header', selectable: false },
  { label: 'iPhone 14', value: 'iphone14', parent: 'apple_header' },
  { label: 'iPhone 15', value: 'iphone15', parent: 'apple_header' },
  
  // 另一个分组
  { label: '三星手机', value: 'samsung_header', selectable: false },
  { label: 'Galaxy S23', value: 's23', parent: 'samsung_header' },
  { label: 'Galaxy S24', value: 's24', parent: 'samsung_header' },
]);

<DropDownPicker items={items} ... />

Item 对象结构

typescript 复制代码
interface Item {
  label: string;           // 显示文本
  value: string;           // 值
  icon?: any;              // 图标组件
  disabled?: boolean;      // 是否禁用
  parent?: string;         // 父级值(用于分组)
  selectable?: boolean;    // 是否可选,false时作为分组标题
}

📋 完整示例

综合示例(单选/多选/搜索/分组/表单)

ts 复制代码
import React, { useState } from "react";
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  ScrollView,
  StyleSheet,
} from "react-native";
import DropDownPicker from "react-native-dropdown-picker";

interface Item {
  label: string;
  value: string;
  parent?: string;
  selectable?: boolean;
}

const App: React.FC = () => {
  // 基础单选状态
  const [fruitOpen, setFruitOpen] = useState(false);
  const [fruitValue, setFruitValue] = useState<string | null>(null);
  const [fruitItems, setFruitItems] = useState<Item[]>([
    { label: "苹果", value: "apple" },
    { label: "香蕉", value: "banana" },
    { label: "橙子", value: "orange" },
    { label: "葡萄", value: "grape" },
  ]);

  // 多选状态
  const [multiOpen, setMultiOpen] = useState(false);
  const [multiValue, setMultiValue] = useState<string[]>([]);
  const [multiItems, setMultiItems] = useState<Item[]>([
    { label: "阅读", value: "reading" },
    { label: "旅游", value: "traveling" },
    { label: "运动", value: "sports" },
    { label: "音乐", value: "music" },
    { label: "电影", value: "movies" },
    { label: "美食", value: "food" },
  ]);

  // 搜索状态
  const [cityOpen, setCityOpen] = useState(false);
  const [cityValue, setCityValue] = useState<string | null>(null);
  const [cityItems, setCityItems] = useState<Item[]>([
    { label: "北京市", value: "beijing" },
    { label: "上海市", value: "shanghai" },
    { label: "广州市", value: "guangzhou" },
    { label: "深圳市", value: "shenzhen" },
    { label: "杭州市", value: "hangzhou" },
    { label: "南京市", value: "nanjing" },
    { label: "武汉市", value: "wuhan" },
    { label: "成都市", value: "chengdu" },
    { label: "西安市", value: "xian" },
    { label: "重庆市", value: "chongqing" },
  ]);

  // 分组状态
  const [productOpen, setProductOpen] = useState(false);
  const [productValue, setProductValue] = useState<string | null>(null);
  const [productItems, setProductItems] = useState<Item[]>([
    { label: "苹果手机", value: "apple", selectable: false },
    { label: "iPhone 14", value: "iphone14", parent: "apple" },
    { label: "iPhone 14 Pro", value: "iphone14pro", parent: "apple" },
    { label: "三星手机", value: "samsung", selectable: false },
    { label: "Galaxy S23", value: "galaxys23", parent: "samsung" },
    { label: "Galaxy S23 Ultra", value: "galaxys23ultra", parent: "samsung" },
    { label: "小米手机", value: "xiaomi", selectable: false },
    { label: "Mi 13", value: "mi13", parent: "xiaomi" },
    { label: "Mi 13 Pro", value: "mi13pro", parent: "xiaomi" },
  ]);

  // 表单状态 - 独立状态,不与上面示例共享
  const [formFruitOpen, setFormFruitOpen] = useState(false);
  const [formFruitValue, setFormFruitValue] = useState<string | null>(null);
  const [formFruitItems, setFormFruitItems] = useState<Item[]>([
    { label: "苹果", value: "apple" },
    { label: "香蕉", value: "banana" },
    { label: "橙子", value: "orange" },
  ]);

  const [formCityOpen, setFormCityOpen] = useState(false);
  const [formCityValue, setFormCityValue] = useState<string | null>(null);
  const [formCityItems, setFormCityItems] = useState<Item[]>([
    { label: "北京市", value: "beijing" },
    { label: "上海市", value: "shanghai" },
    { label: "广州市", value: "guangzhou" },
  ]);

  const [formGenderOpen, setFormGenderOpen] = useState(false);
  const [formGenderValue, setFormGenderValue] = useState<string | null>(null);
  const [formGenderItems, setFormGenderItems] = useState<Item[]>([
    { label: "男", value: "male" },
    { label: "女", value: "female" },
    { label: "保密", value: "secret" },
  ]);

  const getLabel = (items: Item[], val: string | null): string => {
    return items.find((i) => i.value === val)?.label || "-";
  };

  const handleSubmit = () => {
    alert(
      "表单数据",
      `水果: ${getLabel(formFruitItems, formFruitValue)}\n` +
      `城市: ${getLabel(formCityItems, formCityValue)}\n` +
      `性别: ${getLabel(formGenderItems, formGenderValue)}`
    );
  };

  return (
    <ScrollView style={styles.container}>
      <Text style={styles.header}>react-native-dropdown-picker 示例</Text>

      {/* 基础单选 */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>基础单选</Text>
        <DropDownPicker
          open={fruitOpen}
          value={fruitValue}
          items={fruitItems}
          setOpen={setFruitOpen}
          setValue={setFruitValue}
          setItems={setFruitItems}
          placeholder="请选择水果"
          listMode="SCROLLVIEW"
          dropDownDirection="BOTTOM"
          style={styles.dropdown}
          dropDownContainerStyle={styles.dropdownContainer}
        />
        {fruitValue && (
          <Text style={styles.resultText}>选择: {getLabel(fruitItems, fruitValue)}</Text>
        )}
      </View>

      {/* 多选模式 */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>多选模式(选择爱好)</Text>
        <DropDownPicker
          multiple
          min={1}
          max={4}
          open={multiOpen}
          value={multiValue}
          items={multiItems}
          setOpen={setMultiOpen}
          setValue={setMultiValue}
          setItems={setMultiItems}
          placeholder="请选择爱好(1-4项)"
          listMode="SCROLLVIEW"
          dropDownDirection="BOTTOM"
          mode="BADGE"
          badgeDotColors={["#5387D8", "#50C7C7", "#FF8C00", "#FF6B6B"]}
          style={styles.dropdown}
          dropDownContainerStyle={styles.dropdownContainer}
        />
        {multiValue.length > 0 && (
          <View style={styles.badgeContainer}>
            {multiValue.map((v) => (
              <View key={v} style={styles.badge}>
                <Text style={styles.badgeText}>
                  {multiItems.find((i) => i.value === v)?.label}
                </Text>
              </View>
            ))}
          </View>
        )}
      </View>

      {/* 搜索功能 */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>搜索过滤</Text>
        <DropDownPicker
          open={cityOpen}
          value={cityValue}
          items={cityItems}
          setOpen={setCityOpen}
          setValue={setCityValue}
          setItems={setCityItems}
          searchable
          searchPlaceholder="输入城市名称..."
          searchTextInputStyle={styles.searchInput}
          placeholder="请选择城市"
          listMode="SCROLLVIEW"
          dropDownDirection="BOTTOM"
          style={styles.dropdown}
          dropDownContainerStyle={styles.dropdownContainer}
        />
        {cityValue && (
          <Text style={styles.resultText}>选择: {getLabel(cityItems, cityValue)}</Text>
        )}
      </View>

      {/* 分组显示 */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>分组选择</Text>
        <DropDownPicker
          open={productOpen}
          value={productValue}
          items={productItems}
          setOpen={setProductOpen}
          setValue={setProductValue}
          setItems={setProductItems}
          placeholder="请选择产品"
          categorySelectable
          stickyHeader
          listMode="SCROLLVIEW"
          dropDownDirection="BOTTOM"
          style={styles.dropdown}
          dropDownContainerStyle={styles.dropdownContainer}
        />
        {productValue && (
          <Text style={styles.resultText}>选择: {getLabel(productItems, productValue)}</Text>
        )}
      </View>

      {/* 表单集成 */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>表单集成</Text>
        <View style={styles.formCard}>
          <View style={styles.inputGroup}>
            <Text style={styles.label}>水果</Text>
            <DropDownPicker
              open={formFruitOpen}
              value={formFruitValue}
              items={formFruitItems}
              setOpen={setFormFruitOpen}
              setValue={setFormFruitValue}
              setItems={setFormFruitItems}
              placeholder="请选择"
              listMode="SCROLLVIEW"
              dropDownDirection="BOTTOM"
              style={styles.smallDropdown}
              dropDownContainerStyle={styles.smallDropdownContainer}
              zIndex={1000}
            />
          </View>

          <View style={styles.inputGroup}>
            <Text style={styles.label}>城市</Text>
            <DropDownPicker
              open={formCityOpen}
              value={formCityValue}
              items={formCityItems}
              setOpen={setFormCityOpen}
              setValue={setFormCityValue}
              setItems={setFormCityItems}
              searchable
              placeholder="请选择"
              listMode="SCROLLVIEW"
              dropDownDirection="BOTTOM"
              style={styles.smallDropdown}
              dropDownContainerStyle={styles.smallDropdownContainer}
              zIndex={999}
            />
          </View>

          <View style={styles.inputGroup}>
            <Text style={styles.label}>性别</Text>
            <DropDownPicker
              open={formGenderOpen}
              value={formGenderValue}
              items={formGenderItems}
              setOpen={setFormGenderOpen}
              setValue={setFormGenderValue}
              setItems={setFormGenderItems}
              placeholder="请选择"
              listMode="SCROLLVIEW"
              dropDownDirection="BOTTOM"
              style={styles.smallDropdown}
              dropDownContainerStyle={styles.smallDropdownContainer}
              zIndex={998}
            />
          </View>

          <TouchableOpacity style={styles.submitButton} onPress={handleSubmit}>
            <Text style={styles.submitButtonText}>提交</Text>
          </TouchableOpacity>
        </View>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: "#F5F5F5",
  },
  header: {
    fontSize: 24,
    fontWeight: "bold",
    color: "#333",
    textAlign: "center",
    marginBottom: 30,
  },
  section: {
    marginBottom: 24,
    zIndex: 0,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: "600",
    color: "#333",
    marginBottom: 12,
  },
  dropdown: {
    backgroundColor: "#FFF",
    borderRadius: 8,
    borderWidth: 1,
    borderColor: "#E5E5EA",
  },
  dropdownContainer: {
    backgroundColor: "#FFF",
    borderRadius: 8,
    borderWidth: 1,
    borderColor: "#E5E5EA",
  },
  smallDropdown: {
    backgroundColor: "#FFF",
    borderRadius: 8,
    borderWidth: 1,
    borderColor: "#E5E5EA",
    minHeight: 44,
  },
  smallDropdownContainer: {
    backgroundColor: "#FFF",
    borderRadius: 8,
    borderWidth: 1,
    borderColor: "#E5E5EA",
  },
  searchInput: {
    borderBottomWidth: 1,
    borderBottomColor: "#E5E5EA",
  },
  resultText: {
    marginTop: 12,
    fontSize: 14,
    color: "#007AFF",
  },
  badgeContainer: {
    flexDirection: "row",
    flexWrap: "wrap",
    gap: 8,
    marginTop: 12,
  },
  badge: {
    backgroundColor: "#007AFF",
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
  },
  badgeText: {
    color: "#FFF",
    fontSize: 12,
  },
  formCard: {
    backgroundColor: "#FFF",
    borderRadius: 12,
    padding: 16,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  inputGroup: {
    marginBottom: 16,
    zIndex: 10,
  },
  label: {
    fontSize: 14,
    color: "#666",
    marginBottom: 8,
  },
  submitButton: {
    backgroundColor: "#007AFF",
    borderRadius: 8,
    paddingVertical: 14,
    alignItems: "center",
    marginTop: 8,
  },
  submitButtonText: {
    color: "#FFF",
    fontSize: 16,
    fontWeight: "600",
  },
});

export default App;

⚠️ 约束与限制

注意事项

  1. Z-Index 层级 :当下拉框在页面底部时,可能会被其他元素遮挡。需要通过 zIndex 属性调整层级。

  2. FlatList 嵌套 :如果在下拉框内使用 FlatList,确保正确设置 maxHeight 避免显示问题。

  3. 多选模式 Badge :当 multiple={true} 时,可以配合 mode="BADGE" 显示选中数量徽章。

样式建议

  1. 统一样式:建议在全局定义统一样式常量,确保整个应用的 DropDownPicker 样式一致。

  2. 安全区域:如果应用使用了安全区域(如刘海屏),需要适当调整下拉框位置。

  3. 键盘处理:如果下拉框在表单中,建议在展开时自动调整滚动位置避免键盘遮挡。


📚 更多资源

相关推荐
跟着珅聪学java9 小时前
js编写中文转unicode 教程
前端·javascript·数据库
英俊潇洒美少年9 小时前
Vue3 深入响应式系统
前端·javascript·vue.js
颜酱9 小时前
回溯算法实战练习(3)
javascript·后端·算法
英俊潇洒美少年11 小时前
React 最核心 3 大底层原理:Fiber + Diff + 事件系统
前端·react.js·前端框架
我命由我1234511 小时前
React Router 6 - 概述、基础路由、重定向、NavLink、路由表
前端·javascript·react.js·前端框架·ecmascript·html5·js
yaaakaaang11 小时前
(四)前端,如此简单!---Promise
前端·javascript
aini_lovee11 小时前
C# 实现邮件发送源码(支持附件)
开发语言·javascript·c#
GISer_Jing12 小时前
ReAct规划原理实战指南
前端·react.js·ai·aigc
英俊潇洒美少年12 小时前
js 进程与线程的讲解
javascript