From adbe3cabccfa340cbffac0ce88b81462510a28b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=94=E4=B8=89=E7=9A=84=E9=9D=93=E4=BB=94?= <36236732+yindushenwen@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:54:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E7=94=A8canvas=E6=96=B9=E6=A1=88?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 阅读器边距调节 * 抽离单独处理txt文件,以便于后面处理其它文件 * 改用canvas方案渲染 --- .../componets/Reader/TxtContentHandler.ets | 238 +++++++++++++++--- .../main/ets/pages/view/Reader/ReaderPage.ets | 73 +++--- 2 files changed, 239 insertions(+), 72 deletions(-) diff --git a/entry/src/main/ets/componets/Reader/TxtContentHandler.ets b/entry/src/main/ets/componets/Reader/TxtContentHandler.ets index 9139e324..6102f94d 100644 --- a/entry/src/main/ets/componets/Reader/TxtContentHandler.ets +++ b/entry/src/main/ets/componets/Reader/TxtContentHandler.ets @@ -1,56 +1,222 @@ -import { display } from '@kit.ArkUI'; -import { LengthMetrics } from '@ohos.arkui.node'; +//todo: Text方案,用原生Text组件渲染 +// import { display } from '@kit.ArkUI'; +// import { LengthMetrics } from '@ohos.arkui.node'; +// import { chaptersItem } from 'ets/componets/dataList/ReaderChaptersItem'; +// +// @Component +// export struct TxtContentHandler { +// @StorageLink('WindowHeight') WindowHeight: number = 0 +// @StorageLink('WindowWidth') WindowWidth: number = 0 +// @State txtChapterContent: string = ''; // 当前章节的文本 +// @State txtShowContent: string = ''; // 当前章节的文本 +// @State ContentHeight: number = 0 +// @Prop txtFile: chaptersItem[] = []; +// @Prop CurrentChapters: number = 0; +// @Prop LineHeight: string = ''; +// @Prop CurrentFontSize: number = 0; +// @Prop ParagraphSpacing: number = 0; +// private settings: RenderingContextSettings = new RenderingContextSettings(true); +// private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); +// +// aboutToAppear() { +// display.on('foldDisplayModeChange', () => { +// let defaultDisplay = display.getDefaultDisplaySync() +// this.WindowHeight = px2vp(defaultDisplay.height) +// this.WindowWidth = px2vp(defaultDisplay.width) +// console.log('WindowHeight:' + this.WindowHeight) +// console.log('WindowWidth:' + this.WindowWidth) +// // this.drawText(this.startIndex); +// }); +// } +// +// aboutToDisappear(): void { +// display.off('foldDisplayModeChange'); +// } +// +// drawText() { +// // this.txtChapterContent = this.txtFile[this.CurrentChapters]?.content +// // console.log('content:' + JSON.stringify(this.txtChapterContent)) +// // console.log('WindowHeight:' + this.WindowHeight) +// // console.log('WindowWidth:' + this.WindowWidth) +// // if (this.ContentHeight < this.WindowHeight) { +// // +// // } +// } +// +// build() { +// Text(this.txtFile[this.CurrentChapters.toFixed(0)]?.content) +// .lineHeight(this.LineHeight) +// .font({ +// size: this.CurrentFontSize, // 用px感觉太小了,用vp算了 +// weight: 400 +// }) +// .lineSpacing(LengthMetrics.vp(this.CurrentFontSize + this.ParagraphSpacing)) +// .fontColor("rgba(0, 0, 0, 0.88)") +// .width("100%") +// } +// } + +// todo: Canvas方案,按行拆分,行内按字拆分 import { chaptersItem } from 'ets/componets/dataList/ReaderChaptersItem'; @Component export struct TxtContentHandler { - @StorageLink('WindowHeight') WindowHeight: number = 0 - @StorageLink('WindowWidth') WindowWidth: number = 0 - @State txtChapterContent: string = ''; // 当前章节的文本 - @State txtShowContent: string = ''; // 当前章节的文本 - @State ContentHeight: number = 0 @Prop txtFile: chaptersItem[] = []; - @Prop CurrentChapters: number = 0; - @Prop LineHeight: string = ''; - @Prop CurrentFontSize: number = 0; + @Prop @Watch('CurrentChaptersChange') CurrentChapters: number = 0; + @Prop @Watch('CurrentFontSizeChange') CurrentFontSize: number = 0; + @Prop LineHeight: number = 1.8; // 百分比 @Prop ParagraphSpacing: number = 0; + @Prop ReaderPadding: number = 0; + @State lines: string[] = []; + @State currentPage: number = 0; + @State ShowHeight: number = 0 + @State ShowWidth: number = 0 + @State linesPerPage: number = 0; + @State isChangeTimer: number = 0; + @State paragraphs: string[] = [] private settings: RenderingContextSettings = new RenderingContextSettings(true); private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); + // 获取总页数 + GetTotalPages() { + return Math.floor(this.lines.length / this.linesPerPage); + } + aboutToAppear() { - display.on('foldDisplayModeChange', () => { - let defaultDisplay = display.getDefaultDisplaySync() - this.WindowHeight = px2vp(defaultDisplay.height) - this.WindowWidth = px2vp(defaultDisplay.width) - console.log('WindowHeight:' + this.WindowHeight) - console.log('WindowWidth:' + this.WindowWidth) - // this.drawText(this.startIndex); + setTimeout(() => { + this.paragraphs = this.txtFile[this.CurrentChapters]?.content.split('\n'); + this.prepareText() + this.drawPage(); + console.log('lines:' + this.lines.length) + console.log('linesPerPage:' + this.linesPerPage) + }, 300) + } + + CurrentChaptersChange() { + this.paragraphs = this.txtFile[this.CurrentChapters]?.content.split('\n'); + this.currentPage = 0 + this.prepareText() + this.drawPage(); + console.log('currentPage:' + this.currentPage + '\ntotalPages:' + this.GetTotalPages()) + } + + CurrentFontSizeChange() { + console.log('isCurrentFontSizeChangeTimer:' + this.isChangeTimer) + this.drawPage(); + + if (this.isChangeTimer) { + // 防抖 + clearTimeout(this.isChangeTimer) + } + + this.isChangeTimer = setTimeout(() => { + this.prepareText() + this.drawPage(); + console.log('currentPage:' + this.currentPage + '\ntotalPages:' + this.GetTotalPages()) + }, 1000) + } + + prepareText() { + this.context.font = `${vp2px(this.CurrentFontSize)}px`; + this.lines = this.wrapText(this.context, this.ShowWidth); + this.linesPerPage = Math.floor(this.ShowHeight / (this.CurrentFontSize * this.LineHeight)); + } + + drawPage() { + if (this.context) { + this.context.clearRect(0, 0, this.ShowWidth, this.ShowHeight); + this.context.font = `${vp2px(this.CurrentFontSize)}px`; + this.context.fillStyle = 'black'; + + const start = this.currentPage * this.linesPerPage; + const end = start + this.linesPerPage; + const pageLines = this.lines.slice(start, end); + console.log('当前页面渲染内容:' + JSON.stringify(pageLines)) // 当前页面需求渲染的内容 + pageLines.forEach((line, index) => { + this.context.fillText(line, 0, (index + 1) * this.CurrentFontSize * Number(this.LineHeight)); + }); + } + } + + // 当前章节内容按字分割 + // todo:当前一次性分割一整个章节的文字,需要大佬优化 + wrapText(context: CanvasRenderingContext2D, maxWidth: number = this.ShowWidth) { + const lines: string[] = []; + this.paragraphs.forEach(paragraph => { + const words = paragraph.split(''); // todo:这里性能缓慢,需要优化 + let currentLine = words[0]; + + for (let i = 1; i < words.length; i++) { + const word = words[i]; + const width = context.measureText(currentLine + ' ' + word).width; + if (width < maxWidth) { + currentLine += ' ' + word; + } else { + lines.push(currentLine); + currentLine = word; + } + } + lines.push(currentLine); }); + return lines; } - aboutToDisappear(): void { - display.off('foldDisplayModeChange'); + nextPage() { + if (this.currentPage < this.GetTotalPages()) { + this.currentPage++; + console.log('currentPage:' + this.currentPage + '\ntotalPages:' + this.GetTotalPages()) + this.drawPage(); + } } - drawText() { - // this.txtChapterContent = this.txtFile[this.CurrentChapters]?.content - // console.log('content:' + JSON.stringify(this.txtChapterContent)) - // console.log('WindowHeight:' + this.WindowHeight) - // console.log('WindowWidth:' + this.WindowWidth) - // if (this.ContentHeight < this.WindowHeight) { - // - // } + prevPage() { + if (this.currentPage > 0) { + this.currentPage--; + console.log('prevPage:' + this.currentPage) + this.drawPage(); + } } build() { - Text(this.txtFile[this.CurrentChapters.toFixed(0)]?.content) - .lineHeight(this.LineHeight) - .font({ - size: this.CurrentFontSize, // 用px感觉太小了,用vp算了 - weight: 400 - }) - .lineSpacing(LengthMetrics.vp(this.CurrentFontSize + this.ParagraphSpacing)) - .fontColor("rgba(0, 0, 0, 0.88)") - .width("100%") + Column() { + Canvas(this.context) + .width("100%") + .layoutWeight(1) + .borderWidth(1) + .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => { + this.ShowHeight = Number(newValue.height) + this.ShowWidth = Number(newValue.width) + this.drawPage() + + if (this.isChangeTimer) { + // 防抖 + clearTimeout(this.isChangeTimer) + } + this.isChangeTimer = setTimeout(() => { + this.prepareText() + this.drawPage(); + console.log('currentPage:' + this.currentPage + '\ntotalPages:' + this.GetTotalPages()) + }, 1000) + + console.log('ShowHeight:' + this.ShowHeight) + console.log('ShowWidth:' + this.ShowWidth) + console.log('currentPage:' + this.currentPage + '\ntotalPages:' + this.GetTotalPages()) + }) + Row() { + Button('上一页') + .onClick(() => { + this.prevPage() + }) + .backgroundColor(this.currentPage > 0 ? 'blue' : 'gray') + + Button('下一页') + .onClick(() => { + this.nextPage() + }) + .backgroundColor(this.currentPage < this.GetTotalPages() ? 'blue' : 'gray') + } + .borderWidth(1) + .margin({ top: 10 }) + } } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/view/Reader/ReaderPage.ets b/entry/src/main/ets/pages/view/Reader/ReaderPage.ets index 99532d32..a520d70e 100644 --- a/entry/src/main/ets/pages/view/Reader/ReaderPage.ets +++ b/entry/src/main/ets/pages/view/Reader/ReaderPage.ets @@ -32,7 +32,7 @@ struct ReaderPage { @State TotalChapters: number = 0 @State CurrentLuminance: number = 0 // 亮度 @State CurrentFontSize: number = 20 // 字号 - @State LineHeight: string = "180%" + @State LineHeight: number = 1.8 @State ParagraphSpacing: number = -2 @State isShowSource: boolean = false @State isShowPurification: boolean = false @@ -73,7 +73,8 @@ struct ReaderPage { .onClick(() => { router.back() }) - Text(`${this.txtFile[this.CurrentChapters.toFixed(0)]?.chapterTitleNumber} ${this.txtFile[this.CurrentChapters.toFixed(0)]?.title}`.replace(new RegExp('\\s{1,}','g'),' ').replaceAll('=','')) + Text(`${this.txtFile[this.CurrentChapters.toFixed(0)]?.chapterTitleNumber} ${this.txtFile[this.CurrentChapters.toFixed(0)]?.title}`.replace(new RegExp('\\s{1,}', + 'g'), ' ').replaceAll('=', '')) .layoutWeight(1) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) @@ -283,17 +284,17 @@ struct ReaderPage { Text('排版') Button('紧凑') .onClick(() => { - this.LineHeight = "160%" + this.LineHeight = 1.6 this.ParagraphSpacing = -4 }) Button('适中') .onClick(() => { - this.LineHeight = "180%" + this.LineHeight = 1.8 this.ParagraphSpacing = -2 }) Button('宽松') .onClick(() => { - this.LineHeight = "200%" + this.LineHeight = 2 this.ParagraphSpacing = 0 }) Row() { @@ -398,39 +399,39 @@ struct ReaderPage { .width("100%") .height(40) - Scroll() { - Column() { - Row() { - Text(`${this.txtFile[this.CurrentChapters.toFixed(0)]?.chapterTitleNumber} ${this.txtFile[this.CurrentChapters.toFixed(0)]?.title}`.replace(new RegExp('\\s{1,}','g'),' ').replaceAll('=','')) - .lineHeight(20) - .font({ - size: 15, - weight: 700 - }) - .fontColor("rgba(0, 0, 0, 0.88)") - } - .padding({ - top: 16, - bottom: 16, - left: 24, - right: 24 - }) - .width("100%") - - // txt小说内容显示处理 - TxtContentHandler({ - txtFile: this.txtFile, - CurrentChapters: this.CurrentChapters, - LineHeight: this.LineHeight, - CurrentFontSize: this.CurrentFontSize, - ParagraphSpacing: this.ParagraphSpacing - }) - } - .padding({ - left: this.ReaderPadding, - right: this.ReaderPadding + Column() { + // Row() { + // Text(`${this.txtFile[this.CurrentChapters.toFixed(0)]?.chapterTitleNumber} ${this.txtFile[this.CurrentChapters.toFixed(0)]?.title}`.replace(new RegExp('\\s{1,}', + // 'g'), ' ').replaceAll('=', '')) + // .lineHeight(20) + // .font({ + // size: 15, + // weight: 700 + // }) + // .fontColor("rgba(0, 0, 0, 0.88)") + // } + // .padding({ + // top: 16, + // bottom: 16, + // left: 24, + // right: 24 + // }) + // .width("100%") + + // txt小说内容显示处理 + TxtContentHandler({ + txtFile: this.txtFile, + CurrentChapters: this.CurrentChapters, + LineHeight: this.LineHeight, + CurrentFontSize: this.CurrentFontSize, + ParagraphSpacing: this.ParagraphSpacing, + // ReaderPadding:this.ReaderPadding }) } + .padding({ + left: this.ReaderPadding, + right: this.ReaderPadding + }) .layoutWeight(1) Blank(1)