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

📌 开发环境声明:本文基于 React Native 0.72.90 版本进行开发适配


🚀 一、开篇引言

PDF 文档查看是移动应用中常见的需求,无论是电子书阅读、合同签署还是报表展示,都需要一个稳定高效的 PDF 渲染组件。react-native-pdf 是 React Native 社区广泛使用的 PDF 查看组件,支持从 URL、本地文件等多种来源加载 PDF 文档,并提供丰富的交互功能。本文将带你深入了解如何在 HarmonyOS 平台上集成和使用这个实用的文档组件。

1.1 你将学到什么?

  • ✅ react-native-pdf 的核心概念与工作原理
  • ✅ HarmonyOS 平台的完整集成流程
  • ✅ 多种 PDF 资源加载方式
  • ✅ PDF API 的深度解析
  • ✅ 实际应用场景的最佳实践

1.2 适用人群

  • 正在进行 React Native 鸿蒙化迁移的开发者
  • 需要展示 PDF 文档的应用开发者
  • 对跨平台文档组件开发感兴趣的技术爱好者

1.3 为什么选择 react-native-pdf?

特点 说明
功能丰富 支持缩放、翻页、密码保护等多种功能
跨平台一致 iOS、Android、HarmonyOS 表现一致
高性能 原生渲染,支持大文件加载
回调完善 支持加载进度、页面变化、错误处理等回调
灵活配置 支持自定义缩放范围、页面间距等

📦 二、库概览

2.1 基本信息

项目 内容
库名称 @react-native-ohos/react-native-pdf
原库名称 react-native-pdf
版本信息 6.7.5 (RN 0.72) / 6.8.0 (RN 0.77)
官方仓库 https://github.com/wonday/react-native-pdf
鸿蒙仓库 https://gitcode.com/openharmony-sig/rntpc_react-native-pdf
开源协议 MIT

2.2 版本兼容性

三方库版本 支持RN版本 是否支持Autolink
~6.8.0 0.77 No
~6.7.5 0.72 Yes
<=6.7.4-0.3.6@deprecated 0.72 No

2.3 核心能力矩阵

能力项 描述 HarmonyOS 支持
URL 加载 从网络 URL 加载 PDF ✅ 完全支持
本地文件加载 从本地资源加载 PDF ✅ 完全支持
加载进度回调 onLoadProgress 回调 ✅ 完全支持
加载完成回调 onLoadComplete 回调 ✅ 部分支持
错误回调 onError 回调 ✅ 完全支持
页面变化回调 onPageChanged 回调 ✅ 完全支持
缩放控制 scale/minScale/maxScale ✅ 完全支持
密码保护 password 属性 ✅ 完全支持
页面间距 spacing 属性 ✅ 完全支持
适配策略 fitPolicy 属性 ✅ 完全支持
链接点击 onPressLink 回调 ✅ 完全支持
缩放变化回调 onScaleChanged 回调 ✅ 完全支持
设置页码 setPage 静态方法 ✅ 完全支持
横向滚动 horizontal 属性 ❌ 不支持
自适应宽度 fitWidth 属性 ❌ 不支持
双击缩放 enableDoubleTapZoom 属性 ❌ 不支持
分页显示 enablePaging 属性 ❌ 不支持

2.4 技术架构图

原生平台层

Bridge Layer

React Native 应用层

Pdf Component

source

scale

Native Module

PdfViewPackage

PdfViewManager

Android
PdfViewer

iOS
PDFKit

HarmonyOS
Web组件

2.5 典型应用场景

场景 描述 示例
电子书阅读 在线/离线 PDF 阅读 📚 小说、教材
合同签署 展示合同文档 📝 电子合同、协议
报表展示 数据报表 PDF 展示 📊 财务报表、分析报告
文档预览 附件文档预览 📄 邮件附件、通知

⚡ 三、快速开始

3.1 环境要求

依赖项 版本要求
React Native 0.72.x / 0.77.x
RNOH (鸿蒙框架) 0.72.90 / 0.77.18
HarmonyOS SDK 6.0.0.47+ (API 20)
DevEco Studio 5.0.3+ / 6.0+
Node.js 16.18.0+ / 18.x

3.2 一键安装

创建鸿蒙项目的过程不再进行描述,不懂得看这篇:https://blog.csdn.net/u011178696/article/details/151932277

npm install @react-native-ohos/react-native-pdf@6.7.5rc.1

或使用 yarn:

yarn add @react-native-ohos/react-native-pdf@6.7.5rc.1

3.3 验证安装

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

{
  "dependencies": {
    "@react-native-ohos/react-native-pdf": "^6.7.5rc.1"
  }
}

🔧 四、HarmonyOS 平台配置

4.1 AutoLink 配置

步骤 1:配置 oh-package.json5

在项目根目录的 oh-package.json5 文件中添加(这里要根据自己的实际情况来,你的不一定是0.72.90,也可能是0.72.96啥的):
在这里插入图片描述

{
  "overrides": {
    "@rnoh/react-native-openharmony": "./react_native_openharmony"
  }
}
步骤 2:引入 HAR 包

entry/oh-package.json5 文件中添加依赖:
在这里插入图片描述

"dependencies": {
  "@rnoh/react-native-openharmony": "0.72.90",
  "@react-native-ohos/react-native-pdf": "file:../../node_modules/@react-native-ohos/react-native-pdf/harmony/pdfview.har"
}
步骤 3:配置 CMakeLists.txt

entry/src/main/cpp/CMakeLists.txt 文件中添加:

+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")

+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-pdf/src/main/cpp" ./pdfview)

+ target_link_libraries(rnoh_app PUBLIC rnoh_pdf_view)
步骤 4:配置 PackageProvider.cpp

entry/src/main/cpp/PackageProvider.cpp 文件中添加:

+ #include "PdfViewPackage.h"

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
+     std::make_shared<PdfViewPackage>(ctx)
    };
}
步骤 5:在 ArkTS 侧引入组件

entry/src/main/ets/pages/index.etsentry/src/main/ets/rn/LoadBundle.ets 文件中添加:
在这里插入图片描述

+ import { RTNPdfView, PDF_VIEW_TYPE } from '@react-native-ohos/react-native-pdf';

@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {
+ if (ctx.componentName === PDF_VIEW_TYPE) {
+   RTNPdfView({
+     ctx: ctx.rnComponentContext,
+     tag: ctx.tag,
+   })
+ }
}

const arkTsComponentNames: Array<string> = [
+ PDF_VIEW_TYPE
];

📱 五、基础使用

5.1 从 URL 加载 PDF

最基础的 PDF 加载方式:

import Pdf from 'react-native-pdf';

<Pdf
  source={{ uri: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' }}
  style={{ flex: 1 }}
  onLoadComplete={(numberOfPages) => {
    console.log(`${numberOfPages}`);
  }}
  onError={(error) => {
    console.log('PDF 加载失败', error);
  }}
/>

5.2 从本地资源加载 PDF

从本地资源加载 PDF:

const source = require('./test.pdf');

<Pdf
  source={source}
  style={{ flex: 1 }}
/>

5.3 带密码的 PDF

加载加密的 PDF 文档:

<Pdf
  source={{ uri: 'https://example.com/protected.pdf' }}
  password="your_password"
  style={{ flex: 1 }}
/>

🎨 六、进阶用法

6.1 缩放控制

设置缩放范围和初始缩放比例:

<Pdf
  source={{ uri: 'https://example.com/document.pdf' }}
  scale={1.0}
  minScale={0.5}
  maxScale={3.0}
  style={{ flex: 1 }}
/>

6.2 页面间距和适配策略

<Pdf
  source={{ uri: 'https://example.com/document.pdf' }}
  spacing={10}
  fitPolicy={2}
  style={{ flex: 1 }}
/>

6.3 监听页面变化

<Pdf
  source={{ uri: 'https://example.com/document.pdf' }}
  onPageChanged={(page, numberOfPages) => {
    console.log(`当前第 ${page} 页,共 ${numberOfPages} 页`);
  }}
  style={{ flex: 1 }}
/>

6.4 监听加载进度

<Pdf
  source={{ uri: 'https://example.com/document.pdf' }}
  onLoadProgress={(percent) => {
    console.log(`加载进度: ${percent * 100}%`);
  }}
  onLoadComplete={(numberOfPages) => {
    console.log('加载完成');
  }}
  style={{ flex: 1 }}
/>

6.5 使用 ref 设置页码

const pdfRef = useRef(null);

const goToPage = (pageNumber) => {
  pdfRef.current?.setPage(pageNumber);
};

<Pdf
  ref={pdfRef}
  source={{ uri: 'https://example.com/document.pdf' }}
  style={{ flex: 1 }}
/>

📚 七、API 详解

7.1 Pdf Props

source

PDF 数据源配置。

<Pdf source={{ uri: 'https://example.com/doc.pdf' }} />

<Pdf source={require('./doc.pdf')} />
page

初始页码索引。

<Pdf source={{ uri: '...' }} page={2} />
scale / minScale / maxScale

缩放比例控制。

<Pdf scale={1.5} minScale={0.5} maxScale={3.0} />
fitPolicy

适配策略:0 按宽度,1 按高度,2 同时适配(默认)。

<Pdf fitPolicy={0} />
spacing

页面间距。

<Pdf spacing={10} />
password

PDF 密码。

<Pdf password="secret" />
onLoadProgress

加载进度回调。

<Pdf onLoadProgress={(percent) => console.log(percent)} />
onLoadComplete

加载完成回调。

<Pdf onLoadComplete={(numberOfPages, path) => {
  console.log(`共 ${numberOfPages} 页`);
}} />
onPageChanged

页面变化回调。

<Pdf onPageChanged={(page, numberOfPages) => {
  console.log(`第 ${page} 页`);
}} />
onError

错误回调。

<Pdf onError={(error) => console.log(error)} />
onScaleChanged

缩放变化回调。

<Pdf onScaleChanged={(scale) => console.log(scale)} />
onPressLink

链接点击回调。

<Pdf onPressLink={(uri) => console.log(uri)} />

7.2 静态方法

setPage

设置当前页码。

const pdfRef = useRef(null);

pdfRef.current?.setPage(3);

⚠️ 八、注意事项与常见问题

8.1 遗留问题

问题 说明
horizontal 属性不支持 横向滚动功能暂未适配
fitWidth 属性不支持 自适应宽度功能暂未适配
enableDoubleTapZoom 不支持 双击缩放功能暂未适配
enablePaging 不支持 分页显示功能暂未适配
onLoadComplete 参数部分支持 仅支持 numberOfPages 和 path 参数

8.2 常见问题

Q1: PDF 无法加载?

A: 检查网络权限配置,确保 URL 可访问。

Q2: 如何处理大文件 PDF?

A: 使用 onLoadProgress 监听加载进度,给用户反馈。

Q3: 如何实现翻页功能?

A: 使用 setPage 方法或监听 onPageChanged 回调。


💻 九、完整示例代码

精美 PDF 查看器示例

在这里插入图片描述

import React, { useState, useRef } from 'react';
import {
  View,
  Text,
  StyleSheet,
  SafeAreaView,
  TouchableOpacity,
  ActivityIndicator,
} from 'react-native';
import Pdf from 'react-native-pdf';

type PdfRef = InstanceType<typeof Pdf>;

const pdfResources = [
  { label: '测试文档', url: 'https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf' },
  { label: '示例PDF', url: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' },
];

export default function App() {
  const [currentUrl, setCurrentUrl] = useState(pdfResources[0].url);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(0);
  const pdfRef = useRef<PdfRef>(null);

  const handleLoadComplete = (numberOfPages: number, filePath: string) => {
    console.log('PDF加载完成,页数:', numberOfPages, '路径:', filePath);
    setIsLoading(false);
    setError(null);
    setTotalPages(numberOfPages);
  };

  const handleError = (err: any) => {
    console.log('PDF加载错误:', err);
    setIsLoading(false);
    setError(typeof err === 'string' ? err : err?.message || 'PDF 加载失败');
  };

  const handlePageChanged = (page: number, numberOfPages: number) => {
    setCurrentPage(page);
    setTotalPages(numberOfPages);
  };

  const goToPrevPage = () => {
    if (currentPage > 1 && pdfRef.current) {
      pdfRef.current.setPage(currentPage - 1);
    }
  };

  const goToNextPage = () => {
    if (currentPage < totalPages && pdfRef.current) {
      pdfRef.current.setPage(currentPage + 1);
    }
  };

  const switchPdf = (url: string) => {
    if (url !== currentUrl) {
      setCurrentUrl(url);
      setIsLoading(true);
      setError(null);
      setCurrentPage(1);
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>PDF 查看器</Text>
        <View style={styles.tabs}>
          {pdfResources.map((pdf) => (
            <TouchableOpacity
              key={pdf.url}
              style={[styles.tab, currentUrl === pdf.url && styles.activeTab]}
              onPress={() => switchPdf(pdf.url)}
            >
              <Text style={[styles.tabText, currentUrl === pdf.url && styles.activeTabText]}>
                {pdf.label}
              </Text>
            </TouchableOpacity>
          ))}
        </View>
      </View>

      <View style={styles.pdfWrapper}>
        {error ? (
          <View style={styles.errorContainer}>
            <Text style={styles.errorText}>{error}</Text>
            <TouchableOpacity
              style={styles.retryBtn}
              onPress={() => {
                setError(null);
                setIsLoading(true);
              }}
            >
              <Text style={styles.retryBtnText}>重试</Text>
            </TouchableOpacity>
          </View>
        ) : (
          <>
            <Pdf
              ref={pdfRef}
              source={{ uri: currentUrl, cache: false }}
              style={styles.pdf}
              onLoadComplete={handleLoadComplete}
              onPageChanged={handlePageChanged}
              onError={handleError}
              enablePaging={false}
              horizontal={false}
              fitPolicy={2}
              spacing={0}
            />
            {isLoading && (
              <View style={styles.loadingContainer}>
                <ActivityIndicator size="large" color="#007AFF" />
                <Text style={styles.loadingText}>加载中...</Text>
              </View>
            )}
          </>
        )}
      </View>

      <View style={styles.footer}>
        <TouchableOpacity
          style={[styles.navBtn, currentPage <= 1 && styles.navBtnDisabled]}
          onPress={goToPrevPage}
          disabled={currentPage <= 1}
        >
          <Text style={[styles.navBtnText, currentPage <= 1 && styles.navBtnTextDisabled]}>
            上一页
          </Text>
        </TouchableOpacity>

        <Text style={styles.pageNum}>
          {currentPage} / {totalPages || '-'}
        </Text>

        <TouchableOpacity
          style={[styles.navBtn, currentPage >= totalPages && styles.navBtnDisabled]}
          onPress={goToNextPage}
          disabled={currentPage >= totalPages}
        >
          <Text style={[styles.navBtnText, currentPage >= totalPages && styles.navBtnTextDisabled]}>
            下一页
          </Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    paddingTop: 12,
    paddingBottom: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
    textAlign: 'center',
    marginBottom: 10,
  },
  tabs: {
    flexDirection: 'row',
    justifyContent: 'center',
    gap: 8,
  },
  tab: {
    paddingHorizontal: 16,
    paddingVertical: 6,
    borderRadius: 16,
    backgroundColor: '#f0f0f0',
  },
  activeTab: {
    backgroundColor: '#007AFF',
  },
  tabText: {
    fontSize: 13,
    color: '#666',
  },
  activeTabText: {
    color: '#fff',
    fontWeight: '500',
  },
  pdfWrapper: {
    flex: 1,
    backgroundColor: '#fff',
  },
  pdf: {
    flex: 1,
  },
  loadingContainer: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(255, 255, 255, 0.9)',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 14,
    color: '#666',
  },
  errorContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  errorText: {
    fontSize: 14,
    color: '#ff3b30',
    textAlign: 'center',
    marginBottom: 16,
  },
  retryBtn: {
    paddingHorizontal: 20,
    paddingVertical: 10,
    backgroundColor: '#007AFF',
    borderRadius: 8,
  },
  retryBtnText: {
    fontSize: 14,
    color: '#fff',
    fontWeight: '500',
  },
  footer: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    paddingHorizontal: 16,
    paddingVertical: 12,
    backgroundColor: '#fff',
    borderTopWidth: 1,
    borderTopColor: '#e0e0e0',
  },
  navBtn: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    backgroundColor: '#007AFF',
    borderRadius: 6,
    minWidth: 70,
    alignItems: 'center',
  },
  navBtnDisabled: {
    backgroundColor: '#ccc',
  },
  navBtnText: {
    fontSize: 13,
    color: '#fff',
    fontWeight: '500',
  },
  navBtnTextDisabled: {
    color: '#999',
  },
  pageNum: {
    fontSize: 14,
    color: '#333',
    fontWeight: '500',
  },
});

🔗 十、相关资源


📝 十一、总结

本文详细介绍了 react-native-pdf 在 HarmonyOS 平台的使用方法。通过 Pdf 组件,你可以轻松实现 PDF 文档的加载和展示。

核心要点

  • ✅ 支持 URL 和本地资源加载
  • ✅ 支持缩放控制和页面间距设置
  • ✅ 支持密码保护的 PDF
  • ✅ 完善的加载进度和页面变化回调

适用场景

  • 电子书阅读
  • 合同文档展示
  • 报表预览
  • 附件查看

希望本文能帮助你在 HarmonyOS 项目中顺利集成 PDF 查看组件!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐