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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 00:06:07