Flutter中使用epub_view实现电子书上次阅读位置续读的方法
解决方案:实现epub_view的书签与续读功能
一、续读功能(自动记录上次阅读位置)
核心思路是持久化存储当前阅读的CFI字符串,每次阅读位置变化或页面销毁时保存,下次打开页面时读取并自动跳转。
1. 添加依赖
在pubspec.yaml中引入存储依赖:
dependencies: shared_preferences: ^2.2.2
2. 代码实现续读逻辑
import 'package:flutter/material.dart'; import 'package:epub_view/epub_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; class EpubReader extends StatefulWidget { const EpubReader({super.key}); @override State<EpubReader> createState() => _EpubReaderState(); } class _EpubReaderState extends State<EpubReader> { late EpubController _epubController; late SharedPreferences _prefs; // 存储键,若支持多书籍,可将此键与书籍ID/名称绑定 final String _lastReadKey = 'last_read_cfi_sample1'; @override void initState() { super.initState(); _initPrefsAndController(); } Future<void> _initPrefsAndController() async { _prefs = await SharedPreferences.getInstance(); // 读取上次保存的CFI,无则用默认值 final savedCfi = _prefs.getString(_lastReadKey) ?? 'epubcfi(/6/0[null]!/4/2[chapter_1]/4)'; _epubController = EpubController( document: EpubDocument.openAsset('assets/sample1.epub'), epubCfi: savedCfi, ); // 监听阅读位置变化,实时保存 _epubController.currentValue.listen((_) { final cfi = _epubController.generateEpubCfi(); if (cfi != null) _prefs.setString(_lastReadKey, cfi); }); } @override void dispose() { // 销毁前最后保存一次,确保数据不丢失 final cfi = _epubController.generateEpubCfi(); if (cfi != null) _prefs.setString(_lastReadKey, cfi); _epubController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: EpubViewActualChapter( controller: _epubController, builder: (chapterValue) => Text( chapterValue?.chapter?.Title?.replaceAll('\n', '').trim() ?? '', textAlign: TextAlign.start, ), ), actions: [ IconButton( onPressed: _saveBookmark, icon: const Icon(Icons.bookmark_add_outlined), ), IconButton( onPressed: () => _showCurrentEpubCfi(context), icon: const Icon(Icons.save_alt_outlined), ), IconButton( onPressed: _showBookmarks, icon: const Icon(Icons.bookmarks_outlined), ), ], ), drawer: Drawer(child: EpubViewTableOfContents(controller: _epubController)), body: EpubView(controller: _epubController), ); } void _showCurrentEpubCfi(context) { final cfi = _epubController.generateEpubCfi(); print(cfi); if (cfi != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(cfi), action: SnackBarAction( label: '跳转', onPressed: () => _epubController.gotoEpubCfi(cfi), ), ), ); } } // 二、书签功能实现 final String _bookmarksKey = 'bookmarks_sample1'; Future<void> _saveBookmark() async { final cfi = _epubController.generateEpubCfi(); if (cfi == null) return; // 获取当前章节标题 final chapterTitle = _epubController.currentValue.value?.chapter?.Title?.replaceAll('\n', '').trim() ?? '未知章节'; // 读取已有书签,避免重复添加 List<String> bookmarks = _prefs.getStringList(_bookmarksKey) ?? []; final bookmarkItem = '$chapterTitle||$cfi'; if (!bookmarks.contains(bookmarkItem)) { bookmarks.add(bookmarkItem); await _prefs.setStringList(_bookmarksKey, bookmarks); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('书签已保存'))); } } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('该位置已添加书签'))); } } } // 展示书签列表并支持跳转、删除 void _showBookmarks() async { List<String> bookmarks = _prefs.getStringList(_bookmarksKey) ?? []; if (bookmarks.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无书签'))); return; } showDialog( context: context, builder: (context) => AlertDialog( title: const Text('我的书签'), content: ListView.builder( shrinkWrap: true, itemCount: bookmarks.length, itemBuilder: (context, index) { final parts = bookmarks[index].split('||'); return ListTile( title: Text(parts[0]), onTap: () { Navigator.pop(context); _epubController.gotoEpubCfi(parts[1]); }, trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () async { bookmarks.removeAt(index); await _prefs.setStringList(_bookmarksKey, bookmarks); setState(() {}); }, ), ); }, ), ), ); } }
关键说明
- 续读逻辑:通过监听
EpubController的位置变化实时保存CFI,初始化时读取并跳转,确保每次打开都回到上次阅读位置。 - 书签逻辑:将章节标题与CFI拼接成字符串存储,避免重复添加;提供书签列表展示、跳转和删除功能。
- 多书籍适配:若支持多本书籍,需将存储键(如
_lastReadKey、_bookmarksKey)与书籍唯一标识绑定,避免数据冲突。
内容的提问来源于stack exchange,提问作者Arsh
相关产品推荐
相关产品推荐

