通过flutter预览本地的pdf文件,选了多个flutter插件后最终选择了pdfx,
首先看一下我的需求吧,左侧是所有的文件列表右侧是文件的内容,左侧比较简单我们读取指定的目录后将文件名显示出来,点击选中时候切换文件显示
说说实现过程吧,首先是通过flutter读取本地的文件夹目录,通过path_provider插件读取本地文件目录
/**
* 获取本地目录getExternalStorageDirectory()
* /storage/emulated/0/Android/data/com.example.read_database/files
* 通过list获取的是包含文件夹的
*/
static Future<Stream<FileSystemEntity>?> getLocalPdfFile() async {
var externalStoragePath = await getExternalStorageDirectory();
var dis = Directory('${externalStoragePath?.path}/mipdf/');
if (dis.existsSync()) {
return dis
.list(recursive: true)//过滤文件,不显示文件夹
.where((event) => FileSystemEntity.isFileSync(event.path));
} else {
dis.create();
return null;
}
}
具体实现代码如下
class MuBiaoZiYuanBody extends StatefulWidget {
@override
State<StatefulWidget> createState() => MuBiaoZiYuanBodyState();
}
class MuBiaoZiYuanBodyState extends State<MuBiaoZiYuanBody> {
List<FileSystemEntity>? _stream;
int _itemCount = 0;
int _selected = 0;
//Android 和ios用 PdfControllerPinch window用PdfController
PdfControllerPinch? _controller;
@override
void initState() {
_initData();
super.initState();
}
/// 初始化数据
Future<void> _initData() async {
_stream = await (await ReadFileUtil.getLocalPdfFile())?.toList();
int length = (_stream?.length)!;
_controller = PdfControllerPinch(
document: PdfDocument.openFile(
_stream![0].uri.toFilePath(windows: false),
),
);
setState(() {
_itemCount = length;
});
}
Future<void> _ChangePdf(int path) async {
//注意此处不可重新初始化,否则会出现刷新切换不了文件,实现不出来
// PdfControllerPinch(
// document: PdfDocument.openFile(
// _stream![0].uri.toFilePath(windows: false),
// ),
// )
//
_controller?.loadDocument(
PdfDocument.openFile(_stream![path].uri.toFilePath(windows: false)!));
setState(() {
_selected = path;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
_buildListFile(),
Expanded(
flex: 1,
child: Container(
padding: const EdgeInsets.only(
left: 4, top: 10, bottom: 10, right: 10),
// child: PdfPreViewWidget(path: _selectFilePath),
// child: PdfxWidget(path:'assets/kotlin.pdf'),
child: (_controller == null)
? const Center(
child: Text("正在加载"),
)
: buildPdfViewPinch(),
),
)
],
),
);
}
Container _buildListFile() {
return Container(
width: 182,
padding:
const EdgeInsets.only(left: 4, top: 10, bottom: 10, right: 10),
child: ListView.separated(
separatorBuilder: (BuildContext context, int index) =>
const Divider(
color: Colors.transparent,
height: 10,
),
itemCount: _itemCount,
itemBuilder: (context, index) {
return _buildItem(index, context);
},
),
);
}
PdfViewPinch buildPdfViewPinch() {
return PdfViewPinch(
controller: _controller!,
builders: PdfViewPinchBuilders<DefaultBuilderOptions>(
options: const DefaultBuilderOptions(
loaderSwitchDuration: Duration(milliseconds: 300)),
documentLoaderBuilder: (_) =>
Center(child: LoadingDialog(text: "加载文档")),
pageLoaderBuilder: (_) => Center(child: LoadingDialog(text: "加载页")),
errorBuilder: (_, error) => Center(child: Text(error.toString())),
),
);
}
Widget _buildItem(int index, BuildContext context) {
var path = _stream?[index].path;
return ElevatedButton(
onPressed: () async {
_ChangePdf(index);
},
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.all(15)),
backgroundColor: MaterialStateProperty.all(
_selected == index ? Color(0xfff8b651) : Colors.white),
elevation: MaterialStateProperty.all(4),
),
child: Text(
basename(path!),
style: const TextStyle(
color: MyColor.textColor,
fontWeight: FontWeight.bold,
fontSize: 16),
),
);
}
}
此处需要避坑:
避坑1:
在change时候,就是重新加载一个新的文件的时候,我们只需要用controller重新loadDocument即可,不可以重新初始化,会出现加载不出来的情况。
_controller?.loadDocument( PdfDocument.openFile(_stream![path].uri.toFilePath(windows: false)));
避坑2:openFile(String path),这里的蚕食我们要用,不可以直接传入_stream![path]
_stream![path].uri.toFilePath(windows: false)