HarmonyOS readerCore(阅读核心能力)使用指南

效果

一、概述

readerCore 是 HarmonyOS Reader Kit 的核心模块,提供了阅读页面数据基础类、页面状态管理、内容分页信息、页面排版属性、组件控制器等完整能力。它是阅读类应用实现排版控制、翻页交互、进度管理的核心依赖。

1.1 模块定位

Reader Kit
├── bookParser        ← 书籍解析(输入层)
├── readerCore        ← 阅读核心(控制层)← 本文重点
└── ReadPageComponent ← 阅读页组件(展示层)

readerCore 处于中间控制层,向下调度书籍解析能力,向上驱动阅读页组件渲染。开发者通过 readerCore 提供的控制器接口,实现对阅读行为的全面控制。

1.2 导入方式

import { readerCore } from '@kit.ReaderKit';

二、核心类型详解

2.1 ReaderComponentController(组件控制器)

ReaderComponentControllerreaderCore 的核心类,提供了对 ReadPageComponent 的完整控制能力。

创建实例
private controller: readerCore.ReaderComponentController = 
  new readerCore.ReaderComponentController();
完整方法列表
方法 返回值 说明
init(context) Promise<void> 初始化控制器,传入 UIAbilityContext
setPageConfig(config) void 设置/更新页面排版属性
registerBookParser(handler) void 注册书籍解析器
startPlay(spineIndex, domPos) Promise<void> 以指定进度打开书籍
flipPage(isNext) void 控制翻页(true=下一页,false=上一页)
releaseBook() void 释放书籍资源
on(event, callback) void 监听阅读事件
off(event) void 取消事件监听

2.2 ReaderSetting(排版配置)

ReaderSetting 定义了阅读页面的所有排版和视觉属性。

interface ReaderSetting {
  fontName: string;      // 字体名称
  fontPath: string;      // 自定义字体文件路径
  fontSize: number;      // 字体大小
  fontColor: string;     // 字体颜色(十六进制或rgba)
  fontWeight: number;    // 字体粗细(1-9)
  lineHeight: number;    // 行高倍数
  nightMode: boolean;    // 夜间模式开关
  themeColor: string;    // 主题背景色
  themeBgImg: string;    // 主题背景图路径
  flipMode: string;      // 翻页模式
  scaledDensity: number; // 屏幕像素密度
  viewPortWidth: number; // 视口宽度(像素)
  viewPortHeight: number;// 视口高度(像素)
}
各属性详解
字体相关
属性 默认值 说明
fontName '' 字体名称,空字符串使用系统默认字体
fontPath '' 自定义字体的文件路径,空字符串不加载自定义字体
fontSize 18 字体大小,单位由系统排版引擎处理
fontColor '#000000' 字体颜色,支持 #RRGGBBrgba() 格式
fontWeight 4 字体粗细,范围 1-9(4 为正常,7 为粗体)
lineHeight 1.8 行高倍数,推荐 1.5-2.0
主题相关
属性 默认值 说明
nightMode false 夜间模式,开启后字体颜色自动适配深色背景
themeColor '#FAFAFA' 背景色,常见选项:白色 #FFFFFF、护眼黄 #EAE2CF、羊皮纸 #F5E7C8
themeBgImg '' 背景图片路径,空字符串不使用背景图
翻页模式
属性 说明
flipMode '0' 仿真翻页(模拟纸张翻转效果)
flipMode '1' 横滑翻页(左右滑动切换)
设备参数
属性 获取方式 说明
scaledDensity display.getDefaultDisplaySync().scaledDensity 屏幕像素密度比,必须 > 0
viewPortWidth display.getDefaultDisplaySync().width 视口宽度(物理像素)
viewPortHeight display.getDefaultDisplaySync().height 视口高度(物理像素)

2.3 PageDataInfo(页面数据信息)

页面事件回调中返回的页面状态信息:

interface PageDataInfo {
  state: PageState;     // 页面状态
  // 其他页面信息...
}

2.4 PageState(页面状态枚举)

枚举值 说明
PageState.PAGE_ON_SHOW 页面显示完成
PageState.PAGE_ON_HIDE 页面隐藏

三、控制器方法详解

3.1 init(context)

初始化组件控制器。必须在所有其他控制器方法之前调用。

import { common } from '@kit.AbilityKit';

let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
await this.controller.init(context);
  • 参数UIAbilityContext 应用上下文
  • 返回Promise<void>
  • 注意:初始化是异步操作,需要 await 等待完成

3.2 setPageConfig(config)

设置或更新页面排版属性。可以在阅读过程中动态调用以更新排版。

// 初始化时设置
this.controller.setPageConfig({
  fontSize: 18,
  fontColor: '#333333',
  themeColor: '#FAFAFA',
  lineHeight: 1.8,
  flipMode: '0',
  // ...其他配置
});

// 动态更新(如用户调整字号)
let newConfig = { ...this.pageConfig, fontSize: 22 };
this.controller.setPageConfig(newConfig);

3.3 registerBookParser(handler)

注册书籍解析器。必须在 startPlay 之前调用。

import { bookParser } from '@kit.ReaderKit';

let handler = await bookParser.getDefaultHandler(filePath);
this.controller.registerBookParser(handler);

3.4 startPlay(spineIndex, domPos)

以指定的阅读进度打开书籍,使用 Promise 异步回调。

// spineIndex: 章节索引(从 getSpineList 获取)
// domPos: DOM 位置定位(空字符串表示章节开头)
await this.controller.startPlay(0, '');
  • spineIndex:章节索引号,通过 BookParserHandler.getSpineList() 获取
  • domPos:章节内的位置标识,用于精确定位阅读进度

3.5 flipPage(isNext)

控制翻页方向。

// 翻到下一页
this.controller.flipPage(true);

// 翻到上一页
this.controller.flipPage(false);

这是实现音量键翻页、手势翻页等自定义翻页交互的核心方法。

3.6 releaseBook()

释放书籍资源。必须在组件销毁时调用。

aboutToDisappear(): void {
  this.controller.releaseBook();
}

3.7 on(event, callback) / off(event)

监听和取消阅读事件。

// 监听页面显示事件
this.controller.on('pageShow', (data: readerCore.PageDataInfo) => {
  if (data.state === readerCore.PageState.PAGE_ON_SHOW) {
    console.info('Page is now visible');
  }
});

// 取消监听
this.controller.off('pageShow');

四、完整实现示例

4.1 基础阅读器实现

import { bookParser, ReadPageComponent, readerCore } from '@kit.ReaderKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { display } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import files from '@ohos.file.fs';

@ComponentV2
struct BasicReader {
  @Local isLoading: boolean = true;
  @Local currentPage: string = '';
  private controller: readerCore.ReaderComponentController = 
    new readerCore.ReaderComponentController();
  private parserHandler: bookParser.BookParserHandler | null = null;

  private createPageConfig(): readerCore.ReaderSetting {
    let d = display.getDefaultDisplaySync();
    return {
      fontName: '',
      fontPath: '',
      fontSize: 18,
      fontColor: '#333333',
      fontWeight: 4,
      lineHeight: 1.8,
      nightMode: false,
      themeColor: '#FAFAFA',
      themeBgImg: '',
      flipMode: '0',
      scaledDensity: d.scaledDensity > 0 ? d.scaledDensity : 1,
      viewPortWidth: d.width,
      viewPortHeight: d.height
    };
  }

  private async loadBook(filePath: string): Promise<void> {
    try {
      let context = this.getUIContext().getHostContext() as common.UIAbilityContext;

      // 1. 初始化控制器(异步)
      let initPromise = this.controller.init(context);

      // 2. 创建解析器(异步)
      let parserPromise = bookParser.getDefaultHandler(filePath);

      // 3. 并行等待
      let results = await Promise.all([parserPromise, initPromise]);
      this.parserHandler = results[0];

      // 4. 获取章节信息
      let spineList = this.parserHandler.getSpineList();
      let startIndex = spineList[0].index;

      // 5. 配置排版
      this.controller.setPageConfig(this.createPageConfig());

      // 6. 注册解析器
      this.controller.registerBookParser(this.parserHandler);

      // 7. 监听页面事件
      this.controller.on('pageShow', (data: readerCore.PageDataInfo) => {
        if (data.state === readerCore.PageState.PAGE_ON_SHOW) {
          this.isLoading = false;
        }
      });

      // 8. 开始阅读
      await this.controller.startPlay(startIndex || 0, '');

    } catch (err) {
      console.error('loadBook failed:', JSON.stringify(err));
    }
  }

  aboutToAppear(): void {
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    let bookPath = context.filesDir + '/sample.txt';
    this.loadBook(bookPath);
  }

  aboutToDisappear(): void {
    this.controller.off('pageShow');
    this.controller.releaseBook();
  }

  build() {
    Stack() {
      ReadPageComponent({
        controller: this.controller,
        readerCallback: (err: BusinessError, data: readerCore.ReaderComponentController) => {
          if (err) {
            console.error('ReadPageComponent error:', err.message);
          }
          this.controller = data;
        }
      })

      if (this.isLoading) {
        Column() {
          LoadingProgress().width(48).height(48)
          Text('加载中...').fontSize(14).margin({ top: 8 })
        }
        .width('100%').height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#FAFAFA')
      }
    }
    .width('100%').height('100%')
  }
}

4.2 动态调整排版

展示如何在阅读过程中动态更新排版属性(如字号、主题色):

@ComponentV2
struct ReaderWithSettings {
  @Local currentFontSize: number = 18;
  @Local isNightMode: boolean = false;
  private controller: readerCore.ReaderComponentController = 
    new readerCore.ReaderComponentController();
  private baseConfig: readerCore.ReaderSetting = { /* ... */ };

  // 调整字号
  changeFontSize(delta: number): void {
    this.currentFontSize = Math.max(12, Math.min(32, this.currentFontSize + delta));
    this.baseConfig.fontSize = this.currentFontSize;
    this.controller.setPageConfig(this.baseConfig);
  }

  // 切换夜间模式
  toggleNightMode(): void {
    this.isNightMode = !this.isNightMode;
    this.baseConfig.nightMode = this.isNightMode;
    this.baseConfig.themeColor = this.isNightMode ? '#1A1A1A' : '#FAFAFA';
    this.baseConfig.fontColor = this.isNightMode ? '#CCCCCC' : '#333333';
    this.controller.setPageConfig(this.baseConfig);
  }

  build() {
    Column() {
      ReadPageComponent({
        controller: this.controller,
        readerCallback: (err, data) => { this.controller = data; }
      })
        .layoutWeight(1)

      Row() {
        Button('A-').onClick(() => this.changeFontSize(-2))
        Text(`${this.currentFontSize}`).fontSize(14).margin({ left: 12, right: 12 })
        Button('A+').onClick(() => this.changeFontSize(2))
        Blank()
        Button(this.isNightMode ? '☀' : '🌙').onClick(() => this.toggleNightMode())
      }
      .width('100%')
      .padding(16)
      .justifyContent(FlexAlign.Center)
    }
  }
}

4.3 音量键翻页集成

readerCoreflipPage 方法与 inputConsumer 结合:

import { inputConsumer, KeyCode } from '@kit.InputKit';

@ComponentV2
struct VolumeKeyReader {
  @Param enableVolumeKey: boolean = false;
  private controller: readerCore.ReaderComponentController = 
    new readerCore.ReaderComponentController();

  @Monitor('enableVolumeKey')
  onVolumeKeyChanged(): void {
    if (this.enableVolumeKey) {
      // 音量+ → 上一页
      inputConsumer.on('keyPressed', {
        key: KeyCode.KEYCODE_VOLUME_UP,
        action: 1,
        isRepeat: false
      }, () => {
        this.controller.flipPage(false);
      });

      // 音量- → 下一页
      inputConsumer.on('keyPressed', {
        key: KeyCode.KEYCODE_VOLUME_DOWN,
        action: 1,
        isRepeat: false
      }, () => {
        this.controller.flipPage(true);
      });
    } else {
      inputConsumer.off('keyPressed');
    }
  }

  aboutToDisappear(): void {
    if (this.enableVolumeKey) {
      inputConsumer.off('keyPressed');
    }
    this.controller.releaseBook();
  }

  build() {
    ReadPageComponent({
      controller: this.controller,
      readerCallback: (err, data) => { this.controller = data; }
    })
  }
}

五、API 调用顺序

以下是各 API 的正确调用顺序,顺序错误可能导致功能异常:

┌──────────────────────────────────────────────┐
│  1. new ReaderComponentController()          │ 创建实例
│  2. controller.init(context)                 │ 初始化(异步)
│  3. bookParser.getDefaultHandler(path)       │ 获取解析器(异步)
│     ↑ 步骤2和3可并行执行                       │
│  4. controller.setPageConfig(config)         │ 设置排版
│  5. controller.registerBookParser(handler)   │ 注册解析器
│  6. controller.on('pageShow', callback)      │ 监听事件(可选)
│  7. controller.startPlay(index, pos)         │ 开始阅读
│     ─────── 阅读过程中 ───────                │
│  8. controller.flipPage(true/false)          │ 翻页操作
│  9. controller.setPageConfig(newConfig)      │ 更新排版(可选)
│     ─────── 页面销毁时 ───────                │
│ 10. controller.off('pageShow')               │ 取消事件监听
│ 11. controller.releaseBook()                 │ 释放资源
└──────────────────────────────────────────────┘

六、常见问题与解决方案

6.1 页面排版异常(文字溢出/空白过多)

原因viewPortWidthviewPortHeight 未使用实际设备像素值。

解决方案

let d = display.getDefaultDisplaySync();
// 确保使用真实值
viewPortWidth: d.width,
viewPortHeight: d.height,
scaledDensity: d.scaledDensity > 0 ? d.scaledDensity : 1,

6.2 初始化失败

原因:未传入正确的 UIAbilityContext

解决方案

let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
await this.controller.init(context);

6.3 startPlay 报错

原因:在 registerBookParser 之前调用了 startPlay

解决方案:严格按照 registerBookParserstartPlay 的顺序调用。

6.4 翻页无响应

原因:书籍尚未完成加载或控制器未正确初始化。

解决方案:确保 startPlay 返回的 Promise 已 resolve,且控制器已正确初始化。

6.5 页面销毁后报错

原因:未在 aboutToDisappear 中释放资源。

解决方案

aboutToDisappear(): void {
  this.controller.off('pageShow');
  this.controller.releaseBook();
}

七、最佳实践

  1. 并行初始化:使用 Promise.all 并行执行 initgetDefaultHandler,减少启动时间
  2. Loading 状态:监听 pageShow 事件,在页面显示完成后关闭 Loading
  3. 设备适配:始终使用 display.getDefaultDisplaySync() 获取真实设备参数
  4. 资源管理:在 aboutToDisappear 中确保 releaseBook() 被调用
  5. 错误处理:所有异步操作使用 try-catch 包裹,记录详细错误信息
  6. 状态管理 V2:使用 @ComponentV2 + @Param + @Monitor 实现响应式的阅读控制

八、总结

readerCore 模块是 HarmonyOS 阅读类应用的核心引擎,通过 ReaderComponentController 提供了从初始化、排版、翻页到资源释放的完整生命周期管理。

核心要点

  • ReaderComponentController 是唯一的控制器入口
  • ReaderSetting 控制所有排版和视觉属性
  • API 调用顺序严格:initsetPageConfigregisterBookParserstartPlay
  • flipPage() 是实现自定义翻页交互的关键方法
  • 页面销毁时务必调用 releaseBook() 释放资源

参考文档

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐