鸿蒙ArkTs如何实现pdf预览功能?
前言
在开发鸿蒙App时,你是否做过pdf预览功能。是否也和我一样碰壁了,来看看我遇到的问题,以及我是如何实现的吧。
PDFKit运行示例代码报错
the requested module '@hms:officeservice.PdfView' does not provide an export name 'pdfViewManager' which imported by 'xxxx'
真机运行
本来以为用真机就能运行了,没想到还是报错
那么下面来看看我是如何实现的吧
先看效果
视频转完gif,视觉上看起来有点卡,实际运行不卡。
pdf_16">一、预览本地pdf文件
预览本地的pdf文件很简单,使用Web组件加载即可。
pdf文件目录:harmonyApp\entry\src\main\resources\rawfile\test.pdf
具体代码如下:
import web_webview from '@ohos.web.webview';
@Entry
@Component
struct Index {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
build() {
Column() {
// src-本地pdf文件
Web({ src: $rawfile('test.pdf'), controller: this.webviewController })
.layoutWeight(1)
.domStorageAccess(true)
}
.height('100%')
}
}
pdf_41">二、预览线上的pdf文件
这里的线上的pdf文件是指可以在浏览器直接打开预览的pdf文件,还有一种是在浏览器打开是直接进入下载的,那么就需要我们进一步处理了,第三点有详解。
这样的文件预览也很简单,使用Web组件加载即可。
具体代码如下:
import web_webview from '@ohos.web.webview';
@Entry
@Component
struct Index {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
build() {
Column() {
// 线上pdf链接
Web({ src: 'http://www.cztouch.com/upfiles/soft/testpdf.pdf', controller: this.webviewController })
.layoutWeight(1)
.domStorageAccess(true)
}
.height('100%')
}
}
pdf_66">三、预览沙箱目录中pdf的文件(重点)
这种就比较麻烦了,有的pdf链接在浏览器打开直接跳转下载不会预览,那么就需要我们下载到沙箱目录中,再预览沙箱目录中的pdf文件。
我这里用到了一个pdfviewer工具,可从我的百度网盘免费获取
拿到文件夹后,放在以下目录:
项目目录:harmonyApp\entry\src\main\resources\rawfile
具体实现代码如下:
import router from '@ohos.router';
import web_webview from '@ohos.web.webview';
import { BusinessError, request } from '@kit.BasicServicesKit';
import showToast from '../../common/utils/ToastUtils';
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';
interface IBase64 {
base64: string;
fileName: string;
}
@Entry
@Component
struct Index2 {
controller: web_webview.WebviewController = new web_webview.WebviewController()
// pdf文件路径
@State fileUrl: string = ''
// 本地沙箱文件地址
@State tempFilePath: string = ''
// 是否显示按钮
@State isShowBtn: boolean = true;
build() {
Stack() {
Column() {
// 页面内容
Scroll(){
Column(){
if(this.tempFilePath){
if(this.isShowBtn){
Button('打开文件').onClick(()=>{
this.isShowBtn = false;
})
}else{
Web({ src: $rawfile('pdfviewer/viewer.html'), controller: this.controller })
.onProgressChange((event)=>{
console.log("newProgress", event?.newProgress)
})
.domStorageAccess(true) // 设置是否开启文档对象模型存储接口(DOM Storage API)权限,默认未开启。
.onPageEnd(()=>{
let file = this.sandBoxPdfToBase64(this.tempFilePath);
this.controller.runJavaScript(`openFile("${file.base64}", "${file.fileName}")`);
})
}
}
}.width('100%').height('100%')
}
.edgeEffect(EdgeEffect.Fade)
.width('100%')
.layoutWeight(1)
.align(Alignment.TopStart)
}
.height('100%')
.backgroundColor(Color.White)
}
}
// 沙箱pdf文件转base64方法
sandBoxPdfToBase64(url: string) {
let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件
let stat = fs.statSync(url); // 获取文件状态
let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象
let base64 = new util.Base64Helper(); // 实例化Base64Helper
let num = fs.readSync(file.fd, buf); // 读取文件
let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) // 转换成Uint8Array
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64
let fileName = file.name
fs.closeSync(file);
return { base64: retStr, fileName: fileName } as IBase64;
}
// 下载pdf文件,获取沙箱文件目录
getTempFile(fileUrl:string){
let context = getContext(this) as common.UIAbilityContext;
const fileFullName = fileUrl.split('/')[fileUrl.split('/').length - 1]
let tempFilePath = `${context.filesDir}/${fileFullName}`;
//文件如果已经存在,就删除
if (fs.accessSync(tempFilePath)) {
fs.unlink(tempFilePath)
}
request.downloadFile(getContext(), { url: fileUrl,filePath: tempFilePath }).then((data: request.DownloadTask) => {
let downloadTask: request.DownloadTask = data;
let progressCallback = (receivedSize: number, totalSize: number) => {
// 这里可以自行编写下载进度条
showToast(`下载大小${receivedSize},总大小${totalSize}`);
};
let completeCallback = ()=>{
showToast("下载完毕");
this.tempFilePath = tempFilePath;
}
downloadTask.on('progress', progressCallback);
downloadTask.on('complete', completeCallback)
}).catch((err: BusinessError) => {
console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);
})
}
// 组件生命周期:组件即将出现时回调该接口
aboutToAppear() {
console.log('进入页面')
// 你的pdf链接
this.fileUrl = (router.getParams() as Record<string, string>).url || '';
this.getTempFile((router.getParams() as Record<string, string>).url as string);
}
}
这里附有将pdf文件下载到沙箱目录代码,可选择使用(不必须)。
效果中的整体代码
import web_webview from '@ohos.web.webview';
import promptAction from '@ohos.promptAction'
import { BusinessError, request } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';
// pdf页面tab接口
interface pageTab {
name:string;
}
interface IBase64 {
base64: string;
fileName: string;
}
/**
* pdfPage的ViewModel
*/
class PdfPageModel {
// 当前索引
curTabIndex:number = 0;
// pdf页面tab
tabList:pageTab[] = [
{ name:'预览本地PDF文件' },
{ name:'预览网络PDF文件' },
{ name:'预览沙箱PDF文件' },
];
// 网络文件
fileUrl: string = 'http://www.cztouch.com/upfiles/soft/testpdf.pdf'
// 本地沙箱文件地址
tempFilePath: string = ''
constructor() {
}
// 沙箱pdf文件转base64方法
sandBoxPdfToBase64(url: string) {
let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件
let stat = fs.statSync(url); // 获取文件状态
let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象
let base64 = new util.Base64Helper(); // 实例化Base64Helper
let num = fs.readSync(file.fd, buf); // 读取文件
let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) // 转换成Uint8Array
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64
let fileName = file.name
fs.closeSync(file);
return { base64: retStr, fileName: fileName } as IBase64;
}
// 下载pdf文件,获取沙箱文件目录
getTempFile(fileUrl:string){
let context = getContext(this) as common.UIAbilityContext;
const fileFullName = fileUrl.split('/')[fileUrl.split('/').length - 1]
let tempFilePath = `${context.filesDir}/${fileFullName}`;
//文件如果已经存在,就删除
if (fs.accessSync(tempFilePath)) {
fs.unlink(tempFilePath)
}
request.downloadFile(getContext(), { url: fileUrl,filePath: tempFilePath }).then((data: request.DownloadTask) => {
let downloadTask: request.DownloadTask = data;
let progressCallback = (receivedSize: number, totalSize: number) => {
// 这里可以自行编写下载进度条
// showToast(`下载大小${receivedSize},总大小${totalSize}`);
};
let completeCallback = ()=>{
// showToast("下载完毕");
this.tempFilePath = tempFilePath;
}
downloadTask.on('progress', progressCallback);
downloadTask.on('complete', completeCallback)
}).catch((err: BusinessError) => {
console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);
})
}
// tab切换
switchTab(index:number){
this.curTabIndex = index;
if(index === 2 && !this.tempFilePath){
try {
promptAction.showDialog({
title: '温馨提示',
message: '有些pdf线上链接是经过第三方加密过的,在浏览器访问时不能直接预览,直接走的是下载的pdf文件链接,可以采用这种方式,先下载在沙箱目录中,然后再预览沙箱中的pdf文件',
buttons: [
{
text: '知道了',
color: '#000000'
}
]
}, (err, data) => {
if (err) {
console.error('showDialog err: ' + err);
return;
}
console.info('showDialog success callback, click button: ' + data.index);
});
} catch (error) {
console.error(`Failed to show dialog. Code: ${error.code}, message: ${error.message}`);
}
this.getTempFile(this.fileUrl);
}
}
}
@Entry
@Component
struct PdfPage {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
@State vm: PdfPageModel = new PdfPageModel();
// 验证是否选中
VerifySelectedFun( curIndex:number , itemIndex:number ):boolean{
return curIndex == itemIndex
}
aboutToAppear(): void {
try {
promptAction.showDialog({
title: '温馨提示',
message: '在模拟器中运行,首次加载会出现黑屏,但来回切换几次tab标签就好了,有条件的建议使用真机运行,不会有这样的问题',
buttons: [
{
text: '知道了',
color: '#000000'
}
]
}, (err, data) => {
if (err) {
console.error('showDialog err: ' + err);
return;
}
console.info('showDialog success callback, click button: ' + data.index);
});
} catch (error) {
console.error(`Failed to show dialog. Code: ${error.code}, message: ${error.message}`);
}
}
build() {
Stack() {
Column() {
// tab标签条
Row(){
Scroll(){
Row(){
ForEach(this.vm.tabList,(item:pageTab,index)=>{
Row(){
if(this.VerifySelectedFun(this.vm.curTabIndex,index)){
Stack(){
Row(){}
.width(40)
.height(10)
.borderRadius(20)
.offset({y:7})
.linearGradient({angle:89.11,colors:[
['rgba(255, 255, 255, 0.55)',0.0682],
['rgba(217, 217, 217, 0)',1]
]})
Text(item.name)
.fontSize(18)
.fontColor($r('app.color.primary_theme_color'))
.fontWeight(600)
.height('100%')
}
}else{
Text(item.name)
.fontSize(16)
// .fontColor($r('app.color.font_color_default'))
.fontWeight(400)
}
}
.height('100%')
.justifyContent(FlexAlign.Start)
.padding({ left: index == 0 ? 0 : 20 })
.onClick(()=>{
this.vm.switchTab(index)
})
})
}
}.edgeEffect(EdgeEffect.Fade)
.layoutWeight(1)
.align(Alignment.Center)
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
}.width('100%').height(50).justifyContent(FlexAlign.Start)
.padding({left:16,right:16})
.backgroundColor(Color.White)
// 页面内容
Scroll(){
Column(){
if(this.vm.curTabIndex === 0 ){
// web组件加载本地pdf文件
Web({ src: $rawfile('Git.pdf'), controller: this.webviewController})
.domStorageAccess(true)
.onProgressChange((event)=>{
console.log("newProgress", event?.newProgress)
})
}else if(this.vm.curTabIndex === 1){
// web组件加载网络pdf文件
Web({ src: 'http://www.cztouch.com/upfiles/soft/testpdf.pdf', controller: this.webviewController })
.layoutWeight(1)
.domStorageAccess(true)
.onProgressChange((event)=>{
console.log("newProgress", event?.newProgress)
})
}else if(this.vm.curTabIndex === 2){
if(this.vm.tempFilePath){
Web({ src: $rawfile('pdfviewer/viewer.html'), controller: this.webviewController })
.onProgressChange((event)=>{
console.log("newProgress", event?.newProgress)
})
.domStorageAccess(true) // 设置是否开启文档对象模型存储接口(DOM Storage API)权限,默认未开启。
.onPageEnd(()=>{
let file = this.vm.sandBoxPdfToBase64(this.vm.tempFilePath);
this.webviewController.runJavaScript(`openFile("${file.base64}", "${file.fileName}")`);
})
}
}
}.padding({ left: 16, right: 16, bottom: 16 })
}
.edgeEffect(EdgeEffect.Fade)
.width('100%')
.layoutWeight(1)
.align(Alignment.TopStart)
}
.height('100%')
.backgroundColor('#F5F5F5')
.padding({ bottom: 16 })
}
}
}
总结
总体来说就是使用Web组件加载pdf文件,在模拟器中运行,首次运行会黑屏,不过来回切换一下tab页就好了,真机运行没有问题。
为啥要存到沙箱中再预览,岂不是多此一举?
当然不是,因为有的pdf文件是通过第三方加密过的,在浏览器打开链接时,是不能直接预览的,而是直接走下载了。这时,就需要先存到沙箱目录中再预览。
有需要的朋友,拿走不谢,求赞,求赞,求赞~
关注我不迷路,不定时分享鸿蒙难点亮点