欢迎加入开源鸿蒙跨平台社区 :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 数组只包含一个索引。