Reader Kit(阅读服务)为开发者提供多种格式电子书的解析、排版、阅读交互能力,开发者可以借助Reader Kit的能力和组件快速构建书籍阅读能力。
通过getDomPosByCatalogHref获取domPos信息,通过getSpineList得到Spine的列表再对比获得对应章节的spineIndex。最后使用startPlay完成跳转。
import { display } from '@kit.ArkUI'; import { fileIo } from '@kit.CoreFileKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { ReadPageComponent, readerCore, bookParser } from '@kit.ReaderKit'; import { common } from '@kit.AbilityKit'; const TAG: string = 'ReaderKitJumpCatalogItem'; @Entry @Component struct Index { private defaultHandler: bookParser.BookParserHandler | null = null; private readerComponentController: readerCore.ReaderComponentController = new readerCore.ReaderComponentController(); /** * Display dialog box */ @State showModalBanner: boolean = false; /** * Menu bar type index, 0 : catalog list, 1 : setting, 1 : close dialog */ @State currentIndex: number = -1; @State catalogItemList: bookParser.CatalogItem[] = []; @State bookTitle: string = ''; private readerSetting: readerCore.ReaderSetting = { fontName: '系统字体', fontPath: '', fontSize: 18, fontColor: '#000000', fontWeight: 400, lineHeight: 1.9, nightMode: false, themeColor: 'rgba(248, 249, 250, 1)', themeBgImg: '', flipMode: '0', scaledDensity: display.getDefaultDisplaySync().scaledDensity > 0 ? display.getDefaultDisplaySync().scaledDensity : 1, viewPortWidth: 1216, viewPortHeight: 2688, }; private screenDensityCallBack: Callback<number> | null = null; copyRawfileToSanBox(context: common.UIAbilityContext, bookName: string): string { let bookSandBoxPath = context.filesDir + '/' + bookName; try { let data = context.resourceManager.getRawFileContentSync(bookName); let buffer = data.buffer; let file = fileIo.openSync(bookSandBoxPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE); fileIo.writeSync(file.fd, buffer); fileIo.close(file.fd).catch(() => { hilog.error(0x0000, TAG, `file close failed`); }); } catch (err) { let e = err as BusinessError; hilog.error(0x0000, TAG,`copy book rawfile to sanbox failed: code = ${e.code}, message = ${e.message}`); } return bookSandBoxPath; } aboutToAppear(): void { // 代码正常运行需要在entry\src\main\resources\rawfile目录下新增名称为test.txt的小说文本文件 let filePath = this.copyRawfileToSanBox(this.getUIContext().getHostContext() as common.UIAbilityContext, 'test.txt'); let resourceIndex = 0; let domPos = ''; this.startPlay(filePath, resourceIndex, domPos); } private async startPlay(path: string, resourceIndex: number, domPos: string) { try { let context = this.getUIContext().getHostContext() as common.UIAbilityContext; let initPromise: Promise<void> = this.readerComponentController.init(context); let defaultHandler: Promise<bookParser.BookParserHandler> = bookParser.getDefaultHandler(path); let result: [bookParser.BookParserHandler, void] = await Promise.all([defaultHandler, initPromise]); this.defaultHandler = result[0]; this.readerComponentController.registerBookParser(this.defaultHandler); this.readerComponentController.setPageConfig(this.readerSetting); this.readerComponentController.startPlay(resourceIndex || 0, domPos).catch(() => { hilog.error(0x0000, TAG, `startPlay failed`); }); } catch (err) { hilog.error(0x0000, TAG, `startPlay: err: + ${JSON.stringify(err)}`); } } private async getBookInfo() { try { let bookInfo: bookParser.BookInfo | undefined = this.defaultHandler?.getBookInfo(); if (bookInfo) { this.bookTitle = bookInfo.bookTitle || ''; } hilog.info(0x0000, TAG, 'getBookInfo bookInfo is: ' + JSON.stringify(bookInfo)); } catch (error) { hilog.error(0x0000, TAG, `getBookInfo failed, Code: ${error.code}, message: ${error.message}`); } } private showModal() { this.showModalBanner = true; } private closeModal() { this.showModalBanner = false; this.currentIndex = -1; } private jumpToCatalogList() { this.currentIndex = 0; try { this.catalogItemList = this.defaultHandler?.getCatalogList() || []; } catch (error) { hilog.error(0x0000, TAG, `getCatalogList failed, Code: ${error.code}, message: ${error.message}`); } this.getBookInfo(); hilog.info(0x0000, TAG, 'catalog list length: ' + this.catalogItemList.length); } // 跳转目录函数 private async jumpToCatalogItem(catalogItem: bookParser.CatalogItem) { const domPos = await this.getDomPos(catalogItem); const resourceIndex = this.getResourceItemByCatalog(catalogItem).index; this.readerComponentController.startPlay(resourceIndex, domPos).catch(() => { hilog.error(0x0000, TAG, `startPlay failed`); }); this.closeModal(); } // 获取dompos private async getDomPos(catalogItem: bookParser.CatalogItem): Promise<string> { try { const domPos: string = this.defaultHandler?.getDomPosByCatalogHref(catalogItem.href || '') || ''; return domPos; } catch (error) { hilog.error(0x0000, TAG, `getDomPos failed, Code: ${error.code}, message: ${error.message}`); } return Promise.reject(); } // 获取spineIndex private getResourceItemByCatalog(catalogItem: bookParser.CatalogItem): bookParser.SpineItem { let resourceFile = catalogItem.resourceFile || ''; try { let spineList: bookParser.SpineItem[] = this.defaultHandler?.getSpineList() || []; let resourceItemArr = spineList.filter(item => item.href === resourceFile); if (resourceItemArr.length > 0) { hilog.info(0x0000, TAG, 'getResourceItemByCatalog get resource ', resourceItemArr[0]); let resourceItem = resourceItemArr[0]; return resourceItem; } else if (spineList.length > 0) { hilog.info(0x0000, TAG, 'getResourceItemByCatalog get resource in resourceList', spineList[0]); return spineList[0]; } } catch (error) { hilog.error(0x0000, TAG, `getSpineList failed, Code: ${error.code}, message: ${error.message}`); } hilog.info(0x0000, TAG, 'getResourceItemByCatalog get resource in escape'); return { idRef: '', index: 0, href: '', properties: '' }; } aboutToDisappear(): void { try { display.off('change', this.screenDensityCallBack); } catch (error) { hilog.error(0x0000, TAG, `display.off change failed, Code: ${error.code}, message: ${error.message}`); } this.readerComponentController.off('pageShow'); this.readerComponentController.releaseBook(); } // 目录列表 @Builder private buildCatalogItemList() { Column() { Row() { Text(this.bookTitle) .maxLines(1) .margin({ right: 12, left: 12 }) .fontWeight(FontWeight.Bold) .flexShrink(1) .height(40) .visibility(this.bookTitle ? Visibility.Visible : Visibility.None) } .padding({ left: 16, right: 16 }) .width('100%') .margin({ bottom: 20 }) .alignSelf(ItemAlign.Start) List() { ForEach(this.catalogItemList, (item: bookParser.CatalogItem) => { ListItem() { Column() { Row() { Row() { Text(' · ') .fontSize(14) Text(item.catalogName) .fontSize(14) .textOverflow({ overflow: TextOverflow.Ellipsis }) .padding({ top: 8, bottom: 8 }) .maxLines(2) .layoutWeight(1) } } .width('100%') .height(48) .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) Divider() } .padding({ left: item.catalogLevel ? item.catalogLevel * 26 : 16, right: 16, top: 6, bottom: 6 }) .onClick(async () => { this.jumpToCatalogItem(item); }) } }) } .scrollBar(BarState.Off) .width('100%') .height('100%') } .borderRadius({ topRight: 32, topLeft: 32 }) .visibility(this.currentIndex === 0 ? Visibility.Visible : Visibility.None) .backgroundColor(Color.White) .zIndex(3) } build() { Stack() { Column(){ Text('注:本文由AI生成无任何版权纠纷!') .fontSize(14) .zIndex(2) .position({x:30, y:90}) .fontColor('rgba(0, 0, 0, 0.5)') ReadPageComponent({ controller: this.readerComponentController, readerCallback: (err: BusinessError, data: readerCore.ReaderComponentController) => { this.readerComponentController = data; hilog.error(0x0000, TAG, `ReadPageComponent: err: + ${JSON.stringify(err)}`); } }) .position({y:20}) .zIndex(1) Column() { Column() { Column() { // 目录列表显示 this.buildCatalogItemList(); } .padding({ bottom: 100 }) .backgroundColor(Color.White) } .visibility(this.currentIndex < 0 ? Visibility.None : Visibility.Visible) .width('100%') .height(this.currentIndex === 0 ? 'calc(100% - 80vp - 56vp)' : '60%') .justifyContent(FlexAlign.End) .onClick(() => { this.showModalBanner = true; }) Row() { Text('目录') .width('100%') .height('100%') .onClick(() => { this.jumpToCatalogList(); }) .textAlign(TextAlign.Center) } .width('100%') .height(80) .backgroundColor(Color.White) } .width('100%') .height('100%') .backgroundColor(this.currentIndex === 0 ? '#0d626262' : Color.Transparent) .zIndex(this.showModalBanner ? 3 : 0) .justifyContent(FlexAlign.End) .onClick(() => { this.closeModal(); }) } .height('100%') .width('100%') .zIndex(3) }.width('100%').height('100%').onClick(() => { this.showModal(); }) } }
代码正常运行需要在“entry\src\main\resources\rawfile”目录下新增名称为test.txt的小说文本文件。