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

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

项目基于 RN 0.72.90 开发

📋 前言

在移动应用开发中,折叠/展开功能是一种常见的交互模式,广泛应用于手风琴列表、FAQ 页面、可折叠面板等场景。通过折叠隐藏次要内容,可以有效节省屏幕空间,提升信息展示效率。react-native-collapsible 是一个功能完善的折叠组件库,提供平滑的动画过渡效果,支持单个折叠组件和手风琴组件两种模式,是实现折叠交互的理想选择。

🎯 库简介

基本信息

  • 库名称 : react-native-collapsible
  • 版本信息 :
    • 1.6.1 支持 RN 0.72 版本
    • 1.6.2 支持 RN 0.77 版本
  • 官方仓库: https://github.com/oblador/react-native-collapsible
  • 主要功能 :
    • 📦 单个折叠组件(Collapsible)
    • 🎹 手风琴组件(Accordion)
    • ✨ 平滑动画过渡
    • 🎨 自定义动画效果
    • 📱 跨平台支持(iOS、Android、HarmonyOS)

为什么需要折叠组件?

特性 传统实现方式 react-native-collapsible
动画效果 ⚠️ 需自行实现 ✅ 内置平滑动画
高度自适应 ⚠️ 需手动计算 ✅ 自动计算高度
手风琴模式 ❌ 需自行开发 ✅ 内置 Accordion 组件
自定义配置 ⚠️ 代码复杂 ✅ 丰富的属性配置
性能优化 ⚠️ 需自行优化 ✅ 动画结束回调
HarmonyOS 支持 ❌ 无 ✅ 完善适配

核心功能

功能 说明 HarmonyOS 支持
Collapsible 单个折叠组件
Accordion 手风琴组件
collapsed 控制折叠状态
duration 动画持续时间
easing 动画缓动函数
onAnimationEnd 动画结束回调

兼容性验证

在以下环境验证通过:

  • RNOH : 0.72.90; SDK : HarmonyOS 6.0.0 (API Version 20); IDE : DevEco Studio 6.0.2; ROM: 6.0.0

📦 安装步骤

1. 安装依赖

bash 复制代码
# RN 0.72 版本(本项目使用)
npm install react-native-collapsible@1.6.1

# RN 0.77 版本
npm install react-native-collapsible@1.6.2

# 或者使用 yarn
yarn add react-native-collapsible@1.6.1

2. 验证安装

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

json 复制代码
{
  "dependencies": {
    "react-native-collapsible": "^1.6.1"
  }
}

🔧 HarmonyOS 平台配置

本库为纯 JavaScript 实现,无需额外的原生端配置。安装完成后即可直接使用。

📖 API 详解

Collapsible 组件

collapsed - 控制折叠状态

控制组件是否处于折叠状态。true 表示折叠,false 表示展开。

类型boolean

默认值true

使用场景

  • 手动控制折叠/展开
  • 响应用户交互
  • 条件渲染场景
ts 复制代码
import React, { useState } from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import Collapsible from "react-native-collapsible";

const CollapsibleExample = () => {
  const [isCollapsed, setIsCollapsed] = useState(true);

  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.header}
        onPress={() => setIsCollapsed(!isCollapsed)}
      >
        <Text style={styles.headerText}>
          {isCollapsed ? "展开内容" : "收起内容"}
        </Text>
      </TouchableOpacity>
    
      <Collapsible collapsed={isCollapsed}>
        <View style={styles.content}>
          <Text style={styles.contentText}>
            这是折叠组件的内容部分。点击上方按钮可以切换折叠状态。
          </Text>
        </View>
      </Collapsible>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 16,
  },
  header: {
    backgroundColor: "#007AFF",
    padding: 16,
    borderRadius: 8,
  },
  headerText: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "600",
  },
  content: {
    padding: 16,
    backgroundColor: "#F5F5F5",
    borderBottomLeftRadius: 8,
    borderBottomRightRadius: 8,
  },
  contentText: {
    fontSize: 14,
    color: "#333333",
    lineHeight: 20,
  },
});

export default CollapsibleExample;

duration - 动画持续时间

设置折叠/展开动画的持续时间,单位为毫秒。

类型number

默认值300

使用场景

  • 快速响应场景
  • 慢速优雅动画
  • 与其他动画同步
ts 复制代码
const FastCollapsible = () => {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <Collapsible collapsed={collapsed} duration={200}>
      <View style={styles.content}>
        <Text>快速动画(200ms)</Text>
      </View>
    </Collapsible>
  );
};

const SlowCollapsible = () => {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <Collapsible collapsed={collapsed} duration={800}>
      <View style={styles.content}>
        <Text>慢速动画(800ms)</Text>
      </View>
    </Collapsible>
  );
};

easing - 动画缓动函数

设置动画的缓动效果,可以使用 Easing 模块中的函数或函数名称字符串。

类型string | function

默认值'easeOutCubic'

常用缓动函数

函数名 效果描述
linear 线性动画,匀速
ease 标准缓动
easeIn 开始慢,结束快
easeOut 开始快,结束慢
easeInOut 开始和结束慢,中间快
easeOutCubic 三次方缓出(默认)
ts 复制代码
import { Easing } from "react-native";

const EasingExample = () => {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <View>
      {/* 使用字符串 */}
      <Collapsible collapsed={collapsed} easing="easeInOut">
        <Text>使用字符串缓动</Text>
      </Collapsible>

      {/* 使用 Easing 函数 */}
      <Collapsible collapsed={collapsed} easing={Easing.elastic(1)}>
        <Text>使用 Easing 函数</Text>
      </Collapsible>
    </View>
  );
};

collapsedHeight - 折叠时高度

设置组件折叠状态下保留的高度。默认为 0,即完全折叠。

类型number

默认值0

使用场景

  • 部分折叠
  • 预留空间
  • 显示摘要信息
ts 复制代码
const PartialCollapsible = () => {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <View>
      <TouchableOpacity onPress={() => setCollapsed(!collapsed)}>
        <Text>{collapsed ? "展开全文" : "收起"}</Text>
      </TouchableOpacity>
    
      <Collapsible collapsed={collapsed} collapsedHeight={60}>
        <View style={styles.content}>
          <Text>
            这是一段很长的文本内容,折叠时会保留前两行的高度,
            让用户能够预览部分内容。点击上方按钮可以展开查看完整内容。
            这种方式常用于新闻摘要、商品描述等场景。
          </Text>
        </View>
      </Collapsible>
    </View>
  );
};

align - 内容对齐方式

设置过渡动画期间内容的对齐方式。

类型'top' | 'center' | 'bottom'

默认值'top'

使用场景

  • 内容顶部对齐
  • 内容居中对齐
  • 内容底部对齐
ts 复制代码
const AlignExample = () => {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <View>
      <Collapsible collapsed={collapsed} align="center">
        <Text>内容居中对齐</Text>
      </Collapsible>
    
      <Collapsible collapsed={collapsed} align="bottom">
        <Text>内容底部对齐</Text>
      </Collapsible>
    </View>
  );
};

onAnimationEnd - 动画结束回调

动画完成时触发的回调函数,可用于执行后续操作。

类型() => void

使用场景

  • 动画完成后更新状态
  • 触发其他动画
  • 性能优化(避免动画期间的繁重操作)
ts 复制代码
const AnimationEndExample = () => {
  const [collapsed, setCollapsed] = useState(true);
  const [status, setStatus] = useState("已折叠");

  const handleAnimationEnd = () => {
    console.log("动画结束");
    setStatus(collapsed ? "已折叠" : "已展开");
  };

  return (
    <View>
      <TouchableOpacity onPress={() => setCollapsed(!collapsed)}>
        <Text>切换状态</Text>
      </TouchableOpacity>
    
      <Text>当前状态:{status}</Text>
    
      <Collapsible
        collapsed={collapsed}
        duration={500}
        onAnimationEnd={handleAnimationEnd}
      >
        <View style={styles.content}>
          <Text>折叠内容</Text>
        </View>
      </Collapsible>
    </View>
  );
};

Accordion 组件

Accordion 是一个手风琴组件,支持多个折叠区域的联动。

sections - 数据源

传递给 Accordion 的数据数组,每个元素代表一个可折叠的区域。

类型array

使用场景

  • FAQ 列表
  • 设置面板
  • 分类导航
ts 复制代码
import Accordion from "react-native-collapsible/Accordion";

const CONTENT = [
  {
    title: "什么是 React Native?",
    content: "React Native 是一个用于构建原生移动应用的框架。",
  },
  {
    title: "如何安装 React Native?",
    content: "可以使用 npm 或 yarn 安装 React Native CLI。",
  },
  {
    title: "React Native 的优势是什么?",
    content: "跨平台开发、热重载、原生性能、庞大的社区支持。",
  },
];

const AccordionExample = () => {
  const [activeSections, setActiveSections] = useState([0]);

  const renderHeader = (section, index, isActive) => {
    return (
      <View style={[styles.header, isActive && styles.activeHeader]}>
        <Text style={styles.headerText}>{section.title}</Text>
        <Text style={styles.arrow}>{isActive ? "▲" : "▼"}</Text>
      </View>
    );
  };

  const renderContent = (section) => {
    return (
      <View style={styles.content}>
        <Text style={styles.contentText}>{section.content}</Text>
      </View>
    );
  };

  return (
    <Accordion
      sections={CONTENT}
      activeSections={activeSections}
      renderHeader={renderHeader}
      renderContent={renderContent}
      onChange={setActiveSections}
    />
  );
};

renderHeader - 渲染标题

渲染每个区域标题的函数。

类型(content, index, isActive, sections) => ReactElement

参数说明

参数 类型 说明
content any 当前区域的数据
index number 当前区域的索引
isActive boolean 当前区域是否展开
sections array 所有区域数据
ts 复制代码
const renderHeader = (section, index, isActive) => {
  return (
    <View style={[styles.header, isActive && styles.activeHeader]}>
      <Text style={styles.headerText}>{section.title}</Text>
      <View style={styles.iconContainer}>
        <Text style={styles.icon}>{isActive ? "−" : "+"}</Text>
      </View>
    </View>
  );
};

renderContent - 渲染内容

渲染每个区域内容的函数。

类型(content, index, isActive, sections) => ReactElement

ts 复制代码
const renderContent = (section, index, isActive) => {
  return (
    <View style={styles.content}>
      <Text style={styles.contentText}>{section.content}</Text>
      {section.extra && (
        <Text style={styles.extraText}>{section.extra}</Text>
      )}
    </View>
  );
};

activeSections - 激活区域

控制哪些区域处于展开状态,使用索引数组表示。

类型number[]

使用场景

  • 控制默认展开区域
  • 响应用户操作
  • 单选/多选模式
ts 复制代码
const AccordionControlExample = () => {
  const [activeSections, setActiveSections] = useState([0]);

  return (
    <View>
      <Accordion
        sections={CONTENT}
        activeSections={activeSections}
        renderHeader={renderHeader}
        renderContent={renderContent}
        onChange={(activeSections) => {
          setActiveSections(activeSections);
        }}
        expandMultiple={true}
      />
    </View>
  );
};

expandMultiple - 多选模式

是否允许同时展开多个区域。

类型boolean

默认值false

ts 复制代码
const MultipleExpandExample = () => {
  const [activeSections, setActiveSections] = useState([]);

  return (
    <Accordion
      sections={CONTENT}
      activeSections={activeSections}
      renderHeader={renderHeader}
      renderContent={renderContent}
      onChange={setActiveSections}
      expandMultiple={true}
    />
  );
};

touchableComponent - 自定义触摸组件

自定义标题的触摸组件,默认为 TouchableHighlight

类型Component

使用场景

  • 自定义触摸反馈
  • 使用 TouchableOpacity
  • 使用自定义组件
ts 复制代码
import { TouchableOpacity } from "react-native";

const CustomTouchableExample = () => {
  return (
    <Accordion
      sections={CONTENT}
      activeSections={activeSections}
      renderHeader={renderHeader}
      renderContent={renderContent}
      onChange={setActiveSections}
      touchableComponent={TouchableOpacity}
      touchableProps={{
        activeOpacity: 0.7,
      }}
    />
  );
};

📋 完整示例

ts 复制代码
import React, { useState } from "react";
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  SafeAreaView,
  StatusBar,
} from "react-native";
import Collapsible from "react-native-collapsible";
import Accordion from "react-native-collapsible/Accordion";

type FAQItem = {
  title: string;
  content: string;
  category: string;
};

type SettingItem = {
  title: string;
  description: string;
  icon: string;
};

const FAQ_DATA: FAQItem[] = [
  {
    title: "如何重置密码?",
    content: "您可以在设置页面点击'重置密码'选项,系统会发送重置链接到您的注册邮箱,按照邮件指引完成密码重置。",
    category: "账户",
  },
  {
    title: "如何修改绑定的手机号?",
    content: "进入个人中心 -> 账户安全 -> 手机绑定,点击'更换手机号',验证当前手机号后即可绑定新号码。",
    category: "账户",
  },
  {
    title: "应用支持哪些支付方式?",
    content: "目前支持微信支付、支付宝、银行卡支付、信用卡支付等多种支付方式,您可以在结算时选择适合的支付方式。",
    category: "支付",
  },
  {
    title: "如何申请退款?",
    content: "在订单详情页面点击'申请退款',填写退款原因后提交申请。我们会在1-3个工作日内处理您的退款请求。",
    category: "支付",
  },
  {
    title: "如何清除缓存?",
    content: "进入设置 -> 存储 -> 清除缓存,点击确认即可清除应用缓存数据。注意:清除缓存不会删除您的账户信息和订单记录。",
    category: "设置",
  },
];

const SETTINGS_DATA: SettingItem[] = [
  { title: "消息通知", description: "管理推送通知设置", icon: "🔔" },
  { title: "隐私设置", description: "管理隐私和数据权限", icon: "🔒" },
  { title: "存储管理", description: "查看和管理存储空间", icon: "💾" },
  { title: "关于我们", description: "应用版本和公司信息", icon: "ℹ️" },
];

const App: React.FC = () => {
  const [activeFAQSections, setActiveFAQSections] = useState<number[]>([]);
  const [expandedSettings, setExpandedSettings] = useState<Record<string, boolean>>({});

  const toggleSetting = (title: string) => {
    setExpandedSettings((prev) => ({
      ...prev,
      [title]: !prev[title],
    }));
  };

  const renderFAQHeader = (section: FAQItem, index: number, isActive: boolean) => {
    return (
      <View
        style={[
          styles.faqHeader,
          isActive && styles.faqHeaderActive,
          index === 0 && styles.faqHeaderFirst,
        ]}
      >
        <View style={styles.faqHeaderLeft}>
          <View style={styles.categoryBadge}>
            <Text style={styles.categoryText}>{section.category}</Text>
          </View>
          <Text style={styles.faqTitle}>{section.title}</Text>
        </View>
        <Text style={styles.expandIcon}>{isActive ? "▲" : "▼"}</Text>
      </View>
    );
  };

  const renderFAQContent = (section: FAQItem) => {
    return (
      <View style={styles.faqContent}>
        <Text style={styles.faqContentText}>{section.content}</Text>
      </View>
    );
  };

  const renderSettingItem = (setting: SettingItem) => {
    const isExpanded = expandedSettings[setting.title];

    return (
      <View key={setting.title} style={styles.settingItem}>
        <TouchableOpacity
          style={styles.settingHeader}
          onPress={() => toggleSetting(setting.title)}
          activeOpacity={0.7}
        >
          <View style={styles.settingLeft}>
            <Text style={styles.settingIcon}>{setting.icon}</Text>
            <View>
              <Text style={styles.settingTitle}>{setting.title}</Text>
              <Text style={styles.settingDescription}>{setting.description}</Text>
            </View>
          </View>
          <Text style={styles.expandIcon}>{isExpanded ? "▲" : "▼"}</Text>
        </TouchableOpacity>

        <Collapsible collapsed={!isExpanded} duration={300}>
          <View style={styles.settingContent}>
            <Text style={styles.settingContentText}>
              这是"{setting.title}"的详细设置内容。您可以在这里进行相关配置。
            </Text>
            <TouchableOpacity style={styles.settingButton}>
              <Text style={styles.settingButtonText}>进入设置</Text>
            </TouchableOpacity>
          </View>
        </Collapsible>
      </View>
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
      <View style={styles.header}>
        <Text style={styles.headerTitle}>折叠组件示例</Text>
        <Text style={styles.headerSubtitle}>Collapsible & Accordion</Text>
      </View>

      <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>常见问题</Text>
          <Text style={styles.sectionSubtitle}>使用 Accordion 组件实现</Text>
        
          <Accordion
            sections={FAQ_DATA}
            activeSections={activeFAQSections}
            renderHeader={renderFAQHeader}
            renderContent={renderFAQContent}
            onChange={setActiveFAQSections}
            duration={300}
            touchableComponent={TouchableOpacity}
            expandMultiple={true}
          />
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>设置面板</Text>
          <Text style={styles.sectionSubtitle}>使用 Collapsible 组件实现</Text>
        
          {SETTINGS_DATA.map(renderSettingItem)}
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#F5F5F5",
  },
  header: {
    padding: 20,
    backgroundColor: "#FFFFFF",
    borderBottomWidth: 1,
    borderBottomColor: "#E5E5EA",
  },
  headerTitle: {
    fontSize: 24,
    fontWeight: "700",
    color: "#333333",
  },
  headerSubtitle: {
    fontSize: 14,
    color: "#999999",
    marginTop: 4,
  },
  scrollView: {
    flex: 1,
  },
  section: {
    padding: 16,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: "600",
    color: "#333333",
    marginBottom: 4,
  },
  sectionSubtitle: {
    fontSize: 12,
    color: "#999999",
    marginBottom: 12,
  },
  faqHeader: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    backgroundColor: "#FFFFFF",
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: "#E5E5EA",
  },
  faqHeaderActive: {
    backgroundColor: "#F0F8FF",
  },
  faqHeaderFirst: {
    borderTopLeftRadius: 12,
    borderTopRightRadius: 12,
  },
  faqHeaderLeft: {
    flex: 1,
    marginRight: 12,
  },
  categoryBadge: {
    alignSelf: "flex-start",
    backgroundColor: "#E3F2FD",
    paddingHorizontal: 8,
    paddingVertical: 2,
    borderRadius: 4,
    marginBottom: 8,
  },
  categoryText: {
    fontSize: 10,
    color: "#1976D2",
    fontWeight: "500",
  },
  faqTitle: {
    fontSize: 15,
    fontWeight: "500",
    color: "#333333",
  },
  expandIcon: {
    fontSize: 12,
    color: "#999999",
  },
  faqContent: {
    backgroundColor: "#FAFAFA",
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: "#E5E5EA",
  },
  faqContentText: {
    fontSize: 14,
    color: "#666666",
    lineHeight: 22,
  },
  settingItem: {
    backgroundColor: "#FFFFFF",
    borderRadius: 12,
    marginBottom: 12,
    overflow: "hidden",
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.05,
    shadowRadius: 4,
    elevation: 2,
  },
  settingHeader: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    padding: 16,
  },
  settingLeft: {
    flexDirection: "row",
    alignItems: "center",
    flex: 1,
  },
  settingIcon: {
    fontSize: 24,
    marginRight: 12,
  },
  settingTitle: {
    fontSize: 16,
    fontWeight: "500",
    color: "#333333",
  },
  settingDescription: {
    fontSize: 12,
    color: "#999999",
    marginTop: 2,
  },
  settingContent: {
    padding: 16,
    paddingTop: 0,
    borderTopWidth: 1,
    borderTopColor: "#E5E5EA",
  },
  settingContentText: {
    fontSize: 14,
    color: "#666666",
    lineHeight: 20,
    marginTop: 12,
  },
  settingButton: {
    backgroundColor: "#007AFF",
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderRadius: 8,
    alignItems: "center",
    marginTop: 12,
  },
  settingButtonText: {
    color: "#FFFFFF",
    fontSize: 14,
    fontWeight: "500",
  },
});

export default App;

⚠️ 注意事项

性能优化

避免在动画期间执行繁重操作:

tsx 复制代码
// ✅ 推荐:在动画结束后执行
<Collapsible
  collapsed={collapsed}
  onAnimationEnd={() => {
    // 动画结束后执行繁重操作
    updateLayout();
  }}
>
  <Content />
</Collapsible>

// ❌ 不推荐:在动画期间执行
<Collapsible collapsed={collapsed}>
  <Content onLayout={heavyCalculation} />
</Collapsible>

高度计算

对于动态高度内容,确保内容渲染完成后再展开:

tsx 复制代码
const DynamicHeightExample = () => {
  const [collapsed, setCollapsed] = useState(true);
  const [contentHeight, setContentHeight] = useState(0);

  return (
    <Collapsible collapsed={collapsed}>
      <View
        onLayout={(e) => {
          setContentHeight(e.nativeEvent.layout.height);
        }}
      >
        <DynamicContent />
      </View>
    </Collapsible>
  );
};

常见问题

Q: 动画不流畅?

A: 检查是否有频繁的状态更新或繁重的渲染操作,可以使用 onAnimationEnd 回调在动画完成后执行。

Q: 内容高度不正确?

A: 确保内容组件有明确的高度或使用 onLayout 获取实际高度。

Q: Accordion 单选模式如何实现?

A: 设置 expandMultiple={false},并确保 activeSections 数组只包含一个索引。

📚 参考资料

相关推荐
前端Hardy2 小时前
别再手写代码了!2026 前端 5 个 AI 杀招,直接解放 80% 重复劳动(附工具+步骤)
前端·javascript·面试
SuperEugene2 小时前
Element Plus/VXE-Table UI 组件库规范:统一用法实战,避开样式冲突与维护混乱|工程化与协作规范篇
前端·javascript·vue.js·ui·前端框架·element plus·vxetable
前端Hardy2 小时前
前端工程师必备的 10 个 AI 万能提示词(Prompt),复制直接用,效率再翻倍!
前端·javascript·面试
BioRunYiXue2 小时前
Nature Methods:CellVoyager 自主 AI 智能体开启生物数据分析新时代
大数据·开发语言·前端·javascript·人工智能·数据挖掘·数据分析
网络点点滴3 小时前
Vue3中Suspense的使用
前端·javascript·vue.js
酉鬼女又兒4 小时前
零基础快速入门前端Web存储(sessionStorage & localStorage)知识点详解与蓝桥杯考点应用(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·职场和发展·蓝桥杯·html
angerdream4 小时前
最新版vue3+TypeScript开发入门到实战教程之组件通信之二
javascript·vue.js
小只笨笨狗~4 小时前
解决objectSpanMethod与expand共存时展开后表格错位问题
开发语言·javascript·ecmascript
海浪浪4 小时前
Symbol 产生的背景以及应用场景
前端·javascript