You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在首次打开Syncfusion Flutter PDF Viewer时下载PDF实现离线访问?

使用Syncfusion Flutter PDF Viewer实现PDF离线缓存查看

当然可以实现!Syncfusion Flutter PDF Viewer本身支持加载本地文件,我们只需要额外加一层「首次下载缓存+后续优先读本地」的逻辑就行。下面是具体的实现步骤和代码示例:

1. 准备依赖

首先需要在pubspec.yaml中添加必要的依赖:

  • syncfusion_flutter_pdfviewer: 核心的PDF查看组件
  • path_provider: 用于获取本地存储路径
  • permission_handler: (可选,仅当你需要保存到公共存储目录时才需要,应用私有目录无需权限)
dependencies:
  flutter:
    sdk: flutter
  syncfusion_flutter_pdfviewer: ^25.1.35 # 建议使用最新版本
  path_provider: ^2.1.2
  permission_handler: ^11.0.1 # 可选

2. 核心实现逻辑

我们的思路是:

  • 首次打开时,检查本地是否已存在目标PDF文件
  • 如果不存在,从网络下载并保存到应用私有目录
  • 后续打开时,直接读取本地文件,无需联网

下面是完整的代码示例:

import 'dart:io';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

class OfflinePdfScreen extends StatefulWidget {
  // 传入PDF的网络地址和唯一文件名(避免缓存冲突)
  final String pdfNetworkUrl;
  final String uniqueFileName;

  const OfflinePdfScreen({
    super.key,
    required this.pdfNetworkUrl,
    required this.uniqueFileName,
  });

  @override
  State<OfflinePdfScreen> createState() => _OfflinePdfScreenState();
}

class _OfflinePdfScreenState extends State<OfflinePdfScreen> {
  String? _localPdfPath;
  bool _isProcessing = true;
  final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();

  @override
  void initState() {
    super.initState();
    // 初始化时准备本地PDF文件
    _setupLocalPdf();
  }

  Future<void> _setupLocalPdf() async {
    // 注意:如果使用应用私有目录(getApplicationDocumentsDirectory),Android无需申请存储权限
    // 只有当你要保存到公共目录时,才需要请求Permission.storage或Permission.manageExternalStorage

    // 获取应用私有文档目录
    final appDocDir = await getApplicationDocumentsDirectory();
    final localPdfPath = '${appDocDir.path}/${widget.uniqueFileName}';
    final localFile = File(localPdfPath);

    // 检查本地文件是否存在
    if (await localFile.exists()) {
      setState(() {
        _localPdfPath = localPdfPath;
        _isProcessing = false;
      });
      return;
    }

    // 文件不存在,从网络下载
    try {
      final httpClient = HttpClient();
      final request = await httpClient.getUrl(Uri.parse(widget.pdfNetworkUrl));
      final response = await request.close();
      final bytes = await response.expand((chunk) => chunk).toList();

      // 将下载的字节写入本地文件
      await localFile.writeAsBytes(bytes);

      setState(() {
        _localPdfPath = localPdfPath;
        _isProcessing = false;
      });
    } catch (e) {
      // 处理下载失败的情况,比如提示用户
      setState(() => _isProcessing = false);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('PDF下载失败:$e')),
        );
      }
      debugPrint('PDF下载错误: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('离线PDF查看'),
        actions: [
          IconButton(
            icon: const Icon(Icons.bookmark),
            onPressed: () => _pdfViewerKey.currentState?.openBookmarkView(),
          ),
        ],
      ),
      body: _isProcessing
          ? const Center(child: CircularProgressIndicator())
          : _localPdfPath != null
              // 加载本地PDF文件
              ? SfPdfViewer.file(
                  File(_localPdfPath!),
                  key: _pdfViewerKey,
                )
              // 本地加载失败时, fallback到网络加载
              : SfPdfViewer.network(
                  widget.pdfNetworkUrl,
                  key: _pdfViewerKey,
                ),
    );
  }
}

3. 额外优化建议

  • 文件名唯一性:可以通过对PDF的URL进行哈希处理(比如md5.encode(utf8.encode(pdfUrl)))生成唯一文件名,避免不同PDF缓存冲突。
  • 缓存更新:如果需要更新PDF,可以添加版本检查逻辑(比如从服务器获取PDF的版本号),当版本不一致时重新下载覆盖本地文件。
  • 错误重试:下载失败时,可以添加重试按钮,让用户手动触发重新下载。
  • 缓存清理:可以提供清理缓存的功能,让用户删除本地存储的PDF文件。

内容的提问来源于stack exchange,提问作者Rifat Hossain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:25:45