flutter pdf 文件浏览

时间:2024-03-10 18:16:04

说明

flutter_full_pdf_viewer 可以实现从网络上下载 pdf 文件并且显示出来。

包地址:flutter_full_pdf_viewer: ^1.0.6

使用方法

1.在 pubspec.yaml 文件中添加如下的包:

dependencies:
  # pdf 阅读器
  flutter_full_pdf_viewer: ^1.0.6

  # 获取系统目录,因为从网络获取的pdf文件,需要保存到手机,所以需要用到这个包
  path_provider: ^1.5.0

2.官方demo:

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

import \'package:flutter/foundation.dart\';
import \'package:flutter/material.dart\';
import \'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart\';
import \'package:path_provider/path_provider.dart\';

void main() {
  runApp(MaterialApp(
    title: \'Plugin example app\',
    home: MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String pathPDF = "";

  @override
  void initState() {
    super.initState();
    createFileOfPdfUrl().then((f) {
      setState(() {
        pathPDF = f.path;
        print(pathPDF);
      });
    });
  }

  Future<File> createFileOfPdfUrl() async {
    final url = "http://africau.edu/images/default/sample.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);
    String dir = (await getApplicationDocumentsDirectory()).path;
    File file = new File(\'$dir/$filename\');
    await file.writeAsBytes(bytes);
    return file;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text(\'Plugin example app\')),
      body: Center(
        child: RaisedButton(
          child: Text("Open PDF"),
          onPressed: () => Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => PDFScreen(pathPDF)),
          ),
        ),
      ),
    );
  }
}

/// /////////////////////////// 显示 pdf 图文件 //////////////////////////// ///
class PDFScreen extends StatelessWidget {
  String pathPDF = "";

  PDFScreen(this.pathPDF);

  @override
  Widget build(BuildContext context) {
    return PDFViewerScaffold(
      appBar: AppBar(
        title: Text("Document"),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () {},
          ),
        ],
      ),
      path: pathPDF,
    );
  }
}

3.官方demo 显示效果:

 源码解析

主要有两个源码文件,分别是 full_pdf_viewer_plugin.dart 和 full_pdf_viewer_scaffold.dart 文件

1.full_pdf_viewer_plugin.dart

import \'dart:async\';
import \'dart:ui\';

import \'package:flutter/material.dart\';
import \'package:flutter/services.dart\';

enum PDFViewState { shouldStart, startLoad, finishLoad }

class PDFViewerPlugin {
  final _channel = const MethodChannel("flutter_full_pdf_viewer");
  static PDFViewerPlugin _instance;

  factory PDFViewerPlugin() => _instance ??= new PDFViewerPlugin._();
  PDFViewerPlugin._() {
    _channel.setMethodCallHandler(_handleMessages);
  }

  final _onDestroy = new StreamController<Null>.broadcast();
  Stream<Null> get onDestroy => _onDestroy.stream;
  Future<Null> _handleMessages(MethodCall call) async {
    switch (call.method) {
      case \'onDestroy\':
        _onDestroy.add(null);
        break;
    }
  }

  Future<Null> launch(String path, {Rect rect}) async {
    final args = <String, dynamic>{\'path\': path};
    if (rect != null) {
      args[\'rect\'] = {
        \'left\': rect.left,
        \'top\': rect.top,
        \'width\': rect.width,
        \'height\': rect.height
      };
    }
    await _channel.invokeMethod(\'launch\', args);
  }

  /// Close the PDFViewer
  /// Will trigger the [onDestroy] event
  Future close() => _channel.invokeMethod(\'close\');

  /// adds the plugin as ActivityResultListener
  /// Only needed and used on Android
  Future registerAcitivityResultListener() =>
      _channel.invokeMethod(\'registerAcitivityResultListener\');

  /// removes the plugin as ActivityResultListener
  /// Only needed and used on Android
  Future removeAcitivityResultListener() =>
      _channel.invokeMethod(\'removeAcitivityResultListener\');

  /// Close all Streams
  void dispose() {
    _onDestroy.close();
    _instance = null;
  }

  /// resize PDFViewer
  Future<Null> resize(Rect rect) async {
    final args = {};
    args[\'rect\'] = {
      \'left\': rect.left,
      \'top\': rect.top,
      \'width\': rect.width,
      \'height\': rect.height
    };
    await _channel.invokeMethod(\'resize\', args);
  }
}

2.full_pdf_viewer_scaffold.dart

其实笔者在实际的使用过程中,是把它提取出来使用的,这样做是为了可以修改源码,让他符合自己的使用场景。当pdf不是全屏显示时,就会遇到状态栏、导航栏高度的问题,可以看笔者写的这篇文章。

import \'dart:async\';

import \'package:flutter/foundation.dart\';
import \'package:flutter/material.dart\';
import \'package:flutter_full_pdf_viewer/full_pdf_viewer_plugin.dart\';

class PDFViewerScaffold extends StatefulWidget {
  final PreferredSizeWidget appBar;
  final String path;
  final bool primary;

  const PDFViewerScaffold({
    Key key,
    this.appBar,
    @required this.path,
    this.primary = true,
  }) : super(key: key);

  @override
  _PDFViewScaffoldState createState() => new _PDFViewScaffoldState();
}

class _PDFViewScaffoldState extends State<PDFViewerScaffold> {
  final pdfViwerRef = new PDFViewerPlugin();
  Rect _rect;
  Timer _resizeTimer;

  @override
  void initState() {
    super.initState();
    pdfViwerRef.close();
  }

  @override
  void dispose() {
    super.dispose();
    pdfViwerRef.close();
    pdfViwerRef.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_rect == null) {
      _rect = _buildRect(context);
      pdfViwerRef.launch(
        widget.path,
        rect: _rect,
      );
    } else {
      final rect = _buildRect(context);
      if (_rect != rect) {
        _rect = rect;
        _resizeTimer?.cancel();
        _resizeTimer = new Timer(new Duration(milliseconds: 300), () {
          pdfViwerRef.resize(_rect);
        });
      }
    }
    return new Scaffold(
        appBar: widget.appBar,
        body: const Center(child: const CircularProgressIndicator()));
  }

  Rect _buildRect(BuildContext context) {
    final fullscreen = widget.appBar == null;

    final mediaQuery = MediaQuery.of(context);
    final topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
    final top =
    fullscreen ? 0.0 : widget.appBar.preferredSize.height + topPadding;
    var height = mediaQuery.size.height - top;
    if (height < 0.0) {
      height = 0.0;
    }

    return new Rect.fromLTWH(0.0, top, mediaQuery.size.width, height);
  }
}

原文:http://www.luyixian.cn/news_show_398459.aspx