1. 插件介绍

flutter_pdfview 是一个强大的 Flutter PDF 渲染插件,支持在移动端原生渲染 PDF 文件。这是一个针对鸿蒙平台进行了自定义修改的版本,提供了流畅的 PDF 浏览体验,支持多种高级功能。

在这里插入图片描述

核心功能

  • ✅ 原生 PDF 渲染,性能优秀
  • ✅ 支持本地和远程 PDF 文件加载
  • ✅ 页面导航(上一页/下一页/跳转到指定页)
  • ✅ 水平和垂直滑动翻页
  • ✅ 自动适应屏幕(宽度/高度/两者兼顾)
  • ✅ 密码保护 PDF 支持
  • ✅ 夜间模式
  • ✅ 页面渲染和错误回调
  • ✅ 链接处理

典型应用场景

  • 文档阅读器应用
  • 电子书应用
  • 合同签署应用
  • 报告查看工具
  • 教育类应用中的 PDF 教材展示

2. 安装与配置

由于这是一个针对鸿蒙平台的自定义修改版本,需要通过 Git 方式引入。

2.1 添加依赖

在项目的 pubspec.yaml 文件中添加以下依赖配置:

dependencies:
  flutter_pdfview:
    git:
      url: "https://atomgit.com/openharmony-sig/flutter_pdfview.git"
      path: "flutter_pdfview_will_delete-master"
  path_provider:
    git:
      url: "https://atomgit.com/packages/path_provider/path_provider.git"
      path: "path_provider"

2.2 安装依赖

运行以下命令安装依赖:

flutter packages get

2.3 导入包

在需要使用 PDFView 的 Dart 文件中导入包:

import 'package:flutter_pdfview/flutter_pdfview.dart';

3. API 使用详解

3.1 基本使用

PDFView(
  filePath: path, // PDF 文件路径
  enableSwipe: true, // 启用滑动翻页
  swipeHorizontal: false, // 垂直滑动(true 为水平滑动)
  autoSpacing: true, // 自动添加页间距
  pageFling: true, // 启用页面快速翻页
  pageSnap: true, // 启用页面吸附效果
  defaultPage: 0, // 默认显示第一页
  fitPolicy: FitPolicy.WIDTH, // 适应宽度
)

3.2 事件处理

PDFView(
  filePath: path,
  onRender: (_pages) {
    setState(() {
      pages = _pages; // 获取总页数
      isReady = true;
    });
  },
  onError: (error) {
    setState(() {
      errorMessage = error.toString();
    });
  },
  onPageError: (page, error) {
    setState(() {
      errorMessage = '$page: ${error.toString()}';
    });
  },
  onPageChanged: (int? page, int? total) {
    print('当前页面: $page/$total');
    setState(() {
      currentPage = page;
    });
  },
  onLinkHandler: (String? uri) {
    print('点击链接: $uri');
    // 自定义链接处理逻辑
  },
)

3.3 使用控制器

final Completer<PDFViewController> _controller = Completer<PDFViewController>();

PDFView(
  filePath: path,
  onViewCreated: (PDFViewController pdfViewController) {
    _controller.complete(pdfViewController);
  },
);

// 使用控制器进行页面操作
FutureBuilder<PDFViewController>(
  future: _controller.future,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          TextButton(
            child: Text('上一页'),
            onPressed: () async {
              final page = await snapshot.data!.getCurrentPage();
              if (page! > 0) {
                await snapshot.data!.setPage(page - 1);
              }
            },
          ),
          TextButton(
            child: Text('下一页'),
            onPressed: () async {
              final page = await snapshot.data!.getCurrentPage();
              final total = await snapshot.data!.getPageCount();
              if (page! < total! - 1) {
                await snapshot.data!.setPage(page + 1);
              }
            },
          ),
          TextButton(
            child: Text('跳转到第5页'),
            onPressed: () async {
              await snapshot.data!.setPage(4); // 页码从0开始
            },
          ),
        ],
      );
    }
    return Container();
  },
)

3.4 加载本地资源 PDF

import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

Future<File> fromAsset(String asset, String filename) async {
  Completer<File> completer = Completer();
  try {
    var dir = await getApplicationDocumentsDirectory();
    File file = File("${dir.path}/$filename");
    var data = await rootBundle.load(asset);
    var bytes = data.buffer.asUint8List();
    await file.writeAsBytes(bytes, flush: true);
    completer.complete(file);
  } catch (e) {
    throw Exception('Error parsing asset file!');
  }
  return completer.future;
}

// 使用示例
fromAsset('assets/demo.pdf', 'demo.pdf').then((f) {
  setState(() {
    pdfPath = f.path;
  });
});

3.5 加载远程 PDF

Future<File> createFileOfPdfUrl() async {
  Completer<File> completer = Completer();
  try {
    final url = "http://www.pdf995.com/samples/pdf.pdf";
    final filename = url.substring(url.lastIndexOf("/") + 1);
    var request = await HttpClient().getUrl(Uri.parse(url));
    var response = await request.close();
    var bytes = await consolidateHttpClientResponseBytes(response);
    var dir = await getApplicationDocumentsDirectory();
    File file = File("${dir.path}/$filename");
    await file.writeAsBytes(bytes, flush: true);
    completer.complete(file);
  } catch (e) {
    throw Exception('Error downloading PDF!');
  }
  return completer.future;
}

// 使用示例
createFileOfPdfUrl().then((f) {
  setState(() {
    pdfPath = f.path;
  });
});

3.6 完整示例代码

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';

class PDFScreen extends StatefulWidget {
  final String? path;

  PDFScreen({Key? key, this.path}) : super(key: key);

  _PDFScreenState createState() => _PDFScreenState();
}

class _PDFScreenState extends State<PDFScreen> {
  final Completer<PDFViewController> _controller = Completer<PDFViewController>();
  int? pages = 0;
  int? currentPage = 0;
  bool isReady = false;
  String errorMessage = '';

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("PDF 阅读器"),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () {},
          ),
        ],
      ),
      body: Stack(
        children: <Widget>[
          PDFView(
            filePath: widget.path,
            enableSwipe: true,
            swipeHorizontal: true,
            autoSpacing: false,
            pageFling: true,
            pageSnap: true,
            defaultPage: currentPage!,
            fitPolicy: FitPolicy.BOTH,
            preventLinkNavigation: false,
            onRender: (_pages) {
              setState(() {
                pages = _pages;
                isReady = true;
              });
            },
            onError: (error) {
              setState(() {
                errorMessage = error.toString();
              });
              print(error.toString());
            },
            onPageError: (page, error) {
              setState(() {
                errorMessage = '$page: ${error.toString()}';
              });
              print('$page: ${error.toString()}');
            },
            onViewCreated: (PDFViewController pdfViewController) {
              _controller.complete(pdfViewController);
            },
            onLinkHandler: (String? uri) {
              print('goto uri: $uri');
            },
            onPageChanged: (int? page, int? total) {
              print('page change: $page/$total');
              setState(() {
                currentPage = page;
              });
            },
          ),
          errorMessage.isEmpty
              ? !isReady
                  ? Center(
                      child: CircularProgressIndicator(),
                    )
                  : Container()
              : Center(
                  child: Text(errorMessage),
                )
        ],
      ),
      floatingActionButton: FutureBuilder<PDFViewController>(
        future: _controller.future,
        builder: (context, AsyncSnapshot<PDFViewController> snapshot) {
          if (snapshot.hasData) {
            return FloatingActionButton.extended(
              label: Text("跳转到第 ${pages! ~/ 2 + 1} 页"),
              onPressed: () async {
                await snapshot.data!.setPage(pages! ~/ 2);
              },
            );
          }
          return Container();
        },
      ),
      bottomNavigationBar: BottomAppBar(
        shape: CircularNotchedRectangle(),
        notchMargin: 8.0,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.chevron_left),
              onPressed: () async {
                final pdfController = await _controller.future;
                final page = await pdfController.getCurrentPage();
                if (page! > 0) {
                  await pdfController.setPage(page - 1);
                }
              },
            ),
            Text("第 ${currentPage! + 1} / ${pages!} 页"),
            IconButton(
              icon: Icon(Icons.chevron_right),
              onPressed: () async {
                final pdfController = await _controller.future;
                final page = await pdfController.getCurrentPage();
                final total = await pdfController.getPageCount();
                if (page! < total! - 1) {
                  await pdfController.setPage(page + 1);
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

4. 高级配置与最佳实践

4.1 适应策略选择

// 适应宽度(默认)
fitPolicy: FitPolicy.WIDTH

// 适应高度
fitPolicy: FitPolicy.HEIGHT

// 同时适应宽度和高度
fitPolicy: FitPolicy.BOTH

4.2 翻页模式

// 垂直滑动翻页(默认)
swipeHorizontal: false

// 水平滑动翻页
swipeHorizontal: true

4.3 性能优化建议

  1. 大文件处理:对于大型 PDF 文件,建议先下载到本地存储,然后再进行加载
  2. 内存管理:在不需要显示 PDF 时,及时释放资源
  3. 预加载:可以考虑预加载当前页的前后几页,提升翻页流畅度
  4. 避免频繁刷新:在 onRenderonPageChanged 回调中避免执行耗时操作

4.4 错误处理

PDFView(
  filePath: path,
  onError: (error) {
    // 处理加载错误
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('加载失败'),
        content: Text('PDF 文件加载失败: $error'),
        actions: [
          TextButton(
            child: Text('确定'),
            onPressed: () => Navigator.pop(context),
          ),
        ],
      ),
    );
  },
  onPageError: (page, error) {
    // 处理特定页面渲染错误
    print('第 $page 页渲染错误: $error');
  },
)

5. 总结

flutter_pdfview 是一个功能强大的 Flutter PDF 渲染插件,通过针对鸿蒙平台的定制修改,为鸿蒙应用开发者提供了高质量的 PDF 渲染能力。

主要优势

  • 原生性能:基于原生 PDF 渲染引擎,性能优秀
  • 丰富功能:支持多种高级功能,满足各种 PDF 浏览需求
  • 易用性:提供简洁的 API,方便快速集成
  • 良好的兼容性:针对鸿蒙平台进行了专门适配
  • 活跃的社区支持:开源项目,持续更新和维护

快速入门建议

  1. 按照本文的安装配置步骤添加依赖
  2. 参考基本使用示例创建简单的 PDF 阅读器
  3. 根据需求逐步添加高级功能
  4. 利用控制器实现自定义的页面导航逻辑
  5. 注意错误处理和性能优化

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

在社区中,您可以获取更多关于鸿蒙平台 Flutter 开发的资源和支持,与其他开发者交流经验,共同推动鸿蒙生态的发展。

Logo

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

更多推荐