From 2fa035ce75b6a18864d5cb480059fed21e80adb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 04:11:06 +0000 Subject: [PATCH 01/31] Bump pnpm/action-setup from 2 to 3 Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 3. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v2...v3) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/web.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 09a21638f..506efa577 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -27,7 +27,7 @@ jobs: with: node-version: 16 - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v3 name: Install pnpm id: pnpm-install with: From 5ac5f8d60bd00d8ee6ac071ad20355ca4cb9e658 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Mon, 12 Feb 2024 20:28:31 +0800 Subject: [PATCH 02/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/lib/theme/ThemeStore.kt | 17 +- .../java/io/legado/app/model/ImageProvider.kt | 5 + .../main/java/io/legado/app/model/ReadBook.kt | 28 ++- .../app/ui/book/read/ReadBookActivity.kt | 30 +-- .../app/ui/book/read/page/ContentTextView.kt | 229 +++++------------- .../legado/app/ui/book/read/page/PageView.kt | 3 - .../legado/app/ui/book/read/page/ReadView.kt | 30 ++- .../app/ui/book/read/page/api/DataSource.kt | 2 + .../ui/book/read/page/entities/TextChapter.kt | 10 +- .../ui/book/read/page/entities/TextLine.kt | 69 +++++- .../ui/book/read/page/entities/TextPage.kt | 68 +++++- .../read/page/entities/column/BaseColumn.kt | 7 + .../read/page/entities/column/ButtonColumn.kt | 13 +- .../read/page/entities/column/ImageColumn.kt | 44 +++- .../read/page/entities/column/ReviewColumn.kt | 13 + .../read/page/entities/column/TextColumn.kt | 48 +++- .../read/page/provider/ChapterProvider.kt | 175 ++++++++----- .../ui/book/read/page/provider/TextMeasure.kt | 146 +++++++++++ .../read/page/provider/TextPageFactory.kt | 19 +- .../ui/book/read/page/provider/ZhLayout.kt | 9 +- .../java/io/legado/app/utils/BitmapCache.kt | 27 +++ .../legado/app/utils/CollectionExtensions.kt | 9 + .../java/io/legado/app/utils/PictureMirror.kt | 40 +++ 23 files changed, 734 insertions(+), 307 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt create mode 100644 app/src/main/java/io/legado/app/utils/CollectionExtensions.kt create mode 100644 app/src/main/java/io/legado/app/utils/PictureMirror.kt diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt index 9c8602eb5..0dab34719 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt @@ -163,7 +163,22 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { .apply() } - companion object { + companion object : SharedPreferences.OnSharedPreferenceChangeListener { + + init { + prefs(appCtx).registerOnSharedPreferenceChangeListener(this) + } + + var accentColor = accentColor() + + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences?, + key: String? + ) { + when (key) { + ThemeStorePrefKeys.KEY_ACCENT_COLOR -> accentColor = accentColor() + } + } fun editTheme(context: Context): ThemeStore { return ThemeStore(context) diff --git a/app/src/main/java/io/legado/app/model/ImageProvider.kt b/app/src/main/java/io/legado/app/model/ImageProvider.kt index a1320d488..1bb69b7ae 100644 --- a/app/src/main/java/io/legado/app/model/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/model/ImageProvider.kt @@ -212,4 +212,9 @@ object ImageProvider { }.getOrDefault(errorBitmap) } + fun clear() { + bitmapLruCache.evictAll() + BitmapCache.clear() + } + } diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 0d7ee0899..20bb7f48a 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -204,7 +204,7 @@ object ReadBook : CoroutineScope by MainScope() { return hasPrevPage } - fun moveToNextChapter(upContent: Boolean): Boolean { + fun moveToNextChapter(upContent: Boolean, upContentInPlace: Boolean = true): Boolean { if (durChapterIndex < chapterSize - 1) { durChapterPos = 0 durChapterIndex++ @@ -213,11 +213,11 @@ object ReadBook : CoroutineScope by MainScope() { nextTextChapter = null if (curTextChapter == null) { AppLog.putDebug("moveToNextChapter-章节未加载,开始加载") - callBack?.upContent() + if (upContentInPlace) callBack?.upContent(resetPageOffset = false) loadContent(durChapterIndex, upContent, resetPageOffset = false) - } else if (upContent) { + } else if (upContent && upContentInPlace) { AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图") - callBack?.upContent() + callBack?.upContent(resetPageOffset = false) } loadContent(durChapterIndex.plus(1), upContent, false) saveRead() @@ -233,7 +233,8 @@ object ReadBook : CoroutineScope by MainScope() { fun moveToPrevChapter( upContent: Boolean, - toLast: Boolean = true + toLast: Boolean = true, + upContentInPlace: Boolean = true ): Boolean { if (durChapterIndex > 0) { durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0 @@ -242,10 +243,10 @@ object ReadBook : CoroutineScope by MainScope() { curTextChapter = prevTextChapter prevTextChapter = null if (curTextChapter == null) { - callBack?.upContent() + if (upContentInPlace) callBack?.upContent(resetPageOffset = false) loadContent(durChapterIndex, upContent, resetPageOffset = false) - } else if (upContent) { - callBack?.upContent() + } else if (upContent && upContentInPlace) { + callBack?.upContent(resetPageOffset = false) } loadContent(durChapterIndex.minus(1), upContent, false) saveRead() @@ -267,6 +268,16 @@ object ReadBook : CoroutineScope by MainScope() { } fun setPageIndex(index: Int) { + val textChapter = curTextChapter + if (textChapter != null) { + val pageIndex = durPageIndex + if (index > pageIndex) { + textChapter.getPage(index - 2)?.recyclePictures() + } + if (index < pageIndex) { + textChapter.getPage(index + 2)?.recyclePictures() + } + } durChapterPos = curTextChapter?.getReadLength(index) ?: index saveRead(true) curPageChanged(true) @@ -595,6 +606,7 @@ object ReadBook : CoroutineScope by MainScope() { coroutineContext.cancelChildren() downloadedChapters.clear() downloadFailChapters.clear() + ImageProvider.clear() } interface CallBack { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 546846367..68b9e481f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -97,6 +97,7 @@ import io.legado.app.utils.LogUtils import io.legado.app.utils.StartActivityContract import io.legado.app.utils.SyncedRenderer import io.legado.app.utils.applyOpenTint +import io.legado.app.utils.buildMainHandler import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefString import io.legado.app.utils.hexString @@ -218,9 +219,10 @@ class ReadBookActivity : BaseReadBookActivity(), private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } } private var bookChanged = false private var pageChanged = false - private var reloadContent = false private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } } private var autoPageScrollOffset = 0.0 + private val handler by lazy { buildMainHandler() } + private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } //恢复跳转前进度对话框的交互结果 private var confirmRestoreProcess: Boolean? = null @@ -265,22 +267,14 @@ class ReadBookActivity : BaseReadBookActivity(), override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) viewModel.initData(intent) { - initDataSuccess() + upMenu() } } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) viewModel.initData(intent ?: return) { - initDataSuccess() - } - } - - private fun initDataSuccess() { - upMenu() - if (reloadContent) { - reloadContent = false - ReadBook.loadContent(resetPageOffset = false) + upMenu() } } @@ -1532,8 +1526,6 @@ class ReadBookActivity : BaseReadBookActivity(), if (it) { // 更新内容排版布局 if (isInitFinish) { ReadBook.loadContent(resetPageOffset = false) - } else { - reloadContent = true } } else { readView.upContent(resetPageOffset = false) @@ -1582,6 +1574,9 @@ class ReadBookActivity : BaseReadBookActivity(), observeEvent(EventBus.UP_SEEK_BAR) { binding.readMenu.upSeekBar() } + observeEvent(EventBus.RECREATE) { + binding.readView.invalidateTextPage() + } } private fun upScreenTimeOut() { @@ -1594,17 +1589,16 @@ class ReadBookActivity : BaseReadBookActivity(), * 重置黑屏时间 */ override fun screenOffTimerStart() { - keepScreenJon?.cancel() - keepScreenJon = lifecycleScope.launch { + handler.post { if (screenTimeOut < 0) { keepScreenOn(true) - return@launch + return@post } val t = screenTimeOut - sysScreenOffTime if (t > 0) { keepScreenOn(true) - delay(screenTimeOut) - keepScreenOn(false) + handler.removeCallbacks(screenOffRunnable) + handler.postDelayed(screenOffRunnable, screenTimeOut) } else { keepScreenOn(false) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 11fb9d542..cc9ce080d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -3,22 +3,16 @@ package io.legado.app.ui.book.read.page import android.content.Context import android.graphics.Canvas import android.graphics.Paint -import android.graphics.Picture import android.graphics.RectF -import android.os.Build import android.util.AttributeSet import android.view.MotionEvent import android.view.View -import androidx.core.graphics.record +import androidx.core.graphics.withTranslation import io.legado.app.R import io.legado.app.constant.PageAnim import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Bookmark -import io.legado.app.help.book.isImage import io.legado.app.help.config.AppConfig -import io.legado.app.help.config.ReadBookConfig -import io.legado.app.lib.theme.accentColor -import io.legado.app.model.ImageProvider import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextPage @@ -31,8 +25,8 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.widget.dialog.PhotoDialog +import io.legado.app.utils.PictureMirror import io.legado.app.utils.activity -import io.legado.app.utils.dpToPx import io.legado.app.utils.getCompatColor import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.showDialogFragment @@ -44,8 +38,7 @@ import kotlin.math.min */ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true) - var upView: ((TextPage) -> Unit)? = null - private val selectedPaint by lazy { + val selectedPaint by lazy { Paint().apply { color = context.getCompatColor(R.color.btn_bg_press_2) style = Paint.Style.FILL @@ -65,13 +58,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at //滚动参数 private val pageFactory: TextPageFactory get() = callBack.pageFactory private var pageOffset = 0 - private lateinit var picture: Picture - private var pictureIsDirty = true - private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + private val pictureMirror = PictureMirror() private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim //绘制图片的paint - private val imagePaint by lazy { + val imagePaint by lazy { Paint().apply { isAntiAlias = AppConfig.useAntiAlias } @@ -79,9 +70,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at init { callBack = activity as CallBack - if (atLeastApi23) { - picture = Picture() - } } /** @@ -108,7 +96,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) if (!isMainView) return - ChapterProvider.upViewSize(w, h) + ChapterProvider.upViewSize(w, h, oldw, oldh) upVisibleRect() textPage.format() } @@ -119,14 +107,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at canvas.translate(0f, scrollY.toFloat()) } canvas.clipRect(visibleRect) - if (atLeastApi23 && !callBack.isScroll && !isNoAnim) { - if (pictureIsDirty) { - pictureIsDirty = false - picture.record(width, height) { - drawPage(this) - } + if (!callBack.isScroll && !isNoAnim) { + pictureMirror.draw(canvas, width, height) { + drawPage(this) } - canvas.drawPicture(picture) } else { drawPage(canvas) } @@ -137,147 +121,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at */ private fun drawPage(canvas: Canvas) { var relativeOffset = relativeOffset(0) - var lines = textPage.lines - for (i in lines.indices) { - drawLine(canvas, textPage, lines[i], relativeOffset) + val view = this + canvas.withTranslation(0f, relativeOffset) { + textPage.draw(view, this) } if (!callBack.isScroll) return //滚动翻页 if (!pageFactory.hasNext()) return val textPage1 = relativePage(1) relativeOffset = relativeOffset(1) - lines = textPage1.lines - for (i in lines.indices) { - drawLine(canvas, textPage1, lines[i], relativeOffset) + canvas.withTranslation(0f, relativeOffset) { + textPage1.draw(view, this) } if (!pageFactory.hasNextPlus()) return relativeOffset = relativeOffset(2) if (relativeOffset < ChapterProvider.visibleHeight) { val textPage2 = relativePage(2) - lines = textPage2.lines - for (i in lines.indices) { - drawLine(canvas, textPage2, lines[i], relativeOffset) + canvas.withTranslation(0f, relativeOffset) { + textPage2.draw(view, this) } } } - /** - * 绘制页面 - */ - private fun drawLine( - canvas: Canvas, - textPage: TextPage, - textLine: TextLine, - relativeOffset: Float, - ) { - val lineTop = textLine.lineTop + relativeOffset - val lineBase = textLine.lineBase + relativeOffset - val lineBottom = textLine.lineBottom + relativeOffset - drawChars(canvas, textPage, textLine, lineTop, lineBase, lineBottom) - if (ReadBookConfig.underline && ReadBook.book?.isImage != true) { - drawUnderline(canvas, textLine, relativeOffset) - } - } - - /** - * 绘制下划线 - */ - private fun drawUnderline(canvas: Canvas, textLine: TextLine, relativeOffset: Float) { - val lineY = relativeOffset + textLine.lineBottom - 1.dpToPx() - canvas.drawLine( - textLine.lineStart + textLine.indentWidth, - lineY, - textLine.lineEnd, - lineY, - ChapterProvider.contentPaint - ) - } - - /** - * 绘制文字 - */ - private fun drawChars( - canvas: Canvas, - textPage: TextPage, - textLine: TextLine, - lineTop: Float, - lineBase: Float, - lineBottom: Float, - ) { - val textPaint = if (textLine.isTitle) { - ChapterProvider.titlePaint - } else { - ChapterProvider.contentPaint - } - val textColor = if (textLine.isReadAloud) context.accentColor else ReadBookConfig.textColor - val columns = textLine.columns - for (i in columns.indices) { - when (val column = columns[i]) { - is TextColumn -> { - if (column.isSearchResult) { - textPaint.color = context.accentColor - } else if (textPaint.color != textColor) { - textPaint.color = textColor - } - canvas.drawText(column.charData, column.start, lineBase, textPaint) - if (column.selected) { - canvas.drawRect( - column.start, - lineTop, - column.end, - lineBottom, - selectedPaint - ) - } - } - - is ImageColumn -> drawImage(canvas, textPage, textLine, column, lineTop, lineBottom) - is ReviewColumn -> column.drawToCanvas(canvas, lineBase, textPaint.textSize) - } - } - } - - /** - * 绘制图片 - */ - @Suppress("UNUSED_PARAMETER") - private fun drawImage( - canvas: Canvas, - textPage: TextPage, - textLine: TextLine, - column: ImageColumn, - lineTop: Float, - lineBottom: Float - ) { - - val book = ReadBook.book ?: return - - val bitmap = ImageProvider.getImage( - book, - column.src, - (column.end - column.start).toInt(), - (lineBottom - lineTop).toInt() - ) { - invalidate() - } ?: return - - val rectF = if (textLine.isImage) { - RectF(column.start, lineTop, column.end, lineBottom) - } else { - /*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/ - val h = (column.end - column.start) / bitmap.width * bitmap.height - val div = (lineBottom - lineTop - h) / 2 - RectF(column.start, lineTop + div, column.end, lineBottom - div) - } - kotlin.runCatching { - canvas.drawBitmap(bitmap, null, rectF, imagePaint) - }.onFailure { e -> - context.toastOnUi(e.localizedMessage) - } - } - /** * 滚动事件 + * pageOffset 向上滚动 减小 向下滚动 增大 + * pageOffset 范围 0 ~ -textPage.height 大于0为上一页,小于-textPage.height为下一页 + * 以内容显示区域顶端为界,pageOffset的绝对值为textPage上方的高度 + * pageOffset + textPage.height 为 textPage 下方的高度 */ fun scroll(mOffset: Int) { if (mOffset == 0) return @@ -294,28 +165,25 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at val offset = (ChapterProvider.visibleHeight - textPage.height).toInt() pageOffset = min(0, offset) } else if (pageOffset > 0) { - pageFactory.moveToPrev(true) - textPage = pageFactory.curPage - pageOffset -= textPage.height.toInt() - upView?.invoke(textPage) - contentDescription = textPage.text + if (pageFactory.moveToPrev(true)) { + pageOffset -= textPage.height.toInt() + } else { + pageOffset = 0 + } } else if (pageOffset < -textPage.height) { - pageOffset += textPage.height.toInt() - pageFactory.moveToNext(true) - textPage = pageFactory.curPage - upView?.invoke(textPage) - contentDescription = textPage.text + val height = textPage.height + if (pageFactory.moveToNext(upContent = true)) { + pageOffset += height.toInt() + } else { + pageOffset = -height.toInt() + } } invalidate() } override fun invalidate() { super.invalidate() - invalidatePicture() - } - - private fun invalidatePicture() { - pictureIsDirty = true + pictureMirror.invalidate() } /** @@ -517,9 +385,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at if (relativeOffset >= ChapterProvider.visibleHeight) return } val textPage = relativePage(relativePos) - for ((lineIndex, textLine) in textPage.lines.withIndex()) { + for (lineIndex in textPage.lines.indices) { + val textLine = textPage.getLine(lineIndex) if (textLine.isTouchY(y, relativeOffset)) { - for ((charIndex, textColumn) in textLine.columns.withIndex()) { + if (textPage.doublePage) { + val halfWidth = width / 2 + if (textLine.isLeftLine && x > halfWidth) { + continue + } + if (!textLine.isLeftLine && x < halfWidth) { + continue + } + } + val columns = textLine.columns + for (charIndex in columns.indices) { + val textColumn = columns[charIndex] if (textColumn.isTouch(x)) { touched.invoke( relativeOffset, @@ -529,12 +409,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at return } } - val isLast = textLine.columns.first().start < x - val (charIndex, textColumn) = if (isLast) { - textLine.columns.withIndex().last() - } else { - textLine.columns.withIndex().first() - } + val isLast = columns.first().start < x + val charIndex = if (isLast) columns.lastIndex else 0 + val textColumn = if (isLast) columns.last() else columns.first() touched.invoke( relativeOffset, TextPos(relativePos, lineIndex, charIndex, false, isLast), @@ -557,7 +434,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at if (relativeOffset >= ChapterProvider.visibleHeight) break } val textPage = relativePage(relativePos) - for (textLine in textPage.lines) { + val lines = textPage.lines + for (i in lines.indices) { + val textLine = lines[i] if (textLine.isVisible(relativeOffset)) { val visibleLine = textLine.copy().apply { lineTop += relativeOffset @@ -580,7 +459,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at if (relativeOffset >= ChapterProvider.visibleHeight) break } val textPage = relativePage(relativePos) - for (textLine in textPage.lines) { + val lines = textPage.lines + for (i in lines.indices) { + val textLine = lines[i] if (textLine.isVisible(relativeOffset)) { val visibleLine = textLine.copy().apply { lineTop += relativeOffset diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 31656dc56..3af462e62 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -57,9 +57,6 @@ class PageView(context: Context) : FrameLayout(context) { if (!isInEditMode) { upStyle() } - binding.contentTextView.upView = { - setProgress(it) - } } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 70833c053..1548cdade 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -21,16 +21,25 @@ import io.legado.app.model.ReadAloud import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.ContentEditDialog import io.legado.app.ui.book.read.page.api.DataSource -import io.legado.app.ui.book.read.page.delegate.* +import io.legado.app.ui.book.read.page.delegate.CoverPageDelegate +import io.legado.app.ui.book.read.page.delegate.NoAnimPageDelegate +import io.legado.app.ui.book.read.page.delegate.PageDelegate +import io.legado.app.ui.book.read.page.delegate.ScrollPageDelegate +import io.legado.app.ui.book.read.page.delegate.SimulationPageDelegate +import io.legado.app.ui.book.read.page.delegate.SlidePageDelegate import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPos import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.TextPageFactory -import io.legado.app.utils.* +import io.legado.app.utils.activity +import io.legado.app.utils.invisible +import io.legado.app.utils.screenshot +import io.legado.app.utils.showDialogFragment +import io.legado.app.utils.visible import java.text.BreakIterator -import java.util.* +import java.util.Locale import kotlin.math.abs /** @@ -49,7 +58,7 @@ class ReadView(context: Context, attrs: AttributeSet) : field = value upContent() } - var isScroll = false + override var isScroll = false val prevPage by lazy { PageView(context) } val curPage by lazy { PageView(context) } val nextPage by lazy { PageView(context) } @@ -529,7 +538,9 @@ class ReadView(context: Context, attrs: AttributeSet) : * @param resetPageOffset 滚动阅读是是否重置位置 */ override fun upContent(relativePosition: Int, resetPageOffset: Boolean) { - curPage.setContentDescription(pageFactory.curPage.text) + post { + curPage.setContentDescription(pageFactory.curPage.text) + } if (isScroll && !callBack.isAutoPage) { curPage.setContent(pageFactory.curPage, resetPageOffset) } else { @@ -643,6 +654,15 @@ class ReadView(context: Context, attrs: AttributeSet) : nextPageBitmap = null } + fun invalidateTextPage() { + pageFactory.run { + prevPage.invalidateAll() + curPage.invalidateAll() + nextPage.invalidateAll() + nextPlusPage.invalidateAll() + } + } + override val currentChapter: TextChapter? get() { return if (callBack.isInitFinish) ReadBook.textChapter(0) else null diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt index 22c0d7ab0..eb28942d6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt @@ -13,6 +13,8 @@ interface DataSource { val prevChapter: TextChapter? + val isScroll: Boolean + fun hasNextChapter(): Boolean fun hasPrevChapter(): Boolean diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt index 09faed73a..07cff13df 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt @@ -186,17 +186,19 @@ data class TextChapter( */ fun getPageIndexByCharIndex(charIndex: Int): Int { var length = 0 - pages.forEach { - length += it.charSize + for (i in pages.indices) { + val page = pages[i] + length += page.charSize if (length > charIndex) { - return it.index + return page.index } } return pages.lastIndex } fun clearSearchResult() { - pages.forEach { page -> + for (i in pages.indices) { + val page = pages[i] page.searchResult.forEach { it.selected = false it.isSearchResult = false diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 1a14ef62b..0f89a6e32 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -1,9 +1,17 @@ package io.legado.app.ui.book.read.page.entities +import android.graphics.Canvas import android.graphics.Paint.FontMetrics import androidx.annotation.Keep +import io.legado.app.help.book.isImage +import io.legado.app.help.config.ReadBookConfig +import io.legado.app.model.ReadBook +import io.legado.app.ui.book.read.page.ContentTextView +import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage import io.legado.app.ui.book.read.page.entities.column.BaseColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.utils.PictureMirror +import io.legado.app.utils.dpToPx /** * 行信息 @@ -22,8 +30,7 @@ data class TextLine( var pagePosition: Int = 0, val isTitle: Boolean = false, var isParagraphEnd: Boolean = false, - var isReadAloud: Boolean = false, - var isImage: Boolean = false + var isImage: Boolean = false, ) { val columns: List get() = textColumns @@ -31,8 +38,20 @@ data class TextLine( val lineStart: Float get() = textColumns.firstOrNull()?.start ?: 0f val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize + val height: Float inline get() = lineBottom - lineTop + val pictureMirror: PictureMirror = PictureMirror() + var isReadAloud: Boolean = false + set(value) { + if (field != value) { + invalidate() + } + field = value + } + var textPage: TextPage = emptyTextPage + var isLeftLine = true fun addColumn(column: BaseColumn) { + column.textLine = this textColumns.add(column) } @@ -102,4 +121,50 @@ data class TextLine( return visible } + fun draw(view: ContentTextView, canvas: Canvas) { + pictureMirror.draw(canvas, view.width, height.toInt()) { + drawTextLine(view, this) + } + } + + private fun drawTextLine(view: ContentTextView, canvas: Canvas) { + for (i in columns.indices) { + columns[i].draw(view, canvas) + } + if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) { + drawUnderline(canvas) + } + } + + /** + * 绘制下划线 + */ + private fun drawUnderline(canvas: Canvas) { + val lineY = height - 1.dpToPx() + canvas.drawLine( + lineStart + indentWidth, + lineY, + lineEnd, + lineY, + ChapterProvider.contentPaint + ) + } + + fun invalidate() { + invalidateSelf() + textPage.invalidate() + } + + fun invalidateSelf() { + pictureMirror.invalidate() + } + + fun recyclePicture() { + pictureMirror.recycle() + } + + companion object { + val emptyTextLine = TextLine() + } + } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index b48710b39..1a9d366f7 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -1,13 +1,17 @@ package io.legado.app.ui.book.read.page.entities +import android.graphics.Canvas import android.text.Layout import android.text.StaticLayout import androidx.annotation.Keep +import androidx.core.graphics.withTranslation import io.legado.app.R import io.legado.app.help.config.ReadBookConfig import io.legado.app.model.ReadBook +import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.utils.PictureMirror import splitties.init.appCtx import java.text.DecimalFormat import kotlin.math.min @@ -31,6 +35,7 @@ data class TextPage( companion object { val readProgressFormatter = DecimalFormat("0.0%") + val emptyTextPage = TextPage() } val lines: List get() = textLines @@ -38,25 +43,29 @@ data class TextPage( val charSize: Int get() = text.length.coerceAtLeast(1) val searchResult = hashSetOf() var isMsgPage: Boolean = false + var pictureMirror: PictureMirror = PictureMirror() + var doublePage = false val paragraphs by lazy { paragraphsInternal } - val paragraphsInternal: ArrayList get() { - val paragraphs = arrayListOf() - val lines = textLines.filter { it.paragraphNum > 0 } - val offset = lines.first().paragraphNum - 1 - lines.forEach { line -> - if (paragraphs.lastIndex < line.paragraphNum - offset - 1) { - paragraphs.add(TextParagraph(0)) + val paragraphsInternal: ArrayList + get() { + val paragraphs = arrayListOf() + val lines = textLines.filter { it.paragraphNum > 0 } + val offset = lines.first().paragraphNum - 1 + lines.forEach { line -> + if (paragraphs.lastIndex < line.paragraphNum - offset - 1) { + paragraphs.add(TextParagraph(0)) + } + paragraphs[line.paragraphNum - offset - 1].textLines.add(line) } - paragraphs[line.paragraphNum - offset - 1].textLines.add(line) + return paragraphs } - return paragraphs - } fun addLine(line: TextLine) { + line.textPage = this textLines.add(line) } @@ -147,7 +156,7 @@ data class TextPage( ) x = x1 } - textLines.add(textLine) + addLine(textLine) } height = ChapterProvider.visibleHeight.toFloat() } @@ -158,8 +167,8 @@ data class TextPage( * 移除朗读标志 */ fun removePageAloudSpan(): TextPage { - textLines.forEach { textLine -> - textLine.isReadAloud = false + for (i in textLines.indices) { + textLines[i].isReadAloud = false } return this } @@ -254,6 +263,39 @@ data class TextPage( return null } + fun draw(view: ContentTextView, canvas: Canvas) { + pictureMirror.draw(canvas, view.width, height.toInt()) { + drawPage(view, this) + } + } + + private fun drawPage(view: ContentTextView, canvas: Canvas) { + for (i in lines.indices) { + val line = lines[i] + canvas.withTranslation(0f, line.lineTop) { + line.draw(view, this) + } + } + } + + fun invalidate() { + pictureMirror.invalidate() + } + + fun invalidateAll() { + for (i in lines.indices) { + lines[i].invalidateSelf() + } + invalidate() + } + + fun recyclePictures() { + pictureMirror.recycle() + for (i in lines.indices) { + lines[i].recyclePicture() + } + } + fun hasImageOrEmpty(): Boolean { return textLines.any { it.isImage } || textLines.isEmpty() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/BaseColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/BaseColumn.kt index 6516e5f0d..ed86cd153 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/BaseColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/BaseColumn.kt @@ -1,11 +1,18 @@ package io.legado.app.ui.book.read.page.entities.column +import android.graphics.Canvas +import io.legado.app.ui.book.read.page.ContentTextView +import io.legado.app.ui.book.read.page.entities.TextLine + /** * 列基类 */ interface BaseColumn { var start: Float var end: Float + var textLine: TextLine + + fun draw(view: ContentTextView, canvas: Canvas) fun isTouch(x: Float): Boolean { return x > start && x < end diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ButtonColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ButtonColumn.kt index 63db76675..abb3ac42a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ButtonColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ButtonColumn.kt @@ -1,6 +1,10 @@ package io.legado.app.ui.book.read.page.entities.column +import android.graphics.Canvas import androidx.annotation.Keep +import io.legado.app.ui.book.read.page.ContentTextView +import io.legado.app.ui.book.read.page.entities.TextLine +import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine /** @@ -9,5 +13,10 @@ import androidx.annotation.Keep @Keep data class ButtonColumn( override var start: Float, - override var end: Float -) : BaseColumn \ No newline at end of file + override var end: Float, +) : BaseColumn { + override var textLine: TextLine = emptyTextLine + override fun draw(view: ContentTextView, canvas: Canvas) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt index d372eda21..39275c8c0 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt @@ -1,6 +1,15 @@ package io.legado.app.ui.book.read.page.entities.column +import android.graphics.Canvas +import android.graphics.RectF import androidx.annotation.Keep +import io.legado.app.model.ImageProvider +import io.legado.app.model.ReadBook +import io.legado.app.ui.book.read.page.ContentTextView +import io.legado.app.ui.book.read.page.entities.TextLine +import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine +import io.legado.app.utils.toastOnUi +import splitties.init.appCtx /** * 图片列 @@ -10,4 +19,37 @@ data class ImageColumn( override var start: Float, override var end: Float, var src: String -) : BaseColumn \ No newline at end of file +) : BaseColumn { + + override var textLine: TextLine = emptyTextLine + override fun draw(view: ContentTextView, canvas: Canvas) { + val book = ReadBook.book ?: return + + val height = textLine.height + + val bitmap = ImageProvider.getImage( + book, + src, + (end - start).toInt(), + height.toInt() + ) { + textLine.invalidate() + view.invalidate() + } ?: return + + val rectF = if (textLine.isImage) { + RectF(start, 0f, end, height) + } else { + /*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/ + val h = (end - start) / bitmap.width * bitmap.height + val div = (height - h) / 2 + RectF(start, div, end, height - div) + } + kotlin.runCatching { + canvas.drawBitmap(bitmap, null, rectF, view.imagePaint) + }.onFailure { e -> + appCtx.toastOnUi(e.localizedMessage) + } + } + +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ReviewColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ReviewColumn.kt index 748f1b57c..c1612fb2f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ReviewColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ReviewColumn.kt @@ -4,6 +4,9 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.Path import androidx.annotation.Keep +import io.legado.app.ui.book.read.page.ContentTextView +import io.legado.app.ui.book.read.page.entities.TextLine +import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine import io.legado.app.ui.book.read.page.provider.ChapterProvider /** @@ -16,6 +19,16 @@ data class ReviewColumn( val count: Int = 0 ) : BaseColumn { + override var textLine: TextLine = emptyTextLine + override fun draw(view: ContentTextView, canvas: Canvas) { + val textPaint = if (textLine.isTitle) { + ChapterProvider.titlePaint + } else { + ChapterProvider.contentPaint + } + drawToCanvas(canvas, textLine.lineBase, textPaint.textSize) + } + val countText by lazy { if (count > 999) { return@lazy "999" diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt index 0ffe9d39a..7096c55d1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt @@ -1,6 +1,13 @@ package io.legado.app.ui.book.read.page.entities.column +import android.graphics.Canvas import androidx.annotation.Keep +import io.legado.app.help.config.ReadBookConfig +import io.legado.app.lib.theme.ThemeStore +import io.legado.app.ui.book.read.page.ContentTextView +import io.legado.app.ui.book.read.page.entities.TextLine +import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine +import io.legado.app.ui.book.read.page.provider.ChapterProvider /** * 文字列 @@ -10,6 +17,43 @@ data class TextColumn( override var start: Float, override var end: Float, val charData: String, - var selected: Boolean = false, +) : BaseColumn { + + override var textLine: TextLine = emptyTextLine + + var selected: Boolean = false + set(value) { + if (field != value) { + textLine.invalidate() + } + field = value + } var isSearchResult: Boolean = false -) : BaseColumn \ No newline at end of file + set(value) { + if (field != value) { + textLine.invalidate() + } + field = value + } + + override fun draw(view: ContentTextView, canvas: Canvas) { + val textPaint = if (textLine.isTitle) { + ChapterProvider.titlePaint + } else { + ChapterProvider.contentPaint + } + val textColor = if (textLine.isReadAloud || isSearchResult) { + ThemeStore.accentColor + } else { + ReadBookConfig.textColor + } + if (textPaint.color != textColor) { + textPaint.color = textColor + } + canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) + if (selected) { + canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint) + } + } + +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 760efbc35..8c5ccc06e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -1,6 +1,5 @@ package io.legado.app.ui.book.read.page.provider -import android.graphics.Paint import android.graphics.Paint.FontMetrics import android.graphics.Typeface import android.net.Uri @@ -25,13 +24,13 @@ import io.legado.app.ui.book.read.page.entities.column.ReviewColumn import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.utils.RealPathUtil import io.legado.app.utils.dpToPx +import io.legado.app.utils.fastSum import io.legado.app.utils.isContentScheme import io.legado.app.utils.isPad import io.legado.app.utils.postEvent import io.legado.app.utils.spToPx import io.legado.app.utils.splitNotBlank import io.legado.app.utils.textHeight -import io.legado.app.utils.toStringArray import splitties.init.appCtx import java.util.LinkedList import java.util.Locale @@ -47,6 +46,8 @@ object ChapterProvider { //用于评论按钮的替换 private const val reviewChar = "▨" + private const val indentChar = " " + @JvmStatic var viewWidth = 0 private set @@ -104,7 +105,7 @@ object ChapterProvider { private var indentCharWidth = 0f @JvmStatic - private var titlePaintTextHeight = 0f + var titlePaintTextHeight = 0f @JvmStatic var contentPaintTextHeight = 0f @@ -132,6 +133,9 @@ object ChapterProvider { var doublePage = false private set + private val titleMeasureHelper = TextMeasure(titlePaint) + private val contentMeasureHelper = TextMeasure(contentPaint) + init { upStyle() } @@ -161,6 +165,7 @@ object ChapterProvider { textPages, stringBuilder, titlePaint, + titleMeasureHelper, titlePaintTextHeight, titlePaintFontMetrics, isTitle = true, @@ -196,6 +201,7 @@ object ChapterProvider { textPages, stringBuilder, contentPaint, + contentMeasureHelper, contentPaintTextHeight, contentPaintFontMetrics, srcList = srcList @@ -217,6 +223,7 @@ object ChapterProvider { textPages, stringBuilder, contentPaint, + contentMeasureHelper, contentPaintTextHeight, contentPaintFontMetrics ).let { @@ -224,10 +231,19 @@ object ChapterProvider { durY = it.second } } - durY = setTypeImage( - book, matcher.group(1)!!, - absStartX, durY, textPages, stringBuilder, book.getImageStyle() - ) + setTypeImage( + book, + matcher.group(1)!!, + absStartX, + durY, + textPages, + contentPaintTextHeight, + stringBuilder, + book.getImageStyle() + ).let { + absStartX = it.first + durY = it.second + } start = matcher.end() } if (start < content.length) { @@ -239,6 +255,7 @@ object ChapterProvider { textPages, stringBuilder, contentPaint, + contentMeasureHelper, contentPaintTextHeight, contentPaintFontMetrics ).let { @@ -249,14 +266,22 @@ object ChapterProvider { } } } - textPages.last().height = durY + 20.dpToPx() - textPages.last().text = stringBuilder.toString() + val textPage = textPages.last() + val endPadding = 20.dpToPx() + val durYPadding = durY + endPadding + if (textPage.height < durYPadding) { + textPage.height = durYPadding + } else { + textPage.height += endPadding + } + textPage.text = stringBuilder.toString() textPages.forEachIndexed { index, item -> item.index = index item.pageSize = textPages.size item.chapterIndex = bookChapter.index item.chapterSize = chapterSize item.title = displayTitle + item.doublePage = doublePage item.upLinesPosition() } @@ -280,15 +305,19 @@ object ChapterProvider { x: Int, y: Float, textPages: ArrayList, + textHeight: Float, stringBuilder: StringBuilder, imageStyle: String?, - ): Float { + ): Pair { + var absStartX = x var durY = y val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource) if (size.width > 0 && size.height > 0) { if (durY > visibleHeight) { val textPage = textPages.last() - textPage.height = durY + if (textPage.height < durY) { + textPage.height = durY + } textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" } stringBuilder.clear() textPages.add(TextPage()) @@ -313,10 +342,23 @@ object ChapterProvider { } if (durY + height > visibleHeight) { val textPage = textPages.last() - textPage.height = durY - textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" } - stringBuilder.clear() - textPages.add(TextPage()) + if (doublePage && absStartX < viewWidth / 2) { + //当前页面左列结束 + textPage.leftLineSize = textPage.lineSize + absStartX = viewWidth / 2 + paddingLeft + } else { + //当前页面结束 + if (textPage.leftLineSize == 0) { + textPage.leftLineSize = textPage.lineSize + } + textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" } + stringBuilder.clear() + textPages.add(TextPage()) + } + // 双页的 durY 不正确,可能会小于实际高度 + if (textPage.height < durY) { + textPage.height = durY + } durY = 0f } } @@ -336,7 +378,7 @@ object ChapterProvider { ) textPages.last().addLine(textLine) } - return durY + paragraphSpacing / 10f + return absStartX to durY + textHeight * paragraphSpacing / 10f } /** @@ -350,6 +392,7 @@ object ChapterProvider { textPages: ArrayList, stringBuilder: StringBuilder, textPaint: TextPaint, + measureHelper: TextMeasure, textHeight: Float, fontMetrics: FontMetrics, isTitle: Boolean = false, @@ -358,14 +401,11 @@ object ChapterProvider { srcList: LinkedList? = null ): Pair { var absStartX = x - val widthsArray = FloatArray(text.length) val layout = if (ReadBookConfig.useZhLayout) { - ZhLayout(text, textPaint, visibleWidth, widthsArray) + ZhLayout(text, textPaint, visibleWidth, measureHelper) } else { - textPaint.getTextWidths(text, widthsArray) StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true) } - val widthsList = widthsArray.asList() var durY = when { //标题y轴居中 emptyContent && textPages.size == 1 -> { @@ -398,6 +438,7 @@ object ChapterProvider { if (durY + textHeight > visibleHeight) { val textPage = textPages.last() if (doublePage && absStartX < viewWidth / 2) { + //当前页面左列结束 textPage.leftLineSize = textPage.lineSize absStartX = viewWidth / 2 + paddingLeft } else { @@ -406,32 +447,34 @@ object ChapterProvider { textPage.leftLineSize = textPage.lineSize } textPage.text = stringBuilder.toString() - textPage.height = durY //新建页面 textPages.add(TextPage()) stringBuilder.clear() absStartX = paddingLeft } + if (textPage.height < durY) { + textPage.height = durY + } durY = 0f } val lineStart = layout.getLineStart(lineIndex) val lineEnd = layout.getLineEnd(lineIndex) - val words = text.substring(lineStart, lineEnd) - val textWidths = widthsList.subList(lineStart, lineEnd) - val desiredWidth = textWidths.sum() + val lineText = text.substring(lineStart, lineEnd) + val (words, widths) = measureHelper.measureTextSplit(lineText) + val desiredWidth = widths.fastSum() when { lineIndex == 0 && layout.lineCount > 1 && !isTitle -> { //第一行 非标题 - textLine.text = words + textLine.text = lineText addCharsToLineFirst( book, absStartX, textLine, words, - textPaint, desiredWidth, textWidths, srcList + desiredWidth, widths, srcList ) } lineIndex == layout.lineCount - 1 -> { //最后一行 - textLine.text = words + textLine.text = lineText textLine.isParagraphEnd = true //标题x轴居中 val startX = if ( @@ -443,8 +486,8 @@ object ChapterProvider { 0f } addCharsToLineNatural( - book, absStartX, textLine, words, textPaint, - startX, !isTitle && lineIndex == 0, textWidths, srcList + book, absStartX, textLine, words, + startX, !isTitle && lineIndex == 0, widths, srcList ) } @@ -457,20 +500,23 @@ object ChapterProvider { val startX = (visibleWidth - desiredWidth) / 2 addCharsToLineNatural( book, absStartX, textLine, words, - textPaint, startX, false, textWidths, srcList + startX, false, widths, srcList ) } else { //中间行 - textLine.text = words + textLine.text = lineText addCharsToLineMiddle( book, absStartX, textLine, words, - textPaint, desiredWidth, 0f, textWidths, srcList + desiredWidth, 0f, widths, srcList ) } } } + if (doublePage) { + textLine.isLeftLine = absStartX < viewWidth / 2 + } val sbLength = stringBuilder.length - stringBuilder.append(words) + stringBuilder.append(lineText) if (textLine.isParagraphEnd) { stringBuilder.append("\n") } @@ -487,10 +533,13 @@ object ChapterProvider { chapterPosition + charSize + if (isParagraphEnd) 1 else 0 } ?: 0) + sbLength textLine.pagePosition = sbLength - textPages.last().addLine(textLine) textLine.upTopBottom(durY, textHeight, fontMetrics) + val textPage = textPages.last() + textPage.addLine(textLine) durY += textHeight * lineSpacingExtra - textPages.last().height = durY + if (textPage.height < durY) { + textPage.height = durY + } } durY += textHeight * paragraphSpacing / 10f return Pair(absStartX, durY) @@ -503,8 +552,7 @@ object ChapterProvider { book: Book, absStartX: Int, textLine: TextLine, - text: String, - textPaint: TextPaint, + words: List, /**自然排版长度**/ desiredWidth: Float, textWidths: List, @@ -513,17 +561,17 @@ object ChapterProvider { var x = 0f if (!ReadBookConfig.textFullJustify) { addCharsToLineNatural( - book, absStartX, textLine, text, textPaint, + book, absStartX, textLine, words, x, true, textWidths, srcList ) return } val bodyIndent = ReadBookConfig.paragraphIndent - for (char in bodyIndent.toStringArray()) { + for (i in bodyIndent.indices) { val x1 = x + indentCharWidth textLine.addColumn( TextColumn( - charData = char, + charData = indentChar, start = absStartX + x, end = absStartX + x1 ) @@ -531,12 +579,12 @@ object ChapterProvider { x = x1 textLine.indentWidth = x } - if (text.length > bodyIndent.length) { - val text1 = text.substring(bodyIndent.length, text.length) + if (words.size > bodyIndent.length) { + val text1 = words.subList(bodyIndent.length, words.size) val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size) addCharsToLineMiddle( book, absStartX, textLine, text1, - textPaint, desiredWidth, x, textWidths1, srcList + desiredWidth, x, textWidths1, srcList ) } } @@ -548,8 +596,7 @@ object ChapterProvider { book: Book, absStartX: Int, textLine: TextLine, - text: String, - textPaint: TextPaint, + words: List, /**自然排版长度**/ desiredWidth: Float, /**起始x坐标**/ @@ -559,19 +606,19 @@ object ChapterProvider { ) { if (!ReadBookConfig.textFullJustify) { addCharsToLineNatural( - book, absStartX, textLine, text, textPaint, + book, absStartX, textLine, words, startX, false, textWidths, srcList ) return } val residualWidth = visibleWidth - desiredWidth - val spaceSize = text.count { it == ' ' } - val (words, widths) = getStringArrayAndTextWidths(text, textWidths, textPaint) + val spaceSize = words.count { it == " " } if (spaceSize > 1) { val d = residualWidth / spaceSize var x = startX - words.forEachIndexed { index, char -> - val cw = widths[index] + for (index in words.indices) { + val char = words[index] + val cw = textWidths[index] val x1 = if (char == " ") { if (index != words.lastIndex) (x + cw + d) else (x + cw) } else { @@ -587,8 +634,9 @@ object ChapterProvider { val gapCount: Int = words.lastIndex val d = residualWidth / gapCount var x = startX - words.forEachIndexed { index, char -> - val cw = widths[index] + for (index in words.indices) { + val char = words[index] + val cw = textWidths[index] val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw) addCharToLine( book, absStartX, textLine, char, @@ -607,8 +655,7 @@ object ChapterProvider { book: Book, absStartX: Int, textLine: TextLine, - text: String, - textPaint: TextPaint, + words: List, startX: Float, hasIndent: Boolean, textWidths: List, @@ -616,9 +663,9 @@ object ChapterProvider { ) { val indentLength = ReadBookConfig.paragraphIndent.length var x = startX - val (words, widths) = getStringArrayAndTextWidths(text, textWidths, textPaint) - words.forEachIndexed { index, char -> - val cw = widths[index] + for (index in words.indices) { + val char = words[index] + val cw = textWidths[index] val x1 = x + cw addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList) x = x1 @@ -731,9 +778,11 @@ object ChapterProvider { getPaints(typeface).let { titlePaint = it.first contentPaint = it.second - reviewPaint.color = contentPaint.color - reviewPaint.textSize = contentPaint.textSize * 0.45f - reviewPaint.textAlign = Paint.Align.CENTER + titleMeasureHelper.setPaint(titlePaint) + contentMeasureHelper.setPaint(contentPaint) +// reviewPaint.color = contentPaint.color +// reviewPaint.textSize = contentPaint.textSize * 0.45f +// reviewPaint.textAlign = Paint.Align.CENTER } //间距 lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f @@ -820,12 +869,14 @@ object ChapterProvider { /** * 更新View尺寸 */ - fun upViewSize(width: Int, height: Int) { + fun upViewSize(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) { viewWidth = width viewHeight = height upLayout() - postEvent(EventBus.UP_CONFIG, true) + if (oldWidth > 0 && oldHeight > 0) { + postEvent(EventBus.UP_CONFIG, true) + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt new file mode 100644 index 000000000..27329769a --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt @@ -0,0 +1,146 @@ +package io.legado.app.ui.book.read.page.provider + +import android.text.TextPaint +import android.util.SparseArray +import androidx.core.util.getOrDefault +import java.util.BitSet +import kotlin.math.ceil + +class TextMeasure(private var paint: TextPaint) { + + private var chineseCommonWidth = paint.measureText("一") + private val chineseCommonWidthBitSet = BitSet() + private val asciiWidths = FloatArray(128) { -1f } + private val codePointWidths = SparseArray() + + private fun measureCodePoint(codePoint: Int): Float { + if (codePoint < 128) { + return asciiWidths[codePoint] + } + if (chineseCommonWidthBitSet[codePoint]) { + return chineseCommonWidth + } + return codePointWidths.getOrDefault(codePoint, -1f) + } + + private fun measureCodePoints(codePoints: List) { + val charArray = String(codePoints.toIntArray(), 0, codePoints.size).toCharArray() + val widths = FloatArray(charArray.size) + paint.getTextWidths(charArray, 0, charArray.size, widths) + val widthsList = ArrayList(charArray.size) + val buf = IntArray(1) + for (i in charArray.indices) { + if (charArray[i].isLowSurrogate()) continue + val width = ceil(widths[i]) + widthsList.add(width) + if (width == 0f && widthsList.size > 0) { + val lastIndex = widthsList.lastIndex + buf[0] = codePoints[lastIndex - 1] + widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1)) + buf[0] = codePoints[lastIndex] + widthsList[lastIndex] = paint.measureText(String(buf, 0, 1)) + } + } + for (i in codePoints.indices) { + val codePoint = codePoints[i] + val width = widthsList[i] + if (codePoint < 128) { + asciiWidths[codePoint] = width + } else if (width == chineseCommonWidth) { + chineseCommonWidthBitSet.set(codePoint) + } else { + codePointWidths[codePoint] = width + } + } + } + + + fun measureTextSplit(text: String): Pair, ArrayList> { + var needMeasureCodePoints: HashSet? = null + val codePoints = text.toCodePoints() + val size = codePoints.size + val widths = ArrayList(size) + val stringList = ArrayList(size) + val buf = IntArray(1) + for (i in codePoints.indices) { + val codePoint = codePoints[i] + val width = measureCodePoint(codePoint) + widths.add(width) + if (width == -1f) { + if (needMeasureCodePoints == null) { + needMeasureCodePoints = hashSetOf() + } + needMeasureCodePoints.add(codePoint) + } + buf[0] = codePoint + stringList.add(String(buf, 0, 1)) + } + if (!needMeasureCodePoints.isNullOrEmpty()) { + measureCodePoints(needMeasureCodePoints.toList()) + for (i in codePoints.indices) { + if (widths[i] == -1f) { + widths[i] = measureCodePoint(codePoints[i]) + } + } + } + return stringList to widths + } + + fun measureText(text: String): Float { + var textWidth = 0f + var needMeasureCodePoints: ArrayList? = null + val codePoints = text.toCodePoints() + for (i in codePoints.indices) { + val codePoint = codePoints[i] + val width = measureCodePoint(codePoint) + if (width == -1f) { + if (needMeasureCodePoints == null) { + needMeasureCodePoints = ArrayList() + } + needMeasureCodePoints.add(codePoint) + continue + } + textWidth += width + } + if (!needMeasureCodePoints.isNullOrEmpty()) { + measureCodePoints(needMeasureCodePoints.toHashSet().toList()) + for (i in needMeasureCodePoints.indices) { + textWidth += measureCodePoint(needMeasureCodePoints[i]) + } + } + return textWidth + } + + private fun String.toCodePoints(): List { + val codePoints = ArrayList(length) + val charArray = toCharArray() + val size = length + var i = 0 + while (i < size) { + val c1 = charArray[i++] + var cp = c1.code + if (c1.isHighSurrogate() && i < size) { + val c2 = charArray[i] + if (c2.isLowSurrogate()) { + i++ + cp = Character.toCodePoint(c1, c2) + } + } + codePoints.add(cp) + } + return codePoints + } + + fun setPaint(paint: TextPaint) { + this.paint = paint + invalidate() + } + + private fun invalidate() { + chineseCommonWidth = paint.measureText("一") + chineseCommonWidthBitSet.clear() + codePointWidths.clear() + asciiWidths.fill(-1f) + } + +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt index 1bceccd6a..9ff5f77ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt @@ -34,9 +34,12 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) { - return if (hasNext() && currentChapter != null) { - if (currentChapter?.isLastIndex(pageIndex) == true) { - ReadBook.moveToNextChapter(upContent) + return if (hasNext()) { + if (currentChapter == null || currentChapter?.isLastIndex(pageIndex) == true) { + if ((currentChapter == null || isScroll) && nextChapter == null) { + return@with false + } + ReadBook.moveToNextChapter(upContent, false) } else { ReadBook.setPageIndex(pageIndex.plus(1)) } @@ -47,10 +50,16 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } override fun moveToPrev(upContent: Boolean): Boolean = with(dataSource) { - return if (hasPrev() && currentChapter != null) { + return if (hasPrev()) { if (pageIndex <= 0) { - ReadBook.moveToPrevChapter(upContent) + if (currentChapter == null && prevChapter == null) { + return@with false + } + ReadBook.moveToPrevChapter(upContent, upContentInPlace = false) } else { + if (currentChapter == null) { + return@with false + } ReadBook.setPageIndex(pageIndex.minus(1)) } if (upContent) upContent(resetPageOffset = false) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt index ab8db392f..688725afa 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt @@ -16,7 +16,7 @@ class ZhLayout( text: CharSequence, textPaint: TextPaint, width: Int, - widthsArray: FloatArray + measureHelper: TextMeasure, ) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) { companion object { private val postPanc = hashSetOf( @@ -50,12 +50,7 @@ class ZhLayout( init { var line = 0 - curPaint.getTextWidths(text as String, widthsArray) - val (words, widths) = ChapterProvider.getStringArrayAndTextWidths( - text, - widthsArray.asList(), - curPaint - ) + val (words, widths) = measureHelper.measureTextSplit(text as String) var lineW = 0f var cwPre = 0f var length = 0 diff --git a/app/src/main/java/io/legado/app/utils/BitmapCache.kt b/app/src/main/java/io/legado/app/utils/BitmapCache.kt index 2f2e08411..37b831b8b 100644 --- a/app/src/main/java/io/legado/app/utils/BitmapCache.kt +++ b/app/src/main/java/io/legado/app/utils/BitmapCache.kt @@ -11,6 +11,33 @@ object BitmapCache { fun add(bitmap: Bitmap) { reusableBitmaps.add(SoftReference(bitmap)) + trimSize() + } + + fun clear() { + if (reusableBitmaps.isEmpty()) { + return + } + val iterator = reusableBitmaps.iterator() + while (iterator.hasNext()) { + val item = iterator.next().get() ?: continue + item.recycle() + iterator.remove() + } + } + + private fun trimSize() { + var byteCount = 0 + val iterator = reusableBitmaps.iterator() + while (iterator.hasNext()) { + val item = iterator.next().get() ?: continue + if (byteCount > 128 * 1024 * 1024) { + item.recycle() + iterator.remove() + } else { + byteCount += item.byteCount + } + } } fun addInBitmapOptions(options: BitmapFactory.Options) { diff --git a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt new file mode 100644 index 000000000..3e6f33306 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt @@ -0,0 +1,9 @@ +package io.legado.app.utils + +fun List.fastSum(): Float { + var sum = 0f + for (i in indices) { + sum += this[i] + } + return sum +} diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt new file mode 100644 index 000000000..9a725500d --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -0,0 +1,40 @@ +package io.legado.app.utils + +import android.graphics.Canvas +import android.graphics.Picture +import android.os.Build +import androidx.core.graphics.record + +class PictureMirror { + + var picture: Picture? = null + var isDirty = true + + inline fun draw(canvas: Canvas, width: Int, height: Int, block: Canvas.() -> Unit) { + if (atLeastApi23) { + if (picture == null) picture = Picture() + val picture = picture!! + if (isDirty) { + isDirty = false + picture.record(width, height, block) + } + canvas.drawPicture(picture) + } else { + canvas.block() + } + } + + fun invalidate() { + isDirty = true + } + + fun recycle() { + picture = null + isDirty = true + } + + companion object { + val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + } + +} From e213de75a19f9b4a28a78f054d1136292c8ffe4c Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Mon, 12 Feb 2024 21:17:23 +0800 Subject: [PATCH 03/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/ui/book/read/page/ContentTextView.kt | 3 ++- .../app/ui/book/read/page/provider/ChapterProvider.kt | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index cc9ce080d..3a2fc507e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -96,7 +96,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) if (!isMainView) return - ChapterProvider.upViewSize(w, h, oldw, oldh) + ChapterProvider.upViewSize(w, h) upVisibleRect() textPage.format() } @@ -106,6 +106,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at if (longScreenshot) { canvas.translate(0f, scrollY.toFloat()) } + check(!visibleRect.isEmpty) { "visibleRect 为空" } canvas.clipRect(visibleRect) if (!callBack.isScroll && !isNoAnim) { pictureMirror.draw(canvas, width, height) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 8c5ccc06e..46ef5ecd1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -869,14 +869,12 @@ object ChapterProvider { /** * 更新View尺寸 */ - fun upViewSize(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { + fun upViewSize(width: Int, height: Int) { if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) { viewWidth = width viewHeight = height upLayout() - if (oldWidth > 0 && oldHeight > 0) { - postEvent(EventBus.UP_CONFIG, true) - } + postEvent(EventBus.UP_CONFIG, true) } } From c873b72513d0c1af90c13cfbbb1ef3ccd2ea48f3 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Mon, 12 Feb 2024 21:42:30 +0800 Subject: [PATCH 04/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/read/ReadBookActivity.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 68b9e481f..dd3295eb3 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -219,6 +219,7 @@ class ReadBookActivity : BaseReadBookActivity(), private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } } private var bookChanged = false private var pageChanged = false + private var reloadContent = false private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } } private var autoPageScrollOffset = 0.0 private val handler by lazy { buildMainHandler() } @@ -267,14 +268,22 @@ class ReadBookActivity : BaseReadBookActivity(), override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) viewModel.initData(intent) { - upMenu() + initDataSuccess() } } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) viewModel.initData(intent ?: return) { - upMenu() + initDataSuccess() + } + } + + private fun initDataSuccess() { + upMenu() + if (reloadContent) { + reloadContent = false + ReadBook.loadContent(resetPageOffset = false) } } @@ -1526,6 +1535,8 @@ class ReadBookActivity : BaseReadBookActivity(), if (it) { // 更新内容排版布局 if (isInitFinish) { ReadBook.loadContent(resetPageOffset = false) + } else { + reloadContent = true } } else { readView.upContent(resetPageOffset = false) From ce1d23a029851a24b0b49ce420e3b17974834273 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Tue, 13 Feb 2024 12:27:57 +0800 Subject: [PATCH 05/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/ReadBookActivity.kt | 4 +-- .../app/ui/book/read/page/ContentTextView.kt | 25 +++++++------------ .../legado/app/ui/book/read/page/PageView.kt | 1 - .../legado/app/ui/book/read/page/ReadView.kt | 2 +- .../book/read/page/delegate/PageDelegate.kt | 2 +- .../read/page/delegate/ScrollPageDelegate.kt | 1 - .../read/page/provider/ChapterProvider.kt | 12 +++++++++ 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index dd3295eb3..30416a784 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -76,7 +76,6 @@ import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_C import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection -import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.searchContent.SearchContentActivity import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.source.edit.BookSourceEditActivity @@ -212,7 +211,8 @@ class ReadBookActivity : BaseReadBookActivity(), private val timeBatteryReceiver = TimeBatteryReceiver() private var screenTimeOut: Long = 0 private var loadStates: Boolean = false - override val pageFactory: TextPageFactory get() = binding.readView.pageFactory + override val pageFactory get() = binding.readView.pageFactory + override val pageDelegate get() = binding.readView.pageDelegate override val headerHeight: Int get() = binding.readView.curPage.headerHeight private val menuLayoutIsVisible get() = bottomDialog > 0 || binding.readMenu.isVisible private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 3a2fc507e..e100476f0 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -3,7 +3,6 @@ package io.legado.app.ui.book.read.page import android.content.Context import android.graphics.Canvas import android.graphics.Paint -import android.graphics.RectF import android.util.AttributeSet import android.view.MotionEvent import android.view.View @@ -14,6 +13,7 @@ import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Bookmark import io.legado.app.help.config.AppConfig import io.legado.app.model.ReadBook +import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPos @@ -45,7 +45,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } } private var callBack: CallBack - private val visibleRect = RectF() + private val visibleRect = ChapterProvider.visibleRect val selectStart = TextPos(0, 0, 0) private val selectEnd = TextPos(0, 0, 0) var textPage: TextPage = TextPage() @@ -56,7 +56,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at var reverseEndCursor = false //滚动参数 - private val pageFactory: TextPageFactory get() = callBack.pageFactory + private val pageFactory get() = callBack.pageFactory + private val pageDelegate get() = callBack.pageDelegate private var pageOffset = 0 private val pictureMirror = PictureMirror() private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim @@ -81,23 +82,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at invalidate() } - /** - * 更新绘制区域 - */ - fun upVisibleRect() { - visibleRect.set( - ChapterProvider.paddingLeft.toFloat(), - ChapterProvider.paddingTop.toFloat(), - ChapterProvider.visibleRight.toFloat(), - ChapterProvider.visibleBottom.toFloat() - ) - } - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) if (!isMainView) return ChapterProvider.upViewSize(w, h) - upVisibleRect() textPage.format() } @@ -159,17 +147,20 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } if (!pageFactory.hasPrev() && pageOffset > 0) { pageOffset = 0 + pageDelegate?.abortAnim() } else if (!pageFactory.hasNext() && pageOffset < 0 && pageOffset + textPage.height < ChapterProvider.visibleHeight ) { val offset = (ChapterProvider.visibleHeight - textPage.height).toInt() pageOffset = min(0, offset) + pageDelegate?.abortAnim() } else if (pageOffset > 0) { if (pageFactory.moveToPrev(true)) { pageOffset -= textPage.height.toInt() } else { pageOffset = 0 + pageDelegate?.abortAnim() } } else if (pageOffset < -textPage.height) { val height = textPage.height @@ -177,6 +168,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at pageOffset += height.toInt() } else { pageOffset = -height.toInt() + pageDelegate?.abortAnim() } } invalidate() @@ -699,6 +691,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at interface CallBack { val headerHeight: Int val pageFactory: TextPageFactory + val pageDelegate: PageDelegate? val isScroll: Boolean var isSelectingSearchResult: Boolean fun upSelectedStart(x: Float, y: Float, top: Float) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 3af462e62..7632ba4a1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -101,7 +101,6 @@ class PageView(context: Context) : FrameLayout(context) { vwTopDivider.gone(llHeader.isGone || !it.showHeaderLine) vwBottomDivider.gone(llFooter.isGone || !it.showFooterLine) } - contentTextView.upVisibleRect() upTime() upBattery(battery) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 1548cdade..c1d5f66e0 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -168,7 +168,7 @@ class ReadView(context: Context, attrs: AttributeSet) : } override fun computeScroll() { - pageDelegate?.scroll() + pageDelegate?.computeScroll() } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index 7be18c761..31fd7e8db 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -96,7 +96,7 @@ abstract class PageDelegate(protected val readView: ReadView) { viewHeight = height } - fun scroll() { + fun computeScroll() { if (scroller.computeScrollOffset()) { readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) } else if (isStarted) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index cec16e65e..e7d58f935 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -9,7 +9,6 @@ import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.provider.ChapterProvider -@Suppress("UnnecessaryVariable") class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) { // 滑动追踪的时间 diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 46ef5ecd1..cf472bdf4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.book.read.page.provider import android.graphics.Paint.FontMetrics +import android.graphics.RectF import android.graphics.Typeface import android.net.Uri import android.os.Build @@ -133,6 +134,9 @@ object ChapterProvider { var doublePage = false private set + @JvmStatic + var visibleRect = RectF() + private val titleMeasureHelper = TextMeasure(titlePaint) private val contentMeasureHelper = TextMeasure(contentPaint) @@ -911,6 +915,14 @@ object ChapterProvider { visibleRight = viewWidth - paddingRight visibleBottom = paddingTop + visibleHeight } + + visibleRect.set( + paddingLeft.toFloat(), + paddingTop.toFloat(), + visibleRight.toFloat(), + visibleBottom.toFloat() + ) + } } From d115910e9bb1e090e66a0747e2e25d72e47e6f77 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Tue, 13 Feb 2024 12:34:19 +0800 Subject: [PATCH 06/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/ui/book/read/ReadBookActivity.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 30416a784..83cd8fbb1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -404,6 +404,11 @@ class ReadBookActivity : BaseReadBookActivity(), } } + override fun onNightModeChanged(mode: Int) { + super.onNightModeChanged(mode) + binding.readView.invalidateTextPage() + } + /** * 菜单 */ @@ -1585,9 +1590,6 @@ class ReadBookActivity : BaseReadBookActivity(), observeEvent(EventBus.UP_SEEK_BAR) { binding.readMenu.upSeekBar() } - observeEvent(EventBus.RECREATE) { - binding.readView.invalidateTextPage() - } } private fun upScreenTimeOut() { From 7fbc23ab13e43afea4a5f06b78162f3229b711a1 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Tue, 13 Feb 2024 22:19:53 +0800 Subject: [PATCH 07/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/config/AppConfig.kt | 3 + .../io/legado/app/help/coroutine/Coroutine.kt | 15 +- .../main/java/io/legado/app/model/ReadBook.kt | 2 +- .../app/ui/book/read/BaseReadBookActivity.kt | 21 +++ .../app/ui/book/read/ReadBookActivity.kt | 67 ++------ .../io/legado/app/ui/book/read/ReadMenu.kt | 35 ++++- .../io/legado/app/ui/book/read/SearchMenu.kt | 10 +- .../legado/app/ui/book/read/page/AutoPager.kt | 146 ++++++++++++++++++ .../app/ui/book/read/page/ContentTextView.kt | 27 +++- .../legado/app/ui/book/read/page/PageView.kt | 19 ++- .../legado/app/ui/book/read/page/ReadView.kt | 56 ++++--- .../app/ui/book/read/page/api/DataSource.kt | 2 + .../read/page/delegate/ScrollPageDelegate.kt | 3 +- .../ui/book/read/page/entities/TextLine.kt | 2 +- .../ui/book/read/page/entities/TextPage.kt | 11 +- .../read/page/provider/TextPageFactory.kt | 2 + .../java/io/legado/app/utils/PictureMirror.kt | 18 ++- 17 files changed, 343 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index 1ce8d8a80..9563699b8 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -137,6 +137,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { } } + val textSelectAble: Boolean + get() = appCtx.getPrefBoolean(PreferKey.textSelectAble, true) + val isTransparentStatusBar: Boolean get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true) diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index 250ac5b51..78fbd70cc 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -1,5 +1,6 @@ package io.legado.app.help.coroutine +import android.os.Looper import io.legado.app.utils.printOnDebug import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletionHandler @@ -15,6 +16,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout +import java.util.concurrent.Executors import kotlin.coroutines.CoroutineContext /** @@ -33,6 +35,9 @@ class Coroutine( companion object { private val DEFAULT = MainScope() + private val launchExecutor = Executors.newSingleThreadExecutor() + private val mainThread = Looper.getMainLooper().thread + private val isMainThread inline get() = mainThread === Thread.currentThread() fun async( scope: CoroutineScope = DEFAULT, @@ -46,7 +51,7 @@ class Coroutine( } - private val job: Job + private val job: Job by lazy { executeInternal(context, block) } private var start: VoidCallback? = null private var success: Callback? = null @@ -67,7 +72,13 @@ class Coroutine( get() = job.isCompleted init { - this.job = executeInternal(context, block) + if (context == Dispatchers.Main.immediate && isMainThread) { + job + } else { + launchExecutor.execute { + job + } + } } fun timeout(timeMillis: () -> Long): Coroutine { diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 20bb7f48a..ada00455d 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -275,7 +275,7 @@ object ReadBook : CoroutineScope by MainScope() { textChapter.getPage(index - 2)?.recyclePictures() } if (index < pageIndex) { - textChapter.getPage(index + 2)?.recyclePictures() + textChapter.getPage(index + 3)?.recyclePictures() } } durChapterPos = curTextChapter?.getReadLength(index) ?: index diff --git a/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt index a7735129b..8496ae091 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt @@ -58,6 +58,12 @@ abstract class BaseReadBookActivity : override val binding by viewBinding(ActivityBookReadBinding::inflate) override val viewModel by viewModels() var bottomDialog = 0 + set(value) { + if (field != value) { + field = value + onBottomDialogChange() + } + } private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) { it.uri?.let { uri -> ReadBook.book?.let { book -> @@ -94,6 +100,21 @@ abstract class BaseReadBookActivity : } } + private fun onBottomDialogChange() { + when (bottomDialog) { + 0 -> onMenuHide() + 1 -> onMenuShow() + } + } + + open fun onMenuShow() { + + } + + open fun onMenuHide() { + + } + fun showPaddingConfig() { showDialogFragment() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 83cd8fbb1..ac05cbb3b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -94,7 +94,6 @@ import io.legado.app.utils.ACache import io.legado.app.utils.Debounce import io.legado.app.utils.LogUtils import io.legado.app.utils.StartActivityContract -import io.legado.app.utils.SyncedRenderer import io.legado.app.utils.applyOpenTint import io.legado.app.utils.buildMainHandler import io.legado.app.utils.getPrefBoolean @@ -201,8 +200,7 @@ class ReadBookActivity : BaseReadBookActivity(), } override val isInitFinish: Boolean get() = viewModel.isInitFinish override val isScroll: Boolean get() = binding.readView.isScroll - override var autoPageProgress = 0 - override var isAutoPage = false + private val isAutoPage get() = binding.readView.isAutoPage override var isShowingSearchResult = false override var isSelectingSearchResult = false set(value) { @@ -220,8 +218,6 @@ class ReadBookActivity : BaseReadBookActivity(), private var bookChanged = false private var pageChanged = false private var reloadContent = false - private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } } - private var autoPageScrollOffset = 0.0 private val handler by lazy { buildMainHandler() } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } @@ -934,6 +930,7 @@ class ReadBookActivity : BaseReadBookActivity(), ReadAloud.upTtsProgress(this) } loadStates = true + binding.readView.onContentLoadFinish() } /** @@ -945,9 +942,6 @@ class ReadBookActivity : BaseReadBookActivity(), success: (() -> Unit)? ) { lifecycleScope.launch { - if (relativePosition == 0) { - autoPageProgress = 0 - } binding.readView.upContent(relativePosition, resetPageOffset) upSeekBarProgress() loadStates = false @@ -970,8 +964,7 @@ class ReadBookActivity : BaseReadBookActivity(), */ override fun pageChanged() { pageChanged = true - lifecycleScope.launch { - autoPageProgress = 0 + handler.post { upSeekBarProgress() startBackupJob() } @@ -1053,8 +1046,7 @@ class ReadBookActivity : BaseReadBookActivity(), if (isAutoPage) { autoPageStop() } else { - isAutoPage = true - autoPagePlus() + binding.readView.autoPager.start() binding.readMenu.setAutoPage(true) screenTimeOut = -1L screenOffTimerStart() @@ -1063,53 +1055,12 @@ class ReadBookActivity : BaseReadBookActivity(), override fun autoPageStop() { if (isAutoPage) { - isAutoPage = false - autoPageRenderer.stop() - binding.readView.invalidate() - binding.readView.clearNextPageBitmap() + binding.readView.autoPager.stop() binding.readMenu.setAutoPage(false) upScreenTimeOut() } } - private fun autoPagePlus() { - autoPageProgress = 0 - autoPageScrollOffset = 0.0 - autoPageRenderer.start() - } - - private fun doAutoPage(frameTime: Double) { - if (menuLayoutIsVisible) { - return - } - if (binding.readView.run { isScroll && pageDelegate?.isRunning == true }) { - return - } - val readTime = ReadBookConfig.autoReadSpeed * 1000.0 - val height = binding.readView.height - autoPageScrollOffset += height / readTime * frameTime - if (autoPageScrollOffset < 1) { - return - } - val scrollOffset = autoPageScrollOffset.toInt() - autoPageScrollOffset -= scrollOffset - if (binding.readView.isScroll) { - binding.readView.curPage.scroll(-scrollOffset) - } else { - autoPageProgress += scrollOffset - if (autoPageProgress >= height) { - autoPageProgress = 0 - if (!binding.readView.fillPage(PageDirection.NEXT)) { - autoPageStop() - } else { - binding.readView.clearNextPageBitmap() - } - } else { - binding.readView.invalidate() - } - } - } - override fun openSourceEditActivity() { ReadBook.bookSource?.let { sourceEditActivity.launch { @@ -1414,6 +1365,14 @@ class ReadBookActivity : BaseReadBookActivity(), skipToSearch(searchResult) } + override fun onMenuShow() { + binding.readView.autoPager.pause() + } + + override fun onMenuHide() { + binding.readView.autoPager.resume() + } + /* 全文搜索跳转 */ private fun skipToSearch(searchResult: SearchResult) { val previousResult = binding.searchMenu.previousSearchResult diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt index bca34bf8b..5e163456c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt @@ -27,13 +27,38 @@ import io.legado.app.help.config.LocalConfig import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ThemeConfig import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.theme.* +import io.legado.app.lib.theme.Selector +import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.buttonDisabledColor +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.lib.theme.primaryColor +import io.legado.app.lib.theme.primaryTextColor import io.legado.app.model.ReadBook import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.browser.WebViewActivity import io.legado.app.ui.widget.seekbar.SeekBarChangeListener -import io.legado.app.utils.* -import splitties.views.* +import io.legado.app.utils.ColorUtils +import io.legado.app.utils.ConstraintModify +import io.legado.app.utils.activity +import io.legado.app.utils.dpToPx +import io.legado.app.utils.getPrefBoolean +import io.legado.app.utils.gone +import io.legado.app.utils.invisible +import io.legado.app.utils.loadAnimation +import io.legado.app.utils.modifyBegin +import io.legado.app.utils.navigationBarGravity +import io.legado.app.utils.navigationBarHeight +import io.legado.app.utils.openUrl +import io.legado.app.utils.putPrefBoolean +import io.legado.app.utils.startActivity +import io.legado.app.utils.visible +import splitties.views.bottomPadding +import splitties.views.leftPadding +import splitties.views.onClick +import splitties.views.onLongClick +import splitties.views.padding +import splitties.views.rightPadding /** * 阅读界面菜单 @@ -273,6 +298,7 @@ class ReadMenu @JvmOverloads constructor( } fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) { + callBack.onMenuShow() this.visible() binding.titleBar.visible() binding.bottomMenu.visible() @@ -286,6 +312,7 @@ class ReadMenu @JvmOverloads constructor( } fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode, onMenuOutEnd: (() -> Unit)? = null) { + callBack.onMenuHide() this.onMenuOutEnd = onMenuOutEnd if (this.isVisible) { if (anim) { @@ -558,6 +585,8 @@ class ReadMenu @JvmOverloads constructor( fun payAction() fun disableSource() fun skipToChapter(index: Int) + fun onMenuShow() + fun onMenuHide() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt index e3f0eeade..6edf40f80 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt @@ -18,7 +18,13 @@ import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.model.ReadBook import io.legado.app.ui.book.searchContent.SearchResult -import io.legado.app.utils.* +import io.legado.app.utils.ColorUtils +import io.legado.app.utils.activity +import io.legado.app.utils.invisible +import io.legado.app.utils.loadAnimation +import io.legado.app.utils.navigationBarGravity +import io.legado.app.utils.navigationBarHeight +import io.legado.app.utils.visible import splitties.views.bottomPadding import splitties.views.leftPadding import splitties.views.padding @@ -235,6 +241,8 @@ class SearchMenu @JvmOverloads constructor( fun exitSearchMenu() fun showMenuBar() fun navigateToSearch(searchResult: SearchResult, index: Int) + fun onMenuShow() + fun onMenuHide() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt new file mode 100644 index 000000000..494abec34 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt @@ -0,0 +1,146 @@ +package io.legado.app.ui.book.read.page + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Picture +import android.graphics.Rect +import android.os.Build +import android.os.SystemClock +import androidx.core.graphics.withClip +import io.legado.app.help.config.AppConfig +import io.legado.app.help.config.ReadBookConfig +import io.legado.app.lib.theme.ThemeStore +import io.legado.app.ui.book.read.page.entities.PageDirection +import io.legado.app.utils.screenshot + +/** + * 自动翻页 + */ +class AutoPager(private val readView: ReadView) { + private var progress = 0 + var isRunning = false + private var isPausing = false + private var scrollOffsetRemain = 0.0 + private var scrollOffset = 0 + private var lastTimeMillis = 0L + private var bitmap: Bitmap? = null + private var picture: Picture? = null + private var pictureIsDirty = true + private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + private val rect = Rect() + private val paint by lazy { Paint() } + + fun start() { + isRunning = true + paint.color = ThemeStore.accentColor + lastTimeMillis = SystemClock.uptimeMillis() + readView.curPage.upSelectAble(false) + readView.invalidate() + } + + fun stop() { + if (!isRunning) { + return + } + isRunning = false + isPausing = false + readView.curPage.upSelectAble(AppConfig.textSelectAble) + readView.invalidate() + reset() + } + + fun pause() { + if (!isRunning) { + return + } + isPausing = true + } + + fun resume() { + if (!isRunning) { + return + } + isPausing = false + lastTimeMillis = SystemClock.uptimeMillis() + readView.invalidate() + } + + fun reset() { + progress = 0 + scrollOffsetRemain = 0.0 + scrollOffset = 0 + bitmap?.recycle() + bitmap = null + pictureIsDirty = true + } + + fun onDraw(canvas: Canvas) { + if (!isRunning) { + return + } + + if (readView.isScroll) { + computeOffset() + if (!isPausing) readView.curPage.scroll(-scrollOffset) + } else { + val bottom = progress + val width = readView.width + if (atLeastApi23) { + if (picture == null) { + picture = Picture() + } + if (pictureIsDirty) { + pictureIsDirty = false + readView.nextPage.screenshot(picture!!) + } + canvas.withClip(0, 0, width, bottom) { + drawPicture(picture!!) + } + } else { + if (bitmap == null) { + bitmap = readView.nextPage.screenshot() + } + rect.set(0, 0, width, bottom) + canvas.drawBitmap(bitmap!!, rect, rect, null) + } + canvas.drawRect( + 0f, + bottom.toFloat() - 1, + width.toFloat(), + bottom.toFloat(), + paint + ) + if (!isPausing) readView.invalidate() + computeOffset() + } + + } + + private fun computeOffset() { + + val currentTime = SystemClock.uptimeMillis() + val elapsedTime = currentTime - lastTimeMillis + lastTimeMillis = currentTime + + val readTime = ReadBookConfig.autoReadSpeed * 1000.0 + val height = readView.height + scrollOffsetRemain += height / readTime * elapsedTime + if (scrollOffsetRemain < 1) { + return + } + scrollOffset = scrollOffsetRemain.toInt() + this.scrollOffsetRemain -= scrollOffset + if (!readView.isScroll) { + progress += scrollOffset + if (progress >= height) { + if (!readView.fillPage(PageDirection.NEXT)) { + stop() + } else { + reset() + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index e100476f0..d69774d0d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -9,7 +9,6 @@ import android.view.View import androidx.core.graphics.withTranslation import io.legado.app.R import io.legado.app.constant.PageAnim -import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Bookmark import io.legado.app.help.config.AppConfig import io.legado.app.model.ReadBook @@ -28,16 +27,16 @@ import io.legado.app.ui.widget.dialog.PhotoDialog import io.legado.app.utils.PictureMirror import io.legado.app.utils.activity import io.legado.app.utils.getCompatColor -import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.showDialogFragment import io.legado.app.utils.toastOnUi +import java.util.concurrent.Executors import kotlin.math.min /** * 阅读内容视图 */ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { - var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true) + var selectAble = AppConfig.textSelectAble val selectedPaint by lazy { Paint().apply { color = context.getCompatColor(R.color.btn_bg_press_2) @@ -61,6 +60,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at private var pageOffset = 0 private val pictureMirror = PictureMirror() private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim + private var autoPager: AutoPager? = null + private val renderThread by lazy { Executors.newSingleThreadExecutor() } + private val renderRunnable by lazy { Runnable { preRenderPage() } } //绘制图片的paint val imagePaint by lazy { @@ -91,6 +93,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at override fun onDraw(canvas: Canvas) { super.onDraw(canvas) + autoPager?.onDraw(canvas) if (longScreenshot) { canvas.translate(0f, scrollY.toFloat()) } @@ -179,6 +182,20 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at pictureMirror.invalidate() } + fun submitPreRenderTask() { + renderThread.submit(renderRunnable) + } + + private fun preRenderPage() { + val view = this + pageFactory.run { + prevPage.preRender(view) + prevPage.preRender(view) + nextPage.preRender(view) + nextPlusPage.preRender(view) + } + } + /** * 重置滚动位置 */ @@ -669,6 +686,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } } + fun setAutoPager(autoPager: AutoPager?) { + this.autoPager = autoPager + } + override fun canScrollVertically(direction: Int): Boolean { return callBack.isScroll && pageFactory.hasNext() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 7632ba4a1..0e2de7eee 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -45,6 +45,8 @@ class PageView(context: Context) : FrameLayout(context) { private var tvBookName: BatteryView? = null private var tvTimeBattery: BatteryView? = null private var tvTimeBatteryP: BatteryView? = null + private var isMainView = false + var isScroll = false val headerHeight: Int get() { @@ -272,7 +274,13 @@ class PageView(context: Context) : FrameLayout(context) { * 设置内容 */ fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) { - setProgress(textPage) + if (isMainView && !isScroll) { + setProgress(textPage) + } else { + post { + setProgress(textPage) + } + } if (resetPageOffset) { resetPageOffset() } @@ -324,6 +332,14 @@ class PageView(context: Context) : FrameLayout(context) { tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress" } + fun setAutoPager(autoPager: AutoPager?) { + binding.contentTextView.setAutoPager(autoPager) + } + + fun submitPreRenderTask() { + binding.contentTextView.submitPreRenderTask() + } + /** * 滚动事件 */ @@ -375,6 +391,7 @@ class PageView(context: Context) : FrameLayout(context) { } fun markAsMainView() { + isMainView = true binding.contentTextView.isMainView = true } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index c1d5f66e0..25df757ea 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -35,7 +35,6 @@ import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.utils.activity import io.legado.app.utils.invisible -import io.legado.app.utils.screenshot import io.legado.app.utils.showDialogFragment import io.legado.app.utils.visible import java.text.BreakIterator @@ -108,6 +107,8 @@ class ReadView(context: Context, attrs: AttributeSet) : private val autoPagePint by lazy { Paint().apply { color = context.accentColor } } private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) } private var nextPageBitmap: Bitmap? = null + val autoPager = AutoPager(this) + val isAutoPage get() = autoPager.isRunning init { addView(nextPage) @@ -149,22 +150,7 @@ class ReadView(context: Context, attrs: AttributeSet) : override fun dispatchDraw(canvas: Canvas) { super.dispatchDraw(canvas) pageDelegate?.onDraw(canvas) - if (!isInEditMode && callBack.isAutoPage && !isScroll) { - // 自动翻页 - val bitmap = nextPageBitmap ?: nextPage.screenshot()?.also { nextPageBitmap = it } - bitmap?.let { - val bottom = callBack.autoPageProgress - autoPageRect.set(0, 0, width, bottom) - canvas.drawBitmap(it, autoPageRect, autoPageRect, null) - canvas.drawRect( - 0f, - bottom.toFloat() - 1, - width.toFloat(), - bottom.toFloat(), - autoPagePint - ) - } - } + autoPager.onDraw(canvas) } override fun computeScroll() { @@ -262,6 +248,7 @@ class ReadView(context: Context, attrs: AttributeSet) : pageDelegate?.onTouch(event) } pressOnTextSelected = false + autoPager.resume() } } return true @@ -530,6 +517,12 @@ class ReadView(context: Context, attrs: AttributeSet) : } else { nextPage.visible() } + if (isScroll) { + curPage.setAutoPager(autoPager) + } else { + curPage.setAutoPager(null) + } + curPage.isScroll = isScroll } /** @@ -541,12 +534,13 @@ class ReadView(context: Context, attrs: AttributeSet) : post { curPage.setContentDescription(pageFactory.curPage.text) } - if (isScroll && !callBack.isAutoPage) { + if (isScroll && !isAutoPage) { curPage.setContent(pageFactory.curPage, resetPageOffset) } else { - if (callBack.isAutoPage && relativePosition >= 0) { - clearNextPageBitmap() - } +// if (isAutoPage && relativePosition >= 0) { +//// clearNextPageBitmap() +// autoPager.clear() +// } when (relativePosition) { -1 -> prevPage.setContent(pageFactory.prevPage) 1 -> nextPage.setContent(pageFactory.nextPage) @@ -663,6 +657,24 @@ class ReadView(context: Context, attrs: AttributeSet) : } } + fun onScrollAnimStart() { + autoPager.pause() + } + + fun onScrollAnimStop() { + autoPager.resume() + } + + override fun onPageChange() { + autoPager.reset() + curPage.submitPreRenderTask() + } + + fun onContentLoadFinish() { + autoPager.reset() + curPage.submitPreRenderTask() + } + override val currentChapter: TextChapter? get() { return if (callBack.isInitFinish) ReadBook.textChapter(0) else null @@ -688,8 +700,6 @@ class ReadView(context: Context, attrs: AttributeSet) : interface CallBack { val isInitFinish: Boolean - val isAutoPage: Boolean - val autoPageProgress: Int fun showActionMenu() fun screenOffTimerStart() fun showTextActionMenu() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt index eb28942d6..e06238554 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt @@ -20,4 +20,6 @@ interface DataSource { fun hasPrevChapter(): Boolean fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) + + fun onPageChange() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index e7d58f935..232aaa925 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -21,6 +21,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) { var noAnim: Boolean = false override fun onAnimStart(animationSpeed: Int) { + readView.onScrollAnimStart() //惯性滚动 fling( 0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(), @@ -29,7 +30,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) { } override fun onAnimStop() { - // nothing + readView.onScrollAnimStop() } override fun onTouch(event: MotionEvent) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 0f89a6e32..2683ed97a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -121,7 +121,7 @@ data class TextLine( return visible } - fun draw(view: ContentTextView, canvas: Canvas) { + fun draw(view: ContentTextView, canvas: Canvas?) { pictureMirror.draw(canvas, view.width, height.toInt()) { drawTextLine(view, this) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 1a9d366f7..079519be1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -263,7 +263,7 @@ data class TextPage( return null } - fun draw(view: ContentTextView, canvas: Canvas) { + fun draw(view: ContentTextView, canvas: Canvas?) { pictureMirror.draw(canvas, view.width, height.toInt()) { drawPage(view, this) } @@ -278,6 +278,15 @@ data class TextPage( } } + fun preRender(view: ContentTextView) { + if (!pictureMirror.isDirty) return + draw(view, null) + } + + fun isDirty(): Boolean { + return pictureMirror.isDirty + } + fun invalidate() { pictureMirror.invalidate() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt index 9ff5f77ee..386d5d2ed 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt @@ -44,6 +44,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource ReadBook.setPageIndex(pageIndex.plus(1)) } if (upContent) upContent(resetPageOffset = false) + dataSource.onPageChange() true } else false @@ -63,6 +64,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource ReadBook.setPageIndex(pageIndex.minus(1)) } if (upContent) upContent(resetPageOffset = false) + dataSource.onPageChange() true } else false diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index 9a725500d..71bd6ccbf 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -4,23 +4,31 @@ import android.graphics.Canvas import android.graphics.Picture import android.os.Build import androidx.core.graphics.record +import java.util.concurrent.locks.ReentrantLock class PictureMirror { var picture: Picture? = null + @Volatile var isDirty = true + val lock = ReentrantLock() - inline fun draw(canvas: Canvas, width: Int, height: Int, block: Canvas.() -> Unit) { + inline fun draw(canvas: Canvas?, width: Int, height: Int, block: Canvas.() -> Unit) { if (atLeastApi23) { if (picture == null) picture = Picture() val picture = picture!! if (isDirty) { - isDirty = false - picture.record(width, height, block) + if (!lock.tryLock()) return + try { + picture.record(width, height, block) + isDirty = false + } finally { + lock.unlock() + } } - canvas.drawPicture(picture) + canvas?.drawPicture(picture) } else { - canvas.block() + canvas?.block() } } From 7ab30130ab409d8fde23712a4c025a97affee6dc Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Tue, 13 Feb 2024 22:33:15 +0800 Subject: [PATCH 08/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/ui/book/read/page/AutoPager.kt | 3 ++- app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt index 494abec34..7d5ebbd0a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt @@ -48,6 +48,7 @@ class AutoPager(private val readView: ReadView) { readView.curPage.upSelectAble(AppConfig.textSelectAble) readView.invalidate() reset() + picture = null } fun pause() { @@ -143,4 +144,4 @@ class AutoPager(private val readView: ReadView) { } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 25df757ea..8a5dd4bef 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -537,10 +537,6 @@ class ReadView(context: Context, attrs: AttributeSet) : if (isScroll && !isAutoPage) { curPage.setContent(pageFactory.curPage, resetPageOffset) } else { -// if (isAutoPage && relativePosition >= 0) { -//// clearNextPageBitmap() -// autoPager.clear() -// } when (relativePosition) { -1 -> prevPage.setContent(pageFactory.prevPage) 1 -> nextPage.setContent(pageFactory.nextPage) From c45f138fd581a7ff1db76af0dc3ad9bc6216d230 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Tue, 13 Feb 2024 22:42:15 +0800 Subject: [PATCH 09/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/page/ContentTextView.kt | 2 +- .../legado/app/ui/book/read/page/ReadView.kt | 19 +------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index d69774d0d..d80f11816 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -190,7 +190,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at val view = this pageFactory.run { prevPage.preRender(view) - prevPage.preRender(view) + curPage.preRender(view) nextPage.preRender(view) nextPlusPage.preRender(view) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 8a5dd4bef..c1814aeb5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -2,10 +2,7 @@ package io.legado.app.ui.book.read.page import android.annotation.SuppressLint import android.content.Context -import android.graphics.Bitmap import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Rect import android.graphics.RectF import android.os.Build import android.util.AttributeSet @@ -16,7 +13,6 @@ import android.widget.FrameLayout import io.legado.app.constant.PageAnim import io.legado.app.help.config.AppConfig import io.legado.app.help.config.ReadBookConfig -import io.legado.app.lib.theme.accentColor import io.legado.app.model.ReadAloud import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.ContentEditDialog @@ -36,7 +32,6 @@ import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.utils.activity import io.legado.app.utils.invisible import io.legado.app.utils.showDialogFragment -import io.legado.app.utils.visible import java.text.BreakIterator import java.util.Locale import kotlin.math.abs @@ -103,10 +98,7 @@ class ReadView(context: Context, attrs: AttributeSet) : private val blRect = RectF() private val bcRect = RectF() private val brRect = RectF() - private val autoPageRect by lazy { Rect() } - private val autoPagePint by lazy { Paint().apply { color = context.accentColor } } private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) } - private var nextPageBitmap: Bitmap? = null val autoPager = AutoPager(this) val isAutoPage get() = autoPager.isRunning @@ -115,6 +107,7 @@ class ReadView(context: Context, attrs: AttributeSet) : addView(curPage) addView(prevPage) prevPage.invisible() + nextPage.invisible() curPage.markAsMainView() if (!isInEditMode) { upBg() @@ -512,11 +505,6 @@ class ReadView(context: Context, attrs: AttributeSet) : } (pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage pageDelegate?.setViewSize(width, height) - if (pageDelegate is NoAnimPageDelegate) { - nextPage.invisible() - } else { - nextPage.visible() - } if (isScroll) { curPage.setAutoPager(autoPager) } else { @@ -639,11 +627,6 @@ class ReadView(context: Context, attrs: AttributeSet) : return curPage.getCurVisibleFirstLine()?.pagePosition ?: 0 } - fun clearNextPageBitmap() { - nextPageBitmap?.recycle() - nextPageBitmap = null - } - fun invalidateTextPage() { pageFactory.run { prevPage.invalidateAll() From 4119231dd028263b2f0ff12949f9c7e5e20a231e Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Wed, 14 Feb 2024 23:09:18 +0800 Subject: [PATCH 10/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/coroutine/Coroutine.kt | 15 +------ .../app/ui/book/read/ReadBookActivity.kt | 10 ++++- .../app/ui/book/read/page/ContentTextView.kt | 24 ++++------- .../legado/app/ui/book/read/page/ReadView.kt | 7 +--- .../app/ui/book/read/page/api/DataSource.kt | 1 - .../ui/book/read/page/entities/TextChapter.kt | 15 +++++++ .../ui/book/read/page/entities/TextPage.kt | 2 +- .../read/page/provider/TextPageFactory.kt | 2 - .../legado/app/utils/CollectionExtensions.kt | 30 +++++++++++++ .../java/io/legado/app/utils/PictureMirror.kt | 42 ++++++++++++++++++- 10 files changed, 104 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index 78fbd70cc..250ac5b51 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -1,6 +1,5 @@ package io.legado.app.help.coroutine -import android.os.Looper import io.legado.app.utils.printOnDebug import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletionHandler @@ -16,7 +15,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import java.util.concurrent.Executors import kotlin.coroutines.CoroutineContext /** @@ -35,9 +33,6 @@ class Coroutine( companion object { private val DEFAULT = MainScope() - private val launchExecutor = Executors.newSingleThreadExecutor() - private val mainThread = Looper.getMainLooper().thread - private val isMainThread inline get() = mainThread === Thread.currentThread() fun async( scope: CoroutineScope = DEFAULT, @@ -51,7 +46,7 @@ class Coroutine( } - private val job: Job by lazy { executeInternal(context, block) } + private val job: Job private var start: VoidCallback? = null private var success: Callback? = null @@ -72,13 +67,7 @@ class Coroutine( get() = job.isCompleted init { - if (context == Dispatchers.Main.immediate && isMainThread) { - job - } else { - launchExecutor.execute { - job - } - } + this.job = executeInternal(context, block) } fun timeout(timeMillis: () -> Long): Coroutine { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index ac05cbb3b..254b3d720 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -116,6 +116,7 @@ import io.legado.app.utils.toastOnUi import io.legado.app.utils.visible import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job +import kotlinx.coroutines.asExecutor import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -220,6 +221,7 @@ class ReadBookActivity : BaseReadBookActivity(), private var reloadContent = false private val handler by lazy { buildMainHandler() } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } + private val executor = IO.asExecutor() //恢复跳转前进度对话框的交互结果 private var confirmRestoreProcess: Boolean? = null @@ -908,7 +910,7 @@ class ReadBookActivity : BaseReadBookActivity(), } override fun upMenuView() { - lifecycleScope.launch { + handler.post { binding.readMenu.upBookView() } } @@ -930,7 +932,6 @@ class ReadBookActivity : BaseReadBookActivity(), ReadAloud.upTtsProgress(this) } loadStates = true - binding.readView.onContentLoadFinish() } /** @@ -964,8 +965,13 @@ class ReadBookActivity : BaseReadBookActivity(), */ override fun pageChanged() { pageChanged = true + runOnUiThread { + binding.readView.onPageChange() + } handler.post { upSeekBarProgress() + } + executor.execute { startBackupJob() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index d80f11816..54002322d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -8,7 +8,6 @@ import android.view.MotionEvent import android.view.View import androidx.core.graphics.withTranslation import io.legado.app.R -import io.legado.app.constant.PageAnim import io.legado.app.data.entities.Bookmark import io.legado.app.help.config.AppConfig import io.legado.app.model.ReadBook @@ -24,12 +23,12 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.widget.dialog.PhotoDialog -import io.legado.app.utils.PictureMirror import io.legado.app.utils.activity import io.legado.app.utils.getCompatColor import io.legado.app.utils.showDialogFragment import io.legado.app.utils.toastOnUi import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory import kotlin.math.min /** @@ -58,10 +57,12 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at private val pageFactory get() = callBack.pageFactory private val pageDelegate get() = callBack.pageDelegate private var pageOffset = 0 - private val pictureMirror = PictureMirror() - private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim private var autoPager: AutoPager? = null - private val renderThread by lazy { Executors.newSingleThreadExecutor() } + private val renderThread by lazy { + Executors.newSingleThreadExecutor { + Thread(it, "TextPageRender") + } + } private val renderRunnable by lazy { Runnable { preRenderPage() } } //绘制图片的paint @@ -99,13 +100,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } check(!visibleRect.isEmpty) { "visibleRect 为空" } canvas.clipRect(visibleRect) - if (!callBack.isScroll && !isNoAnim) { - pictureMirror.draw(canvas, width, height) { - drawPage(this) - } - } else { - drawPage(canvas) - } + drawPage(canvas) } /** @@ -177,11 +172,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at invalidate() } - override fun invalidate() { - super.invalidate() - pictureMirror.invalidate() - } - fun submitPreRenderTask() { renderThread.submit(renderRunnable) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index c1814aeb5..0b21995d8 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -644,12 +644,7 @@ class ReadView(context: Context, attrs: AttributeSet) : autoPager.resume() } - override fun onPageChange() { - autoPager.reset() - curPage.submitPreRenderTask() - } - - fun onContentLoadFinish() { + fun onPageChange() { autoPager.reset() curPage.submitPreRenderTask() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt index e06238554..ceddd279b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt @@ -21,5 +21,4 @@ interface DataSource { fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) - fun onPageChange() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt index 07cff13df..b1f3fb9c6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt @@ -4,6 +4,8 @@ package io.legado.app.ui.book.read.page.entities import androidx.annotation.Keep import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.ReplaceRule +import io.legado.app.utils.fastBinarySearchBy +import kotlin.math.abs import kotlin.math.min /** @@ -85,12 +87,15 @@ data class TextChapter( * @return 已读长度 */ fun getReadLength(pageIndex: Int): Int { + return pages[min(pageIndex, lastIndex)].lines.first().chapterPosition + /* var length = 0 val maxIndex = min(pageIndex, pages.size) for (index in 0 until maxIndex) { length += pages[index].charSize } return length + */ } /** @@ -185,6 +190,15 @@ data class TextChapter( * @return 根据索引位置获取所在页 */ fun getPageIndexByCharIndex(charIndex: Int): Int { + val index = pages.fastBinarySearchBy(charIndex) { + it.lines.first().chapterPosition + } + return if (index >= 0) { + index + } else { + abs(index + 1) - 1 + } + /* 相当于以下实现 var length = 0 for (i in pages.indices) { val page = pages[i] @@ -194,6 +208,7 @@ data class TextChapter( } } return pages.lastIndex + */ } fun clearSearchResult() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 079519be1..95d06d8a9 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -264,7 +264,7 @@ data class TextPage( } fun draw(view: ContentTextView, canvas: Canvas?) { - pictureMirror.draw(canvas, view.width, height.toInt()) { + pictureMirror.drawLocked(canvas, view.width, height.toInt(), view) { drawPage(view, this) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt index 386d5d2ed..9ff5f77ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt @@ -44,7 +44,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource ReadBook.setPageIndex(pageIndex.plus(1)) } if (upContent) upContent(resetPageOffset = false) - dataSource.onPageChange() true } else false @@ -64,7 +63,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource ReadBook.setPageIndex(pageIndex.minus(1)) } if (upContent) upContent(resetPageOffset = false) - dataSource.onPageChange() true } else false diff --git a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt index 3e6f33306..e8e7b87d7 100644 --- a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt @@ -7,3 +7,33 @@ fun List.fastSum(): Float { } return sum } + +inline fun List.fastBinarySearch( + fromIndex: Int = 0, + toIndex: Int = size, + comparison: (T) -> Int +): Int { + var low = fromIndex + var high = toIndex - 1 + + while (low <= high) { + val mid = (low + high).ushr(1) // safe from overflows + val midVal = get(mid) + val cmp = comparison(midVal) + + if (cmp < 0) + low = mid + 1 + else if (cmp > 0) + high = mid - 1 + else + return mid // key found + } + return -(low + 1) // key not found +} + +inline fun > List.fastBinarySearchBy( + key: K?, + fromIndex: Int = 0, + toIndex: Int = size, + crossinline selector: (T) -> K? +): Int = fastBinarySearch(fromIndex, toIndex) { compareValues(selector(it), key) } diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index 71bd6ccbf..2b03eab20 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -3,6 +3,7 @@ package io.legado.app.utils import android.graphics.Canvas import android.graphics.Picture import android.os.Build +import android.view.View import androidx.core.graphics.record import java.util.concurrent.locks.ReentrantLock @@ -12,16 +13,31 @@ class PictureMirror { @Volatile var isDirty = true val lock = ReentrantLock() + @Volatile + var scheduleInvalidateView: View? = null - inline fun draw(canvas: Canvas?, width: Int, height: Int, block: Canvas.() -> Unit) { + inline fun drawLocked( + canvas: Canvas?, + width: Int, + height: Int, + view: View? = null, + block: Canvas.() -> Unit + ) { if (atLeastApi23) { if (picture == null) picture = Picture() val picture = picture!! if (isDirty) { - if (!lock.tryLock()) return + if (!lock.tryLock()) { + if (canvas != null && view != null) { + scheduleInvalidateView = view + } + return + } try { picture.record(width, height, block) isDirty = false + scheduleInvalidateView?.postInvalidate() + scheduleInvalidateView = null } finally { lock.unlock() } @@ -32,6 +48,28 @@ class PictureMirror { } } + /** + * 非线程安全,多线程调用可能会崩溃 + */ + inline fun draw( + canvas: Canvas?, + width: Int, + height: Int, + block: Canvas.() -> Unit + ) { + if (atLeastApi23) { + if (picture == null) picture = Picture() + val picture = picture!! + if (isDirty) { + picture.record(width, height, block) + isDirty = false + } + canvas?.drawPicture(picture) + } else { + canvas?.block() + } + } + fun invalidate() { isDirty = true } From db8f9ca6b91fd11126c6e71bce77a2201223131a Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Wed, 14 Feb 2024 23:15:54 +0800 Subject: [PATCH 11/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/page/ContentTextView.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 54002322d..c54f8521b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -28,7 +28,6 @@ import io.legado.app.utils.getCompatColor import io.legado.app.utils.showDialogFragment import io.legado.app.utils.toastOnUi import java.util.concurrent.Executors -import java.util.concurrent.ThreadFactory import kotlin.math.min /** @@ -58,11 +57,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at private val pageDelegate get() = callBack.pageDelegate private var pageOffset = 0 private var autoPager: AutoPager? = null - private val renderThread by lazy { - Executors.newSingleThreadExecutor { - Thread(it, "TextPageRender") - } - } private val renderRunnable by lazy { Runnable { preRenderPage() } } //绘制图片的paint @@ -699,6 +693,14 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at return callBack.onLongScreenshotTouchEvent(event) } + companion object { + private val renderThread by lazy { + Executors.newSingleThreadExecutor { + Thread(it, "TextPageRender") + } + } + } + interface CallBack { val headerHeight: Int val pageFactory: TextPageFactory From 8311a0508cc91be21ad447ade404c82a8d45dbf6 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 09:49:09 +0800 Subject: [PATCH 12/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/model/ReadBook.kt | 56 ++++++++++--------- .../app/service/HttpReadAloudService.kt | 9 +-- .../app/ui/book/read/ReadBookActivity.kt | 3 +- .../read/page/provider/ChapterProvider.kt | 37 +++++++----- 4 files changed, 60 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index ada00455d..1294454de 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -29,6 +29,8 @@ import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.delay import kotlinx.coroutines.launch import splitties.init.appCtx +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors import kotlin.math.min @@ -63,6 +65,7 @@ object ReadBook : CoroutineScope by MainScope() { val downloadFailChapters = hashMapOf() var contentProcessor: ContentProcessor? = null val downloadScope = CoroutineScope(SupervisorJob() + IO) + val executor: ExecutorService = Executors.newSingleThreadExecutor() //暂时保存跳转前进度 fun saveCurrentBookProcess() { @@ -158,10 +161,10 @@ object ReadBook : CoroutineScope by MainScope() { } fun upReadTime() { - if (!AppConfig.enableReadRecord) { - return - } - Coroutine.async(executeContext = IO) { + executor.execute { + if (!AppConfig.enableReadRecord) { + return@execute + } readRecord.readTime = readRecord.readTime + System.currentTimeMillis() - readStartTime readStartTime = System.currentTimeMillis() readRecord.lastRead = System.currentTimeMillis() @@ -536,8 +539,8 @@ object ReadBook : CoroutineScope by MainScope() { } fun saveRead(pageChanged: Boolean = false) { - Coroutine.async(executeContext = IO) { - val book = book ?: return@async + executor.execute { + val book = book ?: return@execute book.lastCheckCount = 0 book.durChapterTime = System.currentTimeMillis() val chapterChanged = book.durChapterIndex != durChapterIndex @@ -560,26 +563,29 @@ object ReadBook : CoroutineScope by MainScope() { */ private fun preDownload() { if (book?.isLocal == true) return - if (AppConfig.preDownloadNum < 2) { - return - } - preDownloadTask?.cancel() - preDownloadTask = Coroutine.async(executeContext = IO) { - //预下载 - launch { - val maxChapterIndex = min(durChapterIndex + AppConfig.preDownloadNum, chapterSize) - for (i in durChapterIndex.plus(2)..maxChapterIndex) { - if (downloadedChapters.contains(i)) continue - if ((downloadFailChapters[i] ?: 0) >= 3) continue - downloadIndex(i) - } + executor.execute { + if (AppConfig.preDownloadNum < 2) { + return@execute } - launch { - val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) - for (i in durChapterIndex.minus(2) downTo minChapterIndex) { - if (downloadedChapters.contains(i)) continue - if ((downloadFailChapters[i] ?: 0) >= 3) continue - downloadIndex(i) + preDownloadTask?.cancel() + preDownloadTask = Coroutine.async(executeContext = IO) { + //预下载 + launch { + val maxChapterIndex = + min(durChapterIndex + AppConfig.preDownloadNum, chapterSize) + for (i in durChapterIndex.plus(2)..maxChapterIndex) { + if (downloadedChapters.contains(i)) continue + if ((downloadFailChapters[i] ?: 0) >= 3) continue + downloadIndex(i) + } + } + launch { + val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) + for (i in durChapterIndex.minus(2) downTo minChapterIndex) { + if (downloadedChapters.contains(i)) continue + if ((downloadFailChapters[i] ?: 0) >= 3) continue + downloadIndex(i) + } } } } diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index 7070861be..a8ef1f579 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -91,7 +91,7 @@ class HttpReadAloudService : BaseReadAloudService(), playIndexJob?.cancel() } - private fun playNext() { + private fun updateNextPos() { readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos paragraphStartPos = 0 if (nowSpeak < contentList.lastIndex) { @@ -355,14 +355,14 @@ class HttpReadAloudService : BaseReadAloudService(), Player.STATE_ENDED -> { // 结束 playErrorNo = 0 - playNext() + updateNextPos() } } } override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return - playNext() + updateNextPos() upPlayPos() } @@ -375,7 +375,8 @@ class HttpReadAloudService : BaseReadAloudService(), AppLog.put("朗读连续5次错误, 最后一次错误代码(${error.localizedMessage})", error) ReadAloud.pause(this) } else { - playNext() + updateNextPos() + exoPlayer.seekToNextMediaItem() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 254b3d720..28623edb5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -191,7 +191,6 @@ class ReadBookActivity : BaseReadBookActivity(), } private var menu: Menu? = null private var backupJob: Job? = null - private var keepScreenJon: Job? = null private var tts: TTS? = null val textActionMenu: TextActionMenu by lazy { TextActionMenu(this, this) @@ -221,7 +220,7 @@ class ReadBookActivity : BaseReadBookActivity(), private var reloadContent = false private val handler by lazy { buildMainHandler() } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } - private val executor = IO.asExecutor() + private val executor = ReadBook.executor //恢复跳转前进度对话框的交互结果 private var confirmRestoreProcess: Boolean? = null diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index cf472bdf4..d78440c88 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -380,6 +380,8 @@ object ChapterProvider { textLine.addColumn( ImageColumn(start = x + start, end = x + end, src = src) ) + calcTextLinePosition(textPages, textLine, stringBuilder.length) + stringBuilder.append(" ") // 确保翻页时索引计算正确 textPages.last().addLine(textLine) } return absStartX to durY + textHeight * paragraphSpacing / 10f @@ -519,24 +521,11 @@ object ChapterProvider { if (doublePage) { textLine.isLeftLine = absStartX < viewWidth / 2 } - val sbLength = stringBuilder.length + calcTextLinePosition(textPages, textLine, stringBuilder.length) stringBuilder.append(lineText) if (textLine.isParagraphEnd) { stringBuilder.append("\n") } - val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 } - ?: textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull { it.paragraphNum > 0 } - val paragraphNum = when { - lastLine == null -> 1 - lastLine.isParagraphEnd -> lastLine.paragraphNum + 1 - else -> lastLine.paragraphNum - } - textLine.paragraphNum = paragraphNum - textLine.chapterPosition = - (textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull()?.run { - chapterPosition + charSize + if (isParagraphEnd) 1 else 0 - } ?: 0) + sbLength - textLine.pagePosition = sbLength textLine.upTopBottom(durY, textHeight, fontMetrics) val textPage = textPages.last() textPage.addLine(textLine) @@ -549,6 +538,26 @@ object ChapterProvider { return Pair(absStartX, durY) } + private fun calcTextLinePosition( + textPages: ArrayList, + textLine: TextLine, + sbLength: Int + ) { + val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 } + ?: textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull { it.paragraphNum > 0 } + val paragraphNum = when { + lastLine == null -> 1 + lastLine.isParagraphEnd -> lastLine.paragraphNum + 1 + else -> lastLine.paragraphNum + } + textLine.paragraphNum = paragraphNum + textLine.chapterPosition = + (textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull()?.run { + chapterPosition + charSize + if (isParagraphEnd) 1 else 0 + } ?: 0) + sbLength + textLine.pagePosition = sbLength + } + /** * 有缩进,两端对齐 */ From 9d1aa1c5d67085d94fd1e85bf6d2375db8896572 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 11:46:50 +0800 Subject: [PATCH 13/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/page/provider/ChapterProvider.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index d78440c88..c38d3dc9c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -180,6 +180,8 @@ object ChapterProvider { durY = it.second } } + textPages.last().lines.last().isParagraphEnd = true + stringBuilder.append("\n") durY += titleBottomSpacing } contents.forEach { content -> @@ -269,6 +271,8 @@ object ChapterProvider { } } } + textPages.last().lines.last().isParagraphEnd = true + stringBuilder.append("\n") } val textPage = textPages.last() val endPadding = 20.dpToPx() @@ -481,7 +485,6 @@ object ChapterProvider { lineIndex == layout.lineCount - 1 -> { //最后一行 textLine.text = lineText - textLine.isParagraphEnd = true //标题x轴居中 val startX = if ( isTitle && @@ -523,9 +526,6 @@ object ChapterProvider { } calcTextLinePosition(textPages, textLine, stringBuilder.length) stringBuilder.append(lineText) - if (textLine.isParagraphEnd) { - stringBuilder.append("\n") - } textLine.upTopBottom(durY, textHeight, fontMetrics) val textPage = textPages.last() textPage.addLine(textLine) From cc99eb1f03a3cb2cf5b2b91f4e1253aa24f0e433 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 15:56:17 +0800 Subject: [PATCH 14/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/constant/PreferKey.kt | 1 - .../io/legado/app/help/config/AppConfig.kt | 2 - .../java/io/legado/app/model/ImageProvider.kt | 39 +------ .../app/ui/book/read/ReadBookActivity.kt | 4 +- .../read/page/entities/column/ImageColumn.kt | 5 +- .../java/io/legado/app/utils/BitmapCache.kt | 104 ------------------ .../java/io/legado/app/utils/BitmapUtils.kt | 1 - app/src/main/res/xml/pref_config_read.xml | 7 -- 8 files changed, 5 insertions(+), 158 deletions(-) delete mode 100644 app/src/main/java/io/legado/app/utils/BitmapCache.kt diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index d8995cec7..fd109f3ef 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -117,7 +117,6 @@ object PreferKey { const val welcomeShowIconDark = "welcomeShowIconDark" const val pageTouchSlop = "pageTouchSlop" const val showAddToShelfAlert = "showAddToShelfAlert" - const val asyncLoadImage = "asyncLoadImage" const val ignoreAudioFocus = "ignoreAudioFocus" const val parallelExportBook = "parallelExportBook" const val progressBarBehavior = "progressBarBehavior" diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index 9563699b8..b719f1d45 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -460,8 +460,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true) - val asyncLoadImage get() = appCtx.getPrefBoolean(PreferKey.asyncLoadImage, false) - val ignoreAudioFocus get() = appCtx.getPrefBoolean(PreferKey.ignoreAudioFocus, false) val onlyLatestBackup get() = appCtx.getPrefBoolean(PreferKey.onlyLatestBackup, true) diff --git a/app/src/main/java/io/legado/app/model/ImageProvider.kt b/app/src/main/java/io/legado/app/model/ImageProvider.kt index 1bb69b7ae..81a256327 100644 --- a/app/src/main/java/io/legado/app/model/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/model/ImageProvider.kt @@ -6,9 +6,7 @@ import android.os.Build import android.util.Size import androidx.collection.LruCache import io.legado.app.R -import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog.putDebug -import io.legado.app.constant.PageAnim import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookSource import io.legado.app.exception.NoStackTraceException @@ -16,10 +14,8 @@ import io.legado.app.help.book.BookHelp import io.legado.app.help.book.isEpub import io.legado.app.help.book.isPdf import io.legado.app.help.config.AppConfig -import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.localBook.EpubFile import io.legado.app.model.localBook.PdfFile -import io.legado.app.utils.BitmapCache import io.legado.app.utils.BitmapUtils import io.legado.app.utils.FileUtils import io.legado.app.utils.SvgUtils @@ -70,8 +66,7 @@ object ImageProvider { ) { //错误图片不能释放,占位用,防止一直重复获取图片 if (oldBitmap != errorBitmap) { - BitmapCache.add(oldBitmap) - //oldBitmap.recycle() + oldBitmap.recycle() //putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath") //putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes") } @@ -160,9 +155,8 @@ object ImageProvider { book: Book, src: String, width: Int, - height: Int? = null, - block: (() -> Unit)? = null - ): Bitmap? { + height: Int? = null + ): Bitmap { //src为空白时 可能被净化替换掉了 或者规则失效 if (book.getUseReplaceRule() && src.isBlank()) { book.setUseReplaceRule(false) @@ -174,32 +168,6 @@ object ImageProvider { //bitmapLruCache的key同一改成缓存文件的路径 val cacheBitmap = getNotRecycled(vFile.absolutePath) if (cacheBitmap != null) return cacheBitmap - if (height != null && AppConfig.asyncLoadImage && ReadBook.pageAnim() == PageAnim.scrollPageAnim) { - if (asyncLoadingImages.contains(vFile.absolutePath)) { - return null - } - asyncLoadingImages.add(vFile.absolutePath) - Coroutine.async { - BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) - ?: SvgUtils.createBitmap(vFile.absolutePath, width, height) - ?: throw NoStackTraceException(appCtx.getString(R.string.error_decode_bitmap)) - }.onSuccess { - bitmapLruCache.run { - if (maxSize() < maxCacheSize && size() + it.byteCount > maxSize() && putCount() - evictionCount() < 5) { - resize(min(maxCacheSize, maxSize() + it.byteCount)) - AppLog.put("图片缓存太小,自动扩增至${(maxSize() / M)}MB。") - } - } - bitmapLruCache.put(vFile.absolutePath, it) - }.onError { - //错误图片占位,防止重复获取 - bitmapLruCache.put(vFile.absolutePath, errorBitmap) - }.onFinally { - asyncLoadingImages.remove(vFile.absolutePath) - block?.invoke() - } - return null - } return kotlin.runCatching { val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) ?: SvgUtils.createBitmap(vFile.absolutePath, width, height) @@ -214,7 +182,6 @@ object ImageProvider { fun clear() { bitmapLruCache.evictAll() - BitmapCache.clear() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 28623edb5..55573449e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -964,9 +964,7 @@ class ReadBookActivity : BaseReadBookActivity(), */ override fun pageChanged() { pageChanged = true - runOnUiThread { - binding.readView.onPageChange() - } + binding.readView.onPageChange() handler.post { upSeekBarProgress() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt index 39275c8c0..084d8d320 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt @@ -32,10 +32,7 @@ data class ImageColumn( src, (end - start).toInt(), height.toInt() - ) { - textLine.invalidate() - view.invalidate() - } ?: return + ) val rectF = if (textLine.isImage) { RectF(start, 0f, end, height) diff --git a/app/src/main/java/io/legado/app/utils/BitmapCache.kt b/app/src/main/java/io/legado/app/utils/BitmapCache.kt deleted file mode 100644 index 37b831b8b..000000000 --- a/app/src/main/java/io/legado/app/utils/BitmapCache.kt +++ /dev/null @@ -1,104 +0,0 @@ -package io.legado.app.utils - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import java.lang.ref.SoftReference -import java.util.concurrent.ConcurrentHashMap - -object BitmapCache { - - private val reusableBitmaps: MutableSet> = ConcurrentHashMap.newKeySet() - - fun add(bitmap: Bitmap) { - reusableBitmaps.add(SoftReference(bitmap)) - trimSize() - } - - fun clear() { - if (reusableBitmaps.isEmpty()) { - return - } - val iterator = reusableBitmaps.iterator() - while (iterator.hasNext()) { - val item = iterator.next().get() ?: continue - item.recycle() - iterator.remove() - } - } - - private fun trimSize() { - var byteCount = 0 - val iterator = reusableBitmaps.iterator() - while (iterator.hasNext()) { - val item = iterator.next().get() ?: continue - if (byteCount > 128 * 1024 * 1024) { - item.recycle() - iterator.remove() - } else { - byteCount += item.byteCount - } - } - } - - fun addInBitmapOptions(options: BitmapFactory.Options) { - // inBitmap only works with mutable bitmaps, so force the decoder to - // return mutable bitmaps. - options.inMutable = true - - // Try to find a bitmap to use for inBitmap. - getBitmapFromReusableSet(options)?.also { inBitmap -> - // If a suitable bitmap has been found, set it as the value of - // inBitmap. - options.inBitmap = inBitmap - } - } - - - private fun getBitmapFromReusableSet(options: BitmapFactory.Options): Bitmap? { - if (reusableBitmaps.isEmpty()) { - return null - } - val iterator = reusableBitmaps.iterator() - while (iterator.hasNext()) { - val item = iterator.next().get() ?: continue - if (item.isMutable) { - // Check to see it the item can be used for inBitmap. - if (canUseForInBitmap(item, options)) { - // Remove from reusable set so it can't be used again. - iterator.remove() - return item - } - } else { - // Remove from the set if the reference has been cleared. - iterator.remove() - } - } - return null - } - - private fun canUseForInBitmap( - candidate: Bitmap, - targetOptions: BitmapFactory.Options - ): Boolean { - // From Android 4.4 (KitKat) onward we can re-use if the byte size of - // the new bitmap is smaller than the reusable bitmap candidate - // allocation byte count. - val width: Int = targetOptions.outWidth / targetOptions.inSampleSize - val height: Int = targetOptions.outHeight / targetOptions.inSampleSize - val byteCount: Int = width * height * getBytesPerPixel(candidate.config) - return byteCount <= candidate.allocationByteCount - } - - /** - * A helper function to return the byte usage per pixel of a bitmap based on its configuration. - */ - private fun getBytesPerPixel(config: Bitmap.Config): Int { - return when (config) { - Bitmap.Config.ARGB_8888 -> 4 - Bitmap.Config.RGB_565, Bitmap.Config.ARGB_4444 -> 2 - Bitmap.Config.ALPHA_8 -> 1 - else -> 1 - } - } - -} diff --git a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt index e962b5a43..80c19e88f 100644 --- a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt +++ b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt @@ -34,7 +34,6 @@ object BitmapUtils { BitmapFactory.decodeFileDescriptor(fis.fd, null, op) op.inSampleSize = calculateInSampleSize(op, width, height) op.inJustDecodeBounds = false - BitmapCache.addInBitmapOptions(op) BitmapFactory.decodeFileDescriptor(fis.fd, null, op) } } diff --git a/app/src/main/res/xml/pref_config_read.xml b/app/src/main/res/xml/pref_config_read.xml index 045303a6d..b6fa78c39 100644 --- a/app/src/main/res/xml/pref_config_read.xml +++ b/app/src/main/res/xml/pref_config_read.xml @@ -136,13 +136,6 @@ app:iconSpaceReserved="false" app:isBottomBackground="true" /> - - Date: Thu, 15 Feb 2024 15:58:16 +0800 Subject: [PATCH 15/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/model/ImageProvider.kt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/ImageProvider.kt b/app/src/main/java/io/legado/app/model/ImageProvider.kt index 81a256327..c0a99fb7f 100644 --- a/app/src/main/java/io/legado/app/model/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/model/ImageProvider.kt @@ -2,7 +2,6 @@ package io.legado.app.model import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.os.Build import android.util.Size import androidx.collection.LruCache import io.legado.app.R @@ -25,8 +24,6 @@ import kotlinx.coroutines.withContext import splitties.init.appCtx import java.io.File import java.io.FileOutputStream -import java.util.concurrent.ConcurrentHashMap -import kotlin.math.min object ImageProvider { @@ -46,12 +43,6 @@ object ImageProvider { } return AppConfig.bitmapCacheSize * M } - private val maxCacheSize = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { - min(128 * M, Runtime.getRuntime().maxMemory().toInt()) - } else { - 256 * M - } - private val asyncLoadingImages = ConcurrentHashMap.newKeySet() val bitmapLruCache = object : LruCache(cacheSize) { override fun sizeOf(filePath: String, bitmap: Bitmap): Int { From 61049ddffa0c121a3bca9b53eba77861d2acd8df Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 16:24:49 +0800 Subject: [PATCH 16/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/page/ContentTextView.kt | 20 +++++++++++++++---- .../ui/book/read/page/entities/TextPage.kt | 7 ++++--- .../java/io/legado/app/utils/PictureMirror.kt | 14 ++----------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index c54f8521b..cd6eff0fd 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -172,11 +172,23 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at private fun preRenderPage() { val view = this + var invalidate = false pageFactory.run { - prevPage.preRender(view) - curPage.preRender(view) - nextPage.preRender(view) - nextPlusPage.preRender(view) + hasPrev() && prevPage.preRender(view) + if (curPage.preRender(view)) { + invalidate = true + } + if (hasNext() && nextPage.preRender(view) && callBack.isScroll) { + invalidate = true + } + if (hasNextPlus() && nextPlusPage.preRender(view) && callBack.isScroll + && relativeOffset(2) < ChapterProvider.visibleHeight + ) { + invalidate = true + } + if (invalidate) { + postInvalidate() + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 95d06d8a9..ab9fc3f71 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -264,7 +264,7 @@ data class TextPage( } fun draw(view: ContentTextView, canvas: Canvas?) { - pictureMirror.drawLocked(canvas, view.width, height.toInt(), view) { + pictureMirror.drawLocked(canvas, view.width, height.toInt()) { drawPage(view, this) } } @@ -278,9 +278,10 @@ data class TextPage( } } - fun preRender(view: ContentTextView) { - if (!pictureMirror.isDirty) return + fun preRender(view: ContentTextView): Boolean { + if (!pictureMirror.isDirty) return false draw(view, null) + return true } fun isDirty(): Boolean { diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index 2b03eab20..a44d34106 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -3,41 +3,31 @@ package io.legado.app.utils import android.graphics.Canvas import android.graphics.Picture import android.os.Build -import android.view.View import androidx.core.graphics.record import java.util.concurrent.locks.ReentrantLock class PictureMirror { var picture: Picture? = null + @Volatile var isDirty = true val lock = ReentrantLock() - @Volatile - var scheduleInvalidateView: View? = null inline fun drawLocked( canvas: Canvas?, width: Int, height: Int, - view: View? = null, block: Canvas.() -> Unit ) { if (atLeastApi23) { if (picture == null) picture = Picture() val picture = picture!! if (isDirty) { - if (!lock.tryLock()) { - if (canvas != null && view != null) { - scheduleInvalidateView = view - } - return - } + if (!lock.tryLock()) return try { picture.record(width, height, block) isDirty = false - scheduleInvalidateView?.postInvalidate() - scheduleInvalidateView = null } finally { lock.unlock() } From fa95dd1e3c8de41f9298a70e11ff45cf138988cf Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 16:39:56 +0800 Subject: [PATCH 17/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/utils/PictureMirror.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index a44d34106..b01271c5f 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -12,7 +12,7 @@ class PictureMirror { @Volatile var isDirty = true - val lock = ReentrantLock() + var lock: ReentrantLock? = null inline fun drawLocked( canvas: Canvas?, @@ -21,8 +21,22 @@ class PictureMirror { block: Canvas.() -> Unit ) { if (atLeastApi23) { - if (picture == null) picture = Picture() + if (picture == null) { + synchronized(this) { + if (picture == null) { + picture = Picture() + } + } + } + if (lock == null) { + synchronized(this) { + if (lock == null) { + lock = ReentrantLock() + } + } + } val picture = picture!! + val lock = lock!! if (isDirty) { if (!lock.tryLock()) return try { From 51cb0dc796f21abd1d1baa2059206f5a67b7e030 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 16:42:18 +0800 Subject: [PATCH 18/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/utils/PictureMirror.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index b01271c5f..44e9c01f2 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -7,13 +7,15 @@ import androidx.core.graphics.record import java.util.concurrent.locks.ReentrantLock class PictureMirror { - + @Volatile var picture: Picture? = null @Volatile - var isDirty = true var lock: ReentrantLock? = null + @Volatile + var isDirty = true + inline fun drawLocked( canvas: Canvas?, width: Int, From 7bf089a53dfb2bc49ee454d6a0bded23de27b9a9 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 15 Feb 2024 17:28:10 +0800 Subject: [PATCH 19/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt b/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt index 63a95e072..5004f1cd0 100644 --- a/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt @@ -30,6 +30,7 @@ class DetailSeekBar @JvmOverloads constructor( get() = binding.seekBar.progress set(value) { binding.seekBar.progress = value + upValue() } var max: Int get() = binding.seekBar.max From 7201f923736af25f5a0137fc60e524fffc81a61d Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Fri, 16 Feb 2024 11:44:51 +0800 Subject: [PATCH 20/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/page/ContentTextView.kt | 19 +++++++----------- .../ui/book/read/page/entities/TextLine.kt | 2 +- .../ui/book/read/page/entities/TextPage.kt | 20 +++++++++++++------ .../read/page/provider/ChapterProvider.kt | 1 + .../read/page/provider/TextPageFactory.kt | 13 ++++++------ .../java/io/legado/app/utils/PictureMirror.kt | 6 +----- 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index cd6eff0fd..f79ff5f92 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -6,7 +6,6 @@ import android.graphics.Paint import android.util.AttributeSet import android.view.MotionEvent import android.view.View -import androidx.core.graphics.withTranslation import io.legado.app.R import io.legado.app.data.entities.Bookmark import io.legado.app.help.config.AppConfig @@ -23,6 +22,7 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.widget.dialog.PhotoDialog +import io.legado.app.utils.PictureMirror import io.legado.app.utils.activity import io.legado.app.utils.getCompatColor import io.legado.app.utils.showDialogFragment @@ -102,25 +102,18 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at */ private fun drawPage(canvas: Canvas) { var relativeOffset = relativeOffset(0) - val view = this - canvas.withTranslation(0f, relativeOffset) { - textPage.draw(view, this) - } + textPage.draw(this, canvas, relativeOffset) if (!callBack.isScroll) return //滚动翻页 if (!pageFactory.hasNext()) return val textPage1 = relativePage(1) relativeOffset = relativeOffset(1) - canvas.withTranslation(0f, relativeOffset) { - textPage1.draw(view, this) - } + textPage1.draw(this, canvas, relativeOffset) if (!pageFactory.hasNextPlus()) return relativeOffset = relativeOffset(2) if (relativeOffset < ChapterProvider.visibleHeight) { val textPage2 = relativePage(2) - canvas.withTranslation(0f, relativeOffset) { - textPage2.draw(view, this) - } + textPage2.draw(this, canvas, relativeOffset) } } @@ -167,7 +160,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } fun submitPreRenderTask() { - renderThread.submit(renderRunnable) + if (PictureMirror.atLeastApi23) { + renderThread.submit(renderRunnable) + } } private fun preRenderPage() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 2683ed97a..0f89a6e32 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -121,7 +121,7 @@ data class TextLine( return visible } - fun draw(view: ContentTextView, canvas: Canvas?) { + fun draw(view: ContentTextView, canvas: Canvas) { pictureMirror.draw(canvas, view.width, height.toInt()) { drawTextLine(view, this) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index ab9fc3f71..4926007b6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -45,6 +45,7 @@ data class TextPage( var isMsgPage: Boolean = false var pictureMirror: PictureMirror = PictureMirror() var doublePage = false + var paddingTop = 0 val paragraphs by lazy { paragraphsInternal @@ -159,6 +160,7 @@ data class TextPage( addLine(textLine) } height = ChapterProvider.visibleHeight.toFloat() + invalidate() } return this } @@ -180,7 +182,8 @@ data class TextPage( fun upPageAloudSpan(aloudSpanStart: Int) { removePageAloudSpan() var lineStart = 0 - for ((index, textLine) in textLines.withIndex()) { + for (index in textLines.indices) { + val textLine = textLines[index] val lineLength = textLine.text.length + if (textLine.isParagraphEnd) 1 else 0 if (aloudSpanStart > lineStart && aloudSpanStart < lineStart + lineLength) { for (i in index - 1 downTo 0) { @@ -263,16 +266,19 @@ data class TextPage( return null } - fun draw(view: ContentTextView, canvas: Canvas?) { - pictureMirror.drawLocked(canvas, view.width, height.toInt()) { - drawPage(view, this) + fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { + val height = height.toInt() + canvas.withTranslation(0f, relativeOffset + paddingTop) { + pictureMirror.drawLocked(canvas, view.width, height) { + drawPage(view, this) + } } } private fun drawPage(view: ContentTextView, canvas: Canvas) { for (i in lines.indices) { val line = lines[i] - canvas.withTranslation(0f, line.lineTop) { + canvas.withTranslation(0f, line.lineTop - paddingTop) { line.draw(view, this) } } @@ -280,7 +286,9 @@ data class TextPage( fun preRender(view: ContentTextView): Boolean { if (!pictureMirror.isDirty) return false - draw(view, null) + pictureMirror.drawLocked(null, view.width, height.toInt()) { + drawPage(view, this) + } return true } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index c38d3dc9c..368528f8d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -290,6 +290,7 @@ object ChapterProvider { item.chapterSize = chapterSize item.title = displayTitle item.doublePage = doublePage + item.paddingTop = paddingTop item.upLinesPosition() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt index 9ff5f77ee..f69b022b1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt @@ -12,7 +12,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } override fun hasNext(): Boolean = with(dataSource) { - return hasNextChapter() || currentChapter?.isLastIndex(pageIndex) != true + return hasNextChapter() || (currentChapter != null && currentChapter?.isLastIndex(pageIndex) != true) } override fun hasNextPlus(): Boolean = with(dataSource) { @@ -85,14 +85,12 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource return@with TextPage(text = it).format() } currentChapter?.let { + val pageIndex = pageIndex if (pageIndex < it.pageSize - 1) { return@with it.getPage(pageIndex + 1)?.removePageAloudSpan() ?: TextPage(title = it.title).format() } } - if (!hasNextChapter()) { - return@with TextPage(text = "") - } nextChapter?.let { return@with it.getPage(0)?.removePageAloudSpan() ?: TextPage(title = it.title).format() @@ -105,8 +103,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource ReadBook.msg?.let { return@with TextPage(text = it).format() } - if (pageIndex > 0) { - currentChapter?.let { + currentChapter?.let { + val pageIndex = pageIndex + if (pageIndex > 0) { return@with it.getPage(pageIndex - 1)?.removePageAloudSpan() ?: TextPage(title = it.title).format() } @@ -121,6 +120,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource override val nextPlusPage: TextPage get() = with(dataSource) { currentChapter?.let { + val pageIndex = pageIndex if (pageIndex < it.pageSize - 2) { return@with it.getPage(pageIndex + 2)?.removePageAloudSpan() ?: TextPage(title = it.title).format() @@ -133,7 +133,6 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource return@with nc.getPage(1)?.removePageAloudSpan() ?: TextPage(text = "继续滑动以加载下一章…").format() } - } return TextPage().format() } diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt index 44e9c01f2..21aebec25 100644 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ b/app/src/main/java/io/legado/app/utils/PictureMirror.kt @@ -23,15 +23,11 @@ class PictureMirror { block: Canvas.() -> Unit ) { if (atLeastApi23) { - if (picture == null) { + if (picture == null || lock == null) { synchronized(this) { if (picture == null) { picture = Picture() } - } - } - if (lock == null) { - synchronized(this) { if (lock == null) { lock = ReentrantLock() } From 96588f60c1c8673a5158d0d16cc6eb651a48e7cb Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Fri, 16 Feb 2024 15:52:21 +0800 Subject: [PATCH 21/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 9 ++++++ .../io/legado/app/help/ExecutorService.kt | 6 ++++ .../legado/app/ui/book/read/page/PageView.kt | 30 +++++-------------- .../java/io/legado/app/ui/widget/TitleBar.kt | 10 +++++-- .../legado/app/ui/widget/dialog/TextDialog.kt | 4 ++- .../io/legado/app/utils/ViewExtensions.kt | 21 +++++++++++++ 6 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/io/legado/app/help/ExecutorService.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5163b16c2..a720ba630 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -473,6 +473,15 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + + binding.textView.setHtml(content) - else -> binding.textView.text = content + else -> binding.textView.setTextAsync(content) } time = it.getLong("time", 0L) } diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index 03096add4..9dc94f8bc 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -25,13 +25,17 @@ import android.widget.TextView import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.menu.MenuPopupHelper +import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.PopupMenu import androidx.core.graphics.record import androidx.core.graphics.withTranslation +import androidx.core.text.PrecomputedTextCompat import androidx.core.view.get +import androidx.core.widget.TextViewCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewpager.widget.ViewPager import io.legado.app.help.config.AppConfig +import io.legado.app.help.globalExecutor import io.legado.app.lib.theme.TintHelper import splitties.systemservices.inputMethodManager import java.lang.reflect.Field @@ -216,6 +220,23 @@ fun TextView.setHtml(html: String) { } } +fun AppCompatTextView.setTextAsync(charSequence: CharSequence) { + globalExecutor.execute { + val precomputedText = PrecomputedTextCompat.create( + charSequence, TextViewCompat.getTextMetricsParams(this), + ) + post { + setPrecomputedText(precomputedText) + } + } +} + +fun TextView.setTextIfNotEqual(charSequence: CharSequence?) { + if (text != charSequence) { + text = charSequence + } +} + @SuppressLint("RestrictedApi") fun PopupMenu.show(x: Int, y: Int) { kotlin.runCatching { From b11af054c5c3e21854066ca1a0ae4a2a45fbadf5 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Fri, 16 Feb 2024 15:58:22 +0800 Subject: [PATCH 22/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/model/ReadBook.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 1294454de..2fee95c8d 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -14,6 +14,7 @@ import io.legado.app.help.book.isLocal import io.legado.app.help.config.AppConfig import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.coroutine.Coroutine +import io.legado.app.help.globalExecutor import io.legado.app.model.localBook.TextFile import io.legado.app.model.webBook.WebBook import io.legado.app.service.BaseReadAloudService @@ -29,8 +30,6 @@ import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.delay import kotlinx.coroutines.launch import splitties.init.appCtx -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors import kotlin.math.min @@ -65,7 +64,7 @@ object ReadBook : CoroutineScope by MainScope() { val downloadFailChapters = hashMapOf() var contentProcessor: ContentProcessor? = null val downloadScope = CoroutineScope(SupervisorJob() + IO) - val executor: ExecutorService = Executors.newSingleThreadExecutor() + val executor = globalExecutor //暂时保存跳转前进度 fun saveCurrentBookProcess() { From e8d3d98238f8dadbe8c7896a232fb29209950e2a Mon Sep 17 00:00:00 2001 From: Xwite <82232510+Xwite@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:14:07 +0800 Subject: [PATCH 23/31] Update config.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加简繁转化问题反馈链接 --- .github/ISSUE_TEMPLATE/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 76602f5b2..ee48d87b8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: false contact_links: + - name: 简繁转化 + url: https://github.com/liuyueyi/quick-chinese-transfer/issues/new + about: 简繁转化问题请优先到quick-chinese-transfer反馈 - name: 讨论 / Discussions url: https://github.com/gedoor/legado/discussions about: Please ask and answer questions here. From f93084f3f155e1f407223793871345b0636f7e84 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Sun, 18 Feb 2024 23:21:19 +0800 Subject: [PATCH 24/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/help/config/ReadBookConfig.kt | 1 - .../main/java/io/legado/app/model/ReadBook.kt | 4 +- .../app/ui/book/read/ReadBookActivity.kt | 59 +++++-------- .../app/ui/book/read/config/BgAdapter.kt | 2 +- .../ui/book/read/config/BgTextConfigDialog.kt | 39 ++++++-- .../ui/book/read/config/MoreConfigDialog.kt | 18 +++- .../book/read/config/PaddingConfigDialog.kt | 28 +++--- .../ui/book/read/config/ReadStyleDialog.kt | 26 +++--- .../ui/book/read/config/TipConfigDialog.kt | 35 ++++---- .../legado/app/ui/book/read/page/AutoPager.kt | 9 +- .../app/ui/book/read/page/ContentTextView.kt | 50 +++++++---- .../legado/app/ui/book/read/page/PageView.kt | 7 +- .../legado/app/ui/book/read/page/ReadView.kt | 4 +- .../read/page/delegate/CoverPageDelegate.kt | 71 ++++----------- .../page/delegate/HorizontalPageDelegate.kt | 27 +++--- .../book/read/page/delegate/PageDelegate.kt | 11 ++- .../read/page/delegate/ScrollPageDelegate.kt | 12 ++- .../page/delegate/SimulationPageDelegate.kt | 44 +++++++++- .../read/page/delegate/SlidePageDelegate.kt | 73 +++------------ .../ui/book/read/page/entities/TextLine.kt | 13 +-- .../ui/book/read/page/entities/TextPage.kt | 29 +++--- .../read/page/entities/column/TextColumn.kt | 14 +-- .../read/page/provider/ChapterProvider.kt | 37 +------- .../ui/book/read/page/provider/TextMeasure.kt | 11 +-- .../io/legado/app/ui/widget/BatteryView.kt | 62 +++++++------ .../java/io/legado/app/utils/ChineseUtils.kt | 27 +++--- .../java/io/legado/app/utils/PictureMirror.kt | 88 ------------------- .../io/legado/app/utils/ViewExtensions.kt | 12 +++ .../canvasrecorder/BaseCanvasRecorder.kt | 36 ++++++++ .../utils/canvasrecorder/CanvasRecorder.kt | 27 ++++++ .../canvasrecorder/CanvasRecorderApi23Impl.kt | 39 ++++++++ .../canvasrecorder/CanvasRecorderApi29Impl.kt | 62 +++++++++++++ .../CanvasRecorderExtensions.kt | 35 ++++++++ .../canvasrecorder/CanvasRecorderFactory.kt | 23 +++++ .../canvasrecorder/CanvasRecorderImpl.kt | 59 +++++++++++++ .../canvasrecorder/CanvasRecorderLocked.kt | 52 +++++++++++ 36 files changed, 683 insertions(+), 463 deletions(-) delete mode 100644 app/src/main/java/io/legado/app/utils/PictureMirror.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt create mode 100644 app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index db5ab007e..eb38c7e78 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt @@ -567,7 +567,6 @@ object ReadBookConfig { textColorInt = color } } - ChapterProvider.upStyle() } fun curTextColor(): Int { diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 2fee95c8d..c5062cfef 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -274,10 +274,10 @@ object ReadBook : CoroutineScope by MainScope() { if (textChapter != null) { val pageIndex = durPageIndex if (index > pageIndex) { - textChapter.getPage(index - 2)?.recyclePictures() + textChapter.getPage(index - 2)?.recycleRecorders() } if (index < pageIndex) { - textChapter.getPage(index + 3)?.recyclePictures() + textChapter.getPage(index + 3)?.recycleRecorders() } } durChapterPos = curTextChapter?.getReadLength(index) ?: index diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 55573449e..6a12f28a4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.res.Configuration import android.net.Uri import android.os.Bundle +import android.os.Looper import android.view.Gravity import android.view.InputDevice import android.view.KeyEvent @@ -76,6 +77,7 @@ import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_C import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection +import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.searchContent.SearchContentActivity import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.source.edit.BookSourceEditActivity @@ -116,7 +118,6 @@ import io.legado.app.utils.toastOnUi import io.legado.app.utils.visible import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job -import kotlinx.coroutines.asExecutor import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -217,7 +218,6 @@ class ReadBookActivity : BaseReadBookActivity(), private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } } private var bookChanged = false private var pageChanged = false - private var reloadContent = false private val handler by lazy { buildMainHandler() } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } private val executor = ReadBook.executor @@ -264,23 +264,9 @@ class ReadBookActivity : BaseReadBookActivity(), override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - viewModel.initData(intent) { - initDataSuccess() - } - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - viewModel.initData(intent ?: return) { - initDataSuccess() - } - } - - private fun initDataSuccess() { - upMenu() - if (reloadContent) { - reloadContent = false - ReadBook.loadContent(resetPageOffset = false) + Looper.myQueue().addIdleHandler { + viewModel.initData(intent) { upMenu() } + false } } @@ -1312,24 +1298,24 @@ class ReadBookActivity : BaseReadBookActivity(), when (dialogId) { TEXT_COLOR -> { setCurTextColor(color) - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(2, 6)) } BG_COLOR -> { setCurBg(0, "#${color.hexString}") - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(1)) } TIP_COLOR -> { ReadTipConfig.tipColor = color postEvent(EventBus.TIP_COLOR, "") - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } TIP_DIVIDER_COLOR -> { ReadTipConfig.tipDividerColor = color postEvent(EventBus.TIP_COLOR, "") - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } } @@ -1493,22 +1479,21 @@ class ReadBookActivity : BaseReadBookActivity(), ReadBook.readAloud(!BaseReadAloudService.pause) } } - observeEvent(EventBus.UP_CONFIG) { - upSystemUiVisibility() - readView.upPageSlopSquare() - readView.upBg() - readView.upStyle() - readView.upBgAlpha() - if (it) { // 更新内容排版布局 - if (isInitFinish) { - ReadBook.loadContent(resetPageOffset = false) - } else { - reloadContent = true + observeEvent>(EventBus.UP_CONFIG) { + it.forEach { value -> + when (value) { + 0 -> upSystemUiVisibility() + 1 -> readView.upBg() + 2 -> readView.upStyle() + 3 -> readView.upBgAlpha() + 4 -> readView.upPageSlopSquare() + 5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false) + 6 -> readView.upContent(resetPageOffset = false) + 8 -> ChapterProvider.upStyle() + 9 -> binding.readView.invalidateTextPage() + 10 -> ChapterProvider.upLayout() } - } else { - readView.upContent(resetPageOffset = false) } - binding.readMenu.reset() } observeEvent(EventBus.ALOUD_STATE) { if (it == Status.STOP || it == Status.PAUSE) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt index 1dfb26983..1d3d41d71 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt @@ -41,7 +41,7 @@ class BgAdapter(context: Context, val textColor: Int) : this.setOnClickListener { getItemByLayoutPosition(holder.layoutPosition)?.let { ReadBookConfig.durConfig.setCurBg(1, it) - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(1)) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 2f16f4628..6ecf6c849 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -33,11 +33,32 @@ import io.legado.app.lib.theme.getSecondaryTextColor import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.file.HandleFileContract import io.legado.app.ui.widget.seekbar.SeekBarChangeListener -import io.legado.app.utils.* +import io.legado.app.utils.ColorUtils +import io.legado.app.utils.FileUtils +import io.legado.app.utils.GSON +import io.legado.app.utils.MD5Utils +import io.legado.app.utils.SelectImageContract import io.legado.app.utils.compress.ZipUtils +import io.legado.app.utils.createFileReplace +import io.legado.app.utils.createFolderReplace +import io.legado.app.utils.externalCache +import io.legado.app.utils.externalFiles +import io.legado.app.utils.getFile +import io.legado.app.utils.inputStream +import io.legado.app.utils.isContentScheme +import io.legado.app.utils.launch +import io.legado.app.utils.longToast +import io.legado.app.utils.openOutputStream +import io.legado.app.utils.outputStream +import io.legado.app.utils.parseToUri +import io.legado.app.utils.postEvent +import io.legado.app.utils.printOnDebug +import io.legado.app.utils.readBytes +import io.legado.app.utils.readUri +import io.legado.app.utils.stackTraceStr +import io.legado.app.utils.toastOnUi import io.legado.app.utils.viewbindingdelegate.viewBinding import splitties.init.appCtx - import java.io.File import java.io.FileOutputStream @@ -168,7 +189,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { if (i >= 0) { ReadBookConfig.durConfig = defaultConfigs[i].copy() initData() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) } } } @@ -178,7 +199,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { } binding.swUnderline.setOnCheckedChangeListener { _, isChecked -> underline = isChecked - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(9)) } binding.tvTextColor.setOnClickListener { ColorPickerDialog.newBuilder() @@ -214,7 +235,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { } binding.ivDelete.setOnClickListener { if (ReadBookConfig.deleteDur()) { - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) dismissAllowingStateLoss() } else { toastOnUi("数量已是最少,不能删除.") @@ -223,11 +244,11 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { binding.sbBgAlpha.setOnSeekBarChangeListener(object : SeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { ReadBookConfig.bgAlpha = progress - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(3)) } override fun onStopTrackingTouch(seekBar: SeekBar) { - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(3)) } }) } @@ -357,7 +378,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { ReadBookConfig.import(byteArray).getOrThrow() }.onSuccess { ReadBookConfig.durConfig = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) toastOnUi("导入成功") }.onError { it.printOnDebug() @@ -378,7 +399,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { inputStream.copyTo(outputStream) } ReadBookConfig.durConfig.setCurBg(2, fileName) - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(1)) }.onFailure { appCtx.toastOnUi(it.localizedMessage) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index a4ed8c938..1ef45ae8b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt @@ -107,39 +107,48 @@ class MoreConfigDialog : DialogFragment() { PreferKey.readBodyToLh -> activity?.recreate() PreferKey.hideStatusBar -> { ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(0)) } + PreferKey.hideNavigationBar -> { ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(0)) } + PreferKey.keepLight -> postEvent(key, true) PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) PreferKey.screenOrientation -> { (activity as? ReadBookActivity)?.setOrientation() } + PreferKey.textFullJustify, PreferKey.textBottomJustify, PreferKey.useZhLayout -> { - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } + PreferKey.showBrightnessView -> { postEvent(PreferKey.showBrightnessView, "") } + PreferKey.expandTextMenu -> { (activity as? ReadBookActivity)?.textActionMenu?.upMenu() } + PreferKey.doublePageHorizontal -> { ChapterProvider.upLayout() ReadBook.loadContent(false) } + PreferKey.showReadTitleAddition, PreferKey.readBarStyleFollowPage -> { postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) } + PreferKey.progressBarBehavior -> { postEvent(EventBus.UP_SEEK_BAR, true) } + PreferKey.noAnimScrollPage -> { ReadBook.callBack?.upPageAnim() } @@ -152,6 +161,7 @@ class MoreConfigDialog : DialogFragment() { "clickRegionalConfig" -> { (activity as? ReadBookActivity)?.showClickRegionalConfig() } + PreferKey.pageTouchSlop -> { NumberPickerDialog(requireContext()) .setTitle(getString(R.string.page_touch_slop_dialog_title)) @@ -160,7 +170,7 @@ class MoreConfigDialog : DialogFragment() { .setValue(AppConfig.pageTouchSlop) .show { AppConfig.pageTouchSlop = it - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(4)) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index d55f28f32..debf0c2a5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -63,61 +63,61 @@ class PaddingConfigDialog : BaseDialogFragment(R.layout.dialog_read_padding) { //正文 dsbPaddingTop.onChanged = { ReadBookConfig.paddingTop = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } dsbPaddingBottom.onChanged = { ReadBookConfig.paddingBottom = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } dsbPaddingLeft.onChanged = { ReadBookConfig.paddingLeft = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } dsbPaddingRight.onChanged = { ReadBookConfig.paddingRight = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } //页眉 dsbHeaderPaddingTop.onChanged = { ReadBookConfig.headerPaddingTop = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbHeaderPaddingBottom.onChanged = { ReadBookConfig.headerPaddingBottom = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbHeaderPaddingLeft.onChanged = { ReadBookConfig.headerPaddingLeft = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbHeaderPaddingRight.onChanged = { ReadBookConfig.headerPaddingRight = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } //页脚 dsbFooterPaddingTop.onChanged = { ReadBookConfig.footerPaddingTop = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbFooterPaddingBottom.onChanged = { ReadBookConfig.footerPaddingBottom = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbFooterPaddingLeft.onChanged = { ReadBookConfig.footerPaddingLeft = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbFooterPaddingRight.onChanged = { ReadBookConfig.footerPaddingRight = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } cbShowTopLine.onCheckedChangeListener = { _, isChecked -> ReadBookConfig.showHeaderLine = isChecked - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } cbShowBottomLine.onCheckedChangeListener = { _, isChecked -> ReadBookConfig.showFooterLine = isChecked - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index 1db0195d3..1201cea16 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -108,10 +108,10 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), private fun initViewEvent() = binding.run { chineseConverter.onChanged { ChineseUtils.unLoad(*TransType.entries.toTypedArray()) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } textFontWeightConverter.onChanged { - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8)) } tvTextFont.setOnClickListener { showDialogFragment() @@ -122,7 +122,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), items = resources.getStringArray(R.array.indent).toList() ) { _, index -> ReadBookConfig.paragraphIndent = " ".repeat(index) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } } tvPadding.setOnClickListener { @@ -141,40 +141,40 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), cbShareLayout.onCheckedChangeListener = { _, isChecked -> ReadBookConfig.shareLayout = isChecked upView() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) } dsbTextSize.onChanged = { ReadBookConfig.textSize = it + 5 - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbTextLetterSpacing.onChanged = { ReadBookConfig.letterSpacing = (it - 50) / 100f - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbLineSize.onChanged = { ReadBookConfig.lineSpacingExtra = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbParagraphSpacing.onChanged = { ReadBookConfig.paragraphSpacing = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } } - private fun changeBg(index: Int) { + private fun changeBgTextConfig(index: Int) { val oldIndex = ReadBookConfig.styleSelect if (index != oldIndex) { ReadBookConfig.styleSelect = index upView() styleAdapter.notifyItemChanged(oldIndex) styleAdapter.notifyItemChanged(index) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) } } private fun showBgTextConfig(index: Int): Boolean { dismissAllowingStateLoss() - changeBg(index) + changeBgTextConfig(index) callBack?.showBgTextConfig() return true } @@ -200,7 +200,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), override fun selectFont(path: String) { if (path != ReadBookConfig.textFont) { ReadBookConfig.textFont = path - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } } @@ -235,7 +235,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), binding.apply { ivStyle.setOnClickListener { if (ivStyle.isInView) { - changeBg(holder.layoutPosition) + changeBgTextConfig(holder.layoutPosition) } } ivStyle.onLongClick(ivStyle.isInView) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt index cca5a2798..6862c7734 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt @@ -11,7 +11,12 @@ import io.legado.app.databinding.DialogTipConfigBinding import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadTipConfig import io.legado.app.lib.dialogs.selector -import io.legado.app.utils.* +import io.legado.app.utils.checkByIndex +import io.legado.app.utils.getIndexById +import io.legado.app.utils.hexString +import io.legado.app.utils.observeEvent +import io.legado.app.utils.postEvent +import io.legado.app.utils.setLayout import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -89,26 +94,26 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { private fun initEvent() = binding.run { rgTitleMode.setOnCheckedChangeListener { _, checkedId -> ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } dsbTitleSize.onChanged = { ReadBookConfig.titleSize = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbTitleTop.onChanged = { ReadBookConfig.titleTopSpacing = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbTitleBottom.onChanged = { ReadBookConfig.titleBottomSpacing = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } llHeaderShow.setOnClickListener { val headerModes = ReadTipConfig.getHeaderModes(requireContext()) context?.selector(items = headerModes.values.toList()) { _, i -> ReadTipConfig.headerMode = headerModes.keys.toList()[i] tvHeaderShow.text = headerModes[ReadTipConfig.headerMode] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterShow.setOnClickListener { @@ -116,7 +121,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { context?.selector(items = footerModes.values.toList()) { _, i -> ReadTipConfig.footerMode = footerModes.keys.toList()[i] tvFooterShow.text = footerModes[ReadTipConfig.footerMode] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llHeaderLeft.setOnClickListener { @@ -125,7 +130,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipHeaderLeft = tipValue tvHeaderLeft.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llHeaderMiddle.setOnClickListener { @@ -134,7 +139,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipHeaderMiddle = tipValue tvHeaderMiddle.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llHeaderRight.setOnClickListener { @@ -143,7 +148,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipHeaderRight = tipValue tvHeaderRight.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterLeft.setOnClickListener { @@ -152,7 +157,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipFooterLeft = tipValue tvFooterLeft.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterMiddle.setOnClickListener { @@ -161,7 +166,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipFooterMiddle = tipValue tvFooterMiddle.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterRight.setOnClickListener { @@ -170,7 +175,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipFooterRight = tipValue tvFooterRight.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llTipColor.setOnClickListener { @@ -179,7 +184,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { 0 -> { ReadTipConfig.tipColor = 0 upTvTipColor() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } 1 -> ColorPickerDialog.newBuilder() .setShowAlphaSlider(false) @@ -195,7 +200,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { 0, 1 -> { ReadTipConfig.tipDividerColor = i - 1 upTvTipDividerColor() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } 2 -> ColorPickerDialog.newBuilder() .setShowAlphaSlider(false) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt index 7d5ebbd0a..5b7b3990b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt @@ -82,7 +82,6 @@ class AutoPager(private val readView: ReadView) { } if (readView.isScroll) { - computeOffset() if (!isPausing) readView.curPage.scroll(-scrollOffset) } else { val bottom = progress @@ -112,13 +111,15 @@ class AutoPager(private val readView: ReadView) { bottom.toFloat(), paint ) - if (!isPausing) readView.invalidate() - computeOffset() + if (!isPausing) readView.postInvalidate() } } - private fun computeOffset() { + fun computeOffset() { + if (!isRunning) { + return + } val currentTime = SystemClock.uptimeMillis() val elapsedTime = currentTime - lastTimeMillis diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index f79ff5f92..3efff6a90 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -22,7 +22,6 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.widget.dialog.PhotoDialog -import io.legado.app.utils.PictureMirror import io.legado.app.utils.activity import io.legado.app.utils.getCompatColor import io.legado.app.utils.showDialogFragment @@ -57,6 +56,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at private val pageDelegate get() = callBack.pageDelegate private var pageOffset = 0 private var autoPager: AutoPager? = null + private var isScroll = false private val renderRunnable by lazy { Runnable { preRenderPage() } } //绘制图片的paint @@ -75,14 +75,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at */ fun setContent(textPage: TextPage) { this.textPage = textPage - imagePaint.isAntiAlias = AppConfig.useAntiAlias - invalidate() + // 非滑动翻页动画需要同步重绘,不然翻页可能会出现闪烁 + if (isScroll) { + postInvalidate() + } else { + invalidate() + } } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - if (!isMainView) return - ChapterProvider.upViewSize(w, h) + ChapterProvider.upViewSize(w, h, isMainView) textPage.format() } @@ -107,16 +110,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at //滚动翻页 if (!pageFactory.hasNext()) return val textPage1 = relativePage(1) - relativeOffset = relativeOffset(1) + relativeOffset += textPage.height textPage1.draw(this, canvas, relativeOffset) if (!pageFactory.hasNextPlus()) return - relativeOffset = relativeOffset(2) + relativeOffset += textPage1.height if (relativeOffset < ChapterProvider.visibleHeight) { val textPage2 = relativePage(2) textPage2.draw(this, canvas, relativeOffset) } } + override fun computeScroll() { + pageDelegate?.computeScroll() + autoPager?.computeOffset() + } + /** * 滚动事件 * pageOffset 向上滚动 减小 向下滚动 增大 @@ -125,7 +133,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at * pageOffset + textPage.height 为 textPage 下方的高度 */ fun scroll(mOffset: Int) { - if (mOffset == 0) return pageOffset += mOffset if (longScreenshot) { scrollY += -mOffset @@ -156,33 +163,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at pageDelegate?.abortAnim() } } - invalidate() + postInvalidate() } - fun submitPreRenderTask() { - if (PictureMirror.atLeastApi23) { - renderThread.submit(renderRunnable) - } + fun submitRenderTask() { + renderThread.submit(renderRunnable) } private fun preRenderPage() { val view = this var invalidate = false pageFactory.run { - hasPrev() && prevPage.preRender(view) - if (curPage.preRender(view)) { + if (hasPrev() && prevPage.render(view)) { invalidate = true } - if (hasNext() && nextPage.preRender(view) && callBack.isScroll) { + if (curPage.render(view)) { invalidate = true } - if (hasNextPlus() && nextPlusPage.preRender(view) && callBack.isScroll + if (hasNext() && nextPage.render(view) && callBack.isScroll) { + invalidate = true + } + if (hasNextPlus() && nextPlusPage.render(view) && callBack.isScroll && relativeOffset(2) < ChapterProvider.visibleHeight ) { invalidate = true } if (invalidate) { postInvalidate() + pageDelegate?.postInvalidate() } } } @@ -254,7 +262,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at touchRough(x, y) { _, textPos, _, _, column -> if (column is TextColumn) { column.selected = true - invalidate() select(textPos) } } @@ -557,7 +564,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } } } - invalidate() + // 由后台线程完成渲染后通知视图重绘 + submitRenderTask() } private fun upSelectedStart(x: Float, y: Float, top: Float) { @@ -681,6 +689,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at this.autoPager = autoPager } + fun setIsScroll(value: Boolean) { + isScroll = value + } + override fun canScrollVertically(direction: Int): Boolean { return callBack.isScroll && pageFactory.hasNext() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index b66e25138..f450cb746 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -321,7 +321,12 @@ class PageView(context: Context) : FrameLayout(context) { } fun submitPreRenderTask() { - binding.contentTextView.submitPreRenderTask() + binding.contentTextView.submitRenderTask() + } + + fun setIsScroll(value: Boolean) { + isScroll = value + binding.contentTextView.setIsScroll(value) } /** diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 0b21995d8..21b175fff 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -148,6 +148,7 @@ class ReadView(context: Context, attrs: AttributeSet) : override fun computeScroll() { pageDelegate?.computeScroll() + autoPager.computeOffset() } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { @@ -510,7 +511,7 @@ class ReadView(context: Context, attrs: AttributeSet) : } else { curPage.setAutoPager(null) } - curPage.isScroll = isScroll + curPage.setIsScroll(isScroll) } /** @@ -634,6 +635,7 @@ class ReadView(context: Context, attrs: AttributeSet) : nextPage.invalidateAll() nextPlusPage.invalidateAll() } + upContent() } fun onScrollAnimStart() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt index f9fb0695c..5e8303312 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt @@ -1,10 +1,7 @@ package io.legado.app.ui.book.read.page.delegate import android.graphics.Canvas -import android.graphics.Matrix -import android.graphics.Picture import android.graphics.drawable.GradientDrawable -import android.os.Build import androidx.core.graphics.withClip import androidx.core.graphics.withTranslation import io.legado.app.ui.book.read.page.ReadView @@ -12,26 +9,14 @@ import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.utils.screenshot class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { - private val bitmapMatrix = Matrix() private val shadowDrawableR: GradientDrawable - private lateinit var curPicture: Picture - private lateinit var prevPicture: Picture - private lateinit var nextPicture: Picture - - private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - init { val shadowColors = intArrayOf(0x66111111, 0x00000000) shadowDrawableR = GradientDrawable( GradientDrawable.Orientation.LEFT_RIGHT, shadowColors ) shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT - if (atLeastApi23) { - curPicture = Picture() - prevPicture = Picture() - nextPicture = Picture() - } } override fun onDraw(canvas: Canvas) { @@ -47,42 +32,21 @@ class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth if (mDirection == PageDirection.PREV) { if (offsetX <= viewWidth) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - canvas.withTranslation(distanceX) { - drawPicture(prevPicture) - } + canvas.withTranslation(distanceX) { + prevRecorder.draw(canvas) } addShadow(distanceX, canvas) } else { - if (!atLeastApi23) { - prevBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } - } else { - canvas.drawPicture(prevPicture) - } + prevRecorder.draw(canvas) } } else if (mDirection == PageDirection.NEXT) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) - nextBitmap?.let { - val width = it.width.toFloat() - val height = it.height.toFloat() - canvas.withClip(width + offsetX, 0f, width, height) { - drawBitmap(it, 0f, 0f, null) - } - } - curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - val width = nextPicture.width.toFloat() - val height = nextPicture.height.toFloat() - canvas.withClip(width + offsetX, 0f, width, height) { - drawPicture(nextPicture) - } - canvas.withTranslation(distanceX - viewWidth) { - drawPicture(curPicture) - } + val width = nextRecorder.width.toFloat() + val height = nextRecorder.height.toFloat() + canvas.withClip(width + offsetX, 0f, width, height) { + nextRecorder.draw(this) + } + canvas.withTranslation(distanceX - viewWidth) { + curRecorder.draw(this) } addShadow(distanceX, canvas) } @@ -90,18 +54,13 @@ class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { override fun setBitmap() { when (mDirection) { - PageDirection.PREV -> if (!atLeastApi23) { - prevBitmap = prevPage.screenshot(prevBitmap, canvas) - } else { - prevPage.screenshot(prevPicture) + PageDirection.PREV -> { + prevPage.screenshot(prevRecorder) } - PageDirection.NEXT -> if (!atLeastApi23) { - nextBitmap = nextPage.screenshot(nextBitmap, canvas) - curBitmap = curPage.screenshot(curBitmap, canvas) - } else { - nextPage.screenshot(nextPicture) - curPage.screenshot(curPicture) + PageDirection.NEXT -> { + nextPage.screenshot(nextRecorder) + curPage.screenshot(curRecorder) } else -> Unit diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt index 9e8e30f2f..f0f91b3ca 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt @@ -1,18 +1,16 @@ package io.legado.app.ui.book.read.page.delegate -import android.graphics.Bitmap -import android.graphics.Canvas import android.view.MotionEvent import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection +import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.screenshot abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readView) { - protected var curBitmap: Bitmap? = null - protected var prevBitmap: Bitmap? = null - protected var nextBitmap: Bitmap? = null - protected var canvas: Canvas = Canvas() + protected val curRecorder = CanvasRecorderFactory.create() + protected val prevRecorder = CanvasRecorderFactory.create() + protected val nextRecorder = CanvasRecorderFactory.create() private val slopSquare get() = readView.pageSlopSquare2 override fun setDirection(direction: PageDirection) { @@ -23,13 +21,13 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie open fun setBitmap() { when (mDirection) { PageDirection.PREV -> { - prevBitmap = prevPage.screenshot(prevBitmap, canvas) - curBitmap = curPage.screenshot(curBitmap, canvas) + prevPage.screenshot(prevRecorder) + curPage.screenshot(curRecorder) } PageDirection.NEXT -> { - nextBitmap = nextPage.screenshot(nextBitmap, canvas) - curBitmap = curPage.screenshot(curBitmap, canvas) + nextPage.screenshot(nextRecorder) + curPage.screenshot(curRecorder) } else -> Unit @@ -140,12 +138,9 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie override fun onDestroy() { super.onDestroy() - prevBitmap?.recycle() - prevBitmap = null - curBitmap?.recycle() - curBitmap = null - nextBitmap?.recycle() - nextBitmap = null + prevRecorder.recycle() + curRecorder.recycle() + nextRecorder.recycle() } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index 31fd7e8db..38e51d6e7 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -96,7 +96,7 @@ abstract class PageDelegate(protected val readView: ReadView) { viewHeight = height } - fun computeScroll() { + open fun computeScroll() { if (scroller.computeScrollOffset()) { readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) } else if (isStarted) { @@ -190,6 +190,15 @@ abstract class PageDelegate(protected val readView: ReadView) { } } + fun postInvalidate() { + if (isRunning && this is HorizontalPageDelegate) { + readView.post { + setBitmap() + readView.invalidate() + } + } + } + open fun onDestroy() { // run on destroy } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index 232aaa925..2d32b1c92 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -79,7 +79,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) { val pointX = event.getX(event.pointerCount - 1) val pointY = event.getY(event.pointerCount - 1) if (isMoved) { - readView.setTouchPoint(pointX, pointY) + readView.setTouchPoint(pointX, pointY, false) } if (!isMoved) { val deltaX = (pointX - startX).toInt() @@ -95,12 +95,22 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) { } } + override fun computeScroll() { + if (scroller.computeScrollOffset()) { + readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat(), false) + } else if (isStarted) { + onAnimStop() + stopScroll() + } + } + override fun onDestroy() { super.onDestroy() mVelocity.recycle() } override fun abortAnim() { + readView.onScrollAnimStop() isStarted = false isMoved = false isRunning = false diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt index 04b7bde03..ebadb8dad 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt @@ -1,13 +1,27 @@ package io.legado.app.ui.book.read.page.delegate -import android.graphics.* +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PointF +import android.graphics.Region import android.graphics.drawable.GradientDrawable import android.os.Build import android.view.MotionEvent import io.legado.app.help.config.ReadBookConfig import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection -import kotlin.math.* +import io.legado.app.utils.screenshot +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.hypot +import kotlin.math.min +import kotlin.math.sin @Suppress("DEPRECATION") class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { @@ -86,6 +100,11 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL } + private var curBitmap: Bitmap? = null + private var prevBitmap: Bitmap? = null + private var nextBitmap: Bitmap? = null + private var canvas: Canvas = Canvas() + init { //设置颜色数组 val color = intArrayOf(0x333333, -0x4fcccccd) @@ -122,6 +141,22 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi mFrontShadowDrawableHBT.gradientType = GradientDrawable.LINEAR_GRADIENT } + override fun setBitmap() { + when (mDirection) { + PageDirection.PREV -> { + prevBitmap = prevPage.screenshot(prevBitmap, canvas) + curBitmap = curPage.screenshot(curBitmap, canvas) + } + + PageDirection.NEXT -> { + nextBitmap = nextPage.screenshot(nextBitmap, canvas) + curBitmap = curPage.screenshot(curBitmap, canvas) + } + + else -> Unit + } + } + override fun setViewSize(width: Int, height: Int) { super.setViewSize(width, height) mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() @@ -133,6 +168,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi MotionEvent.ACTION_DOWN -> { calcCornerXY(event.x, event.y) } + MotionEvent.ACTION_MOVE -> { if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3) || mDirection == PageDirection.PREV @@ -159,10 +195,12 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi } else { calcCornerXY(viewWidth - startX, viewHeight.toFloat()) } + PageDirection.NEXT -> if (viewWidth / 2 > startX) { calcCornerXY(viewWidth - startX, startY) } + else -> Unit } } @@ -216,6 +254,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi drawCurrentPageShadow(canvas) drawCurrentBackArea(canvas, curBitmap) } + PageDirection.PREV -> { calcPoints() drawCurrentPageArea(canvas, prevBitmap) @@ -223,6 +262,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi drawCurrentPageShadow(canvas) drawCurrentBackArea(canvas, prevBitmap) } + else -> return } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt index 103cf3a09..33c6480cb 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt @@ -1,51 +1,12 @@ package io.legado.app.ui.book.read.page.delegate import android.graphics.Canvas -import android.graphics.Matrix -import android.graphics.Picture -import android.os.Build import androidx.core.graphics.withTranslation import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection -import io.legado.app.utils.screenshot class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { - private val bitmapMatrix = Matrix() - - private lateinit var curPicture: Picture - private lateinit var prevPicture: Picture - private lateinit var nextPicture: Picture - - private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - - init { - if (atLeastApi23) { - curPicture = Picture() - prevPicture = Picture() - nextPicture = Picture() - } - } - - override fun setBitmap() { - if (!atLeastApi23) { - return super.setBitmap() - } - when (mDirection) { - PageDirection.PREV -> { - prevPage.screenshot(prevPicture) - curPage.screenshot(curPicture) - } - - PageDirection.NEXT -> { - nextPage.screenshot(nextPicture) - curPage.screenshot(curPicture) - } - - else -> Unit - } - } - override fun onAnimStart(animationSpeed: Int) { val distanceX: Float when (mDirection) { @@ -79,32 +40,18 @@ class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth if (!isRunning) return if (mDirection == PageDirection.PREV) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX + viewWidth, 0.toFloat()) - curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - canvas.withTranslation(distanceX + viewWidth) { - drawPicture(curPicture) - } - canvas.withTranslation(distanceX) { - drawPicture(prevPicture) - } + canvas.withTranslation(distanceX + viewWidth) { + curRecorder.draw(this) + } + canvas.withTranslation(distanceX) { + prevRecorder.draw(this) } } else if (mDirection == PageDirection.NEXT) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - nextBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) - curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - canvas.withTranslation(distanceX) { - drawPicture(nextPicture) - } - canvas.withTranslation(distanceX - viewWidth) { - drawPicture(curPicture) - } + canvas.withTranslation(distanceX) { + nextRecorder.draw(this) + } + canvas.withTranslation(distanceX - viewWidth) { + curRecorder.draw(this) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 0f89a6e32..2adc81ea6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -10,7 +10,8 @@ import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage import io.legado.app.ui.book.read.page.entities.column.BaseColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider -import io.legado.app.utils.PictureMirror +import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory +import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw import io.legado.app.utils.dpToPx /** @@ -39,7 +40,7 @@ data class TextLine( val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize val height: Float inline get() = lineBottom - lineTop - val pictureMirror: PictureMirror = PictureMirror() + val canvasRecorder = CanvasRecorderFactory.create() var isReadAloud: Boolean = false set(value) { if (field != value) { @@ -122,7 +123,7 @@ data class TextLine( } fun draw(view: ContentTextView, canvas: Canvas) { - pictureMirror.draw(canvas, view.width, height.toInt()) { + canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) { drawTextLine(view, this) } } @@ -156,11 +157,11 @@ data class TextLine( } fun invalidateSelf() { - pictureMirror.invalidate() + canvasRecorder.invalidate() } - fun recyclePicture() { - pictureMirror.recycle() + fun recycleRecorder() { + canvasRecorder.recycle() } companion object { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 4926007b6..ef8032b95 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -11,7 +11,8 @@ import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider -import io.legado.app.utils.PictureMirror +import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory +import io.legado.app.utils.canvasrecorder.recordIfNeeded import splitties.init.appCtx import java.text.DecimalFormat import kotlin.math.min @@ -43,7 +44,7 @@ data class TextPage( val charSize: Int get() = text.length.coerceAtLeast(1) val searchResult = hashSetOf() var isMsgPage: Boolean = false - var pictureMirror: PictureMirror = PictureMirror() + var canvasRecorder = CanvasRecorderFactory.create(true) var doublePage = false var paddingTop = 0 @@ -267,11 +268,9 @@ data class TextPage( } fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { - val height = height.toInt() + render(view) canvas.withTranslation(0f, relativeOffset + paddingTop) { - pictureMirror.drawLocked(canvas, view.width, height) { - drawPage(view, this) - } + canvasRecorder.draw(this) } } @@ -284,20 +283,14 @@ data class TextPage( } } - fun preRender(view: ContentTextView): Boolean { - if (!pictureMirror.isDirty) return false - pictureMirror.drawLocked(null, view.width, height.toInt()) { + fun render(view: ContentTextView): Boolean { + return canvasRecorder.recordIfNeeded(view.width, height.toInt()) { drawPage(view, this) } - return true - } - - fun isDirty(): Boolean { - return pictureMirror.isDirty } fun invalidate() { - pictureMirror.invalidate() + canvasRecorder.invalidate() } fun invalidateAll() { @@ -307,10 +300,10 @@ data class TextPage( invalidate() } - fun recyclePictures() { - pictureMirror.recycle() + fun recycleRecorders() { + canvasRecorder.recycle() for (i in lines.indices) { - lines[i].recyclePicture() + lines[i].recycleRecorder() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt index 7096c55d1..de204e42d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt @@ -42,15 +42,15 @@ data class TextColumn( } else { ChapterProvider.contentPaint } - val textColor = if (textLine.isReadAloud || isSearchResult) { - ThemeStore.accentColor + if (textLine.isReadAloud || isSearchResult) { + synchronized(textPaint) { + textPaint.color = ThemeStore.accentColor + canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) + textPaint.color = ReadBookConfig.textColor + } } else { - ReadBookConfig.textColor + canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) } - if (textPaint.color != textColor) { - textPaint.color = textColor - } - canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) if (selected) { canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 368528f8d..3754893ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -690,39 +690,6 @@ object ChapterProvider { exceed(absStartX, textLine, words) } - fun getStringArrayAndTextWidths( - text: String, - textWidths: List, - textPaint: TextPaint - ): Pair, List> { - val charArray = text.toCharArray() - val strList = ArrayList(text.length) - val textWidthList = ArrayList(text.length) - val lastIndex = charArray.lastIndex - var ca: CharArray? = null - for (i in textWidths.indices) { - if (charArray[i].isLowSurrogate()) { - continue - } - val char = if (i + 1 <= lastIndex && charArray[i + 1].isLowSurrogate()) { - if (ca == null) ca = CharArray(2) - System.arraycopy(charArray, i, ca, 0, 2) - String(ca) - } else { - charArray[i].toString() - } - val w = textWidths[i] - if (w == 0f && textWidthList.size > 0) { - textWidthList[textWidthList.lastIndex] = textPaint.measureText(strList.last()) - textWidthList.add(textPaint.measureText(char)) - } else { - textWidthList.add(w) - } - strList.add(char) - } - return strList to textWidthList - } - /** * 添加字符 */ @@ -883,12 +850,12 @@ object ChapterProvider { /** * 更新View尺寸 */ - fun upViewSize(width: Int, height: Int) { + fun upViewSize(width: Int, height: Int, postEvent: Boolean) { if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) { viewWidth = width viewHeight = height upLayout() - postEvent(EventBus.UP_CONFIG, true) + if (postEvent) postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt index 27329769a..4c8e51e9b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt @@ -3,13 +3,11 @@ package io.legado.app.ui.book.read.page.provider import android.text.TextPaint import android.util.SparseArray import androidx.core.util.getOrDefault -import java.util.BitSet import kotlin.math.ceil class TextMeasure(private var paint: TextPaint) { private var chineseCommonWidth = paint.measureText("一") - private val chineseCommonWidthBitSet = BitSet() private val asciiWidths = FloatArray(128) { -1f } private val codePointWidths = SparseArray() @@ -17,7 +15,8 @@ class TextMeasure(private var paint: TextPaint) { if (codePoint < 128) { return asciiWidths[codePoint] } - if (chineseCommonWidthBitSet[codePoint]) { + // 中文 Unicode 范围 U+4E00 - U+9FA5 + if (codePoint in 19968 .. 40869) { return chineseCommonWidth } return codePointWidths.getOrDefault(codePoint, -1f) @@ -33,7 +32,8 @@ class TextMeasure(private var paint: TextPaint) { if (charArray[i].isLowSurrogate()) continue val width = ceil(widths[i]) widthsList.add(width) - if (width == 0f && widthsList.size > 0) { + // 可能需要检查是否不可见字符 + if (width == 0f && widthsList.size > 1) { val lastIndex = widthsList.lastIndex buf[0] = codePoints[lastIndex - 1] widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1)) @@ -46,8 +46,6 @@ class TextMeasure(private var paint: TextPaint) { val width = widthsList[i] if (codePoint < 128) { asciiWidths[codePoint] = width - } else if (width == chineseCommonWidth) { - chineseCommonWidthBitSet.set(codePoint) } else { codePointWidths[codePoint] = width } @@ -138,7 +136,6 @@ class TextMeasure(private var paint: TextPaint) { private fun invalidate() { chineseCommonWidth = paint.measureText("一") - chineseCommonWidthBitSet.clear() codePointWidths.clear() asciiWidths.fill(-1f) } diff --git a/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt b/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt index 3606b6d72..eb1dca7f5 100644 --- a/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt @@ -10,6 +10,8 @@ import android.text.StaticLayout import android.util.AttributeSet import androidx.annotation.ColorInt import androidx.appcompat.widget.AppCompatTextView +import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory +import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw import io.legado.app.utils.dpToPx class BatteryView @JvmOverloads constructor( @@ -22,6 +24,7 @@ class BatteryView @JvmOverloads constructor( private val batteryPaint = Paint() private val outFrame = Rect() private val polar = Rect() + private val canvasRecorder = CanvasRecorderFactory.create() var isBattery = false set(value) { field = value @@ -62,31 +65,40 @@ class BatteryView @JvmOverloads constructor( } override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - if (!isBattery) return - layout.getLineBounds(0, outFrame) - val batteryStart = layout - .getPrimaryHorizontal(text.length - battery.toString().length) - .toInt() + 2.dpToPx() - val batteryEnd = batteryStart + - StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx() - outFrame.set( - batteryStart, - 2.dpToPx(), - batteryEnd, - height - 2.dpToPx() - ) - val dj = (outFrame.bottom - outFrame.top) / 3 - polar.set( - batteryEnd, - outFrame.top + dj, - batteryEnd + 2.dpToPx(), - outFrame.bottom - dj - ) - batteryPaint.style = Paint.Style.STROKE - canvas.drawRect(outFrame, batteryPaint) - batteryPaint.style = Paint.Style.FILL - canvas.drawRect(polar, batteryPaint) + canvasRecorder.recordIfNeededThenDraw(canvas, width, height) { + super.onDraw(this) + if (!isBattery) return@recordIfNeededThenDraw + layout.getLineBounds(0, outFrame) + val batteryStart = layout + .getPrimaryHorizontal(text.length - battery.toString().length) + .toInt() + 2.dpToPx() + val batteryEnd = batteryStart + + StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx() + outFrame.set( + batteryStart, + 2.dpToPx(), + batteryEnd, + height - 2.dpToPx() + ) + val dj = (outFrame.bottom - outFrame.top) / 3 + polar.set( + batteryEnd, + outFrame.top + dj, + batteryEnd + 2.dpToPx(), + outFrame.bottom - dj + ) + batteryPaint.style = Paint.Style.STROKE + drawRect(outFrame, batteryPaint) + batteryPaint.style = Paint.Style.FILL + drawRect(polar, batteryPaint) + } + } + + override fun invalidate() { + super.invalidate() + kotlin.runCatching { + canvasRecorder.invalidate() + } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/ChineseUtils.kt b/app/src/main/java/io/legado/app/utils/ChineseUtils.kt index d7d01bda9..34744e32c 100644 --- a/app/src/main/java/io/legado/app/utils/ChineseUtils.kt +++ b/app/src/main/java/io/legado/app/utils/ChineseUtils.kt @@ -32,26 +32,19 @@ object ChineseUtils { fun fixT2sDict() { val dict = DictionaryContainer.getInstance().getDictionary(TransType.TRADITIONAL_TO_SIMPLE) dict.run { - remove("劈") - remove("脊") - remove("支援") - remove("沈默") - remove("類比") - remove("模擬") - remove("划槳") - remove("列根") - remove("路易斯") - remove("非同步") - remove("出租车") - remove("周杰倫") + remove("劈", "脊") + remove("支援", "沈默", "類比", "模擬", "划槳", "列根", "先進") + remove("路易斯", "非同步", "出租车", "周杰倫") } } - fun BasicDictionary.remove(key: String) { - if (key.length == 1) { - chars.remove(key[0]) - } else { - dict.remove(key) + fun BasicDictionary.remove(vararg keys: String) { + for (key in keys) { + if (key.length == 1) { + chars.remove(key[0]) + } else { + dict.remove(key) + } } } diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt deleted file mode 100644 index 21aebec25..000000000 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ /dev/null @@ -1,88 +0,0 @@ -package io.legado.app.utils - -import android.graphics.Canvas -import android.graphics.Picture -import android.os.Build -import androidx.core.graphics.record -import java.util.concurrent.locks.ReentrantLock - -class PictureMirror { - @Volatile - var picture: Picture? = null - - @Volatile - var lock: ReentrantLock? = null - - @Volatile - var isDirty = true - - inline fun drawLocked( - canvas: Canvas?, - width: Int, - height: Int, - block: Canvas.() -> Unit - ) { - if (atLeastApi23) { - if (picture == null || lock == null) { - synchronized(this) { - if (picture == null) { - picture = Picture() - } - if (lock == null) { - lock = ReentrantLock() - } - } - } - val picture = picture!! - val lock = lock!! - if (isDirty) { - if (!lock.tryLock()) return - try { - picture.record(width, height, block) - isDirty = false - } finally { - lock.unlock() - } - } - canvas?.drawPicture(picture) - } else { - canvas?.block() - } - } - - /** - * 非线程安全,多线程调用可能会崩溃 - */ - inline fun draw( - canvas: Canvas?, - width: Int, - height: Int, - block: Canvas.() -> Unit - ) { - if (atLeastApi23) { - if (picture == null) picture = Picture() - val picture = picture!! - if (isDirty) { - picture.record(width, height, block) - isDirty = false - } - canvas?.drawPicture(picture) - } else { - canvas?.block() - } - } - - fun invalidate() { - isDirty = true - } - - fun recycle() { - picture = null - isDirty = true - } - - companion object { - val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - } - -} diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index 9dc94f8bc..89a8887bf 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -37,6 +37,8 @@ import androidx.viewpager.widget.ViewPager import io.legado.app.help.config.AppConfig import io.legado.app.help.globalExecutor import io.legado.app.lib.theme.TintHelper +import io.legado.app.utils.canvasrecorder.CanvasRecorder +import io.legado.app.utils.canvasrecorder.record import splitties.systemservices.inputMethodManager import java.lang.reflect.Field @@ -180,6 +182,16 @@ fun View.screenshot(picture: Picture) { } } +fun View.screenshot(canvasRecorder: CanvasRecorder) { + if (width > 0 && height > 0) { + canvasRecorder.record(width, height) { + withTranslation(-scrollX.toFloat(), -scrollY.toFloat()) { + draw(this) + } + } + } +} + fun View.setPaddingBottom(bottom: Int) { setPadding(paddingLeft, paddingTop, paddingRight, bottom) } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt new file mode 100644 index 000000000..ee521b0b4 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt @@ -0,0 +1,36 @@ +package io.legado.app.utils.canvasrecorder + +import androidx.annotation.CallSuper + +abstract class BaseCanvasRecorder : CanvasRecorder { + + @JvmField + protected var isDirty = true + + override fun invalidate() { + isDirty = true + } + + @CallSuper + override fun recycle() { + isDirty = true + } + + @CallSuper + override fun endRecording() { + isDirty = false + } + + override fun isDirty(): Boolean { + return isDirty + } + + override fun isLocked(): Boolean { + return false + } + + override fun needRecord(): Boolean { + return isDirty() && !isLocked() + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt new file mode 100644 index 000000000..904d1bf56 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt @@ -0,0 +1,27 @@ +package io.legado.app.utils.canvasrecorder + +import android.graphics.Canvas + +interface CanvasRecorder { + + val width: Int + + val height: Int + + fun beginRecording(width: Int, height: Int): Canvas + + fun endRecording() + + fun draw(canvas: Canvas) + + fun invalidate() + + fun recycle() + + fun isDirty(): Boolean + + fun isLocked(): Boolean + + fun needRecord(): Boolean + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt new file mode 100644 index 000000000..b23024e85 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt @@ -0,0 +1,39 @@ +package io.legado.app.utils.canvasrecorder + +import android.graphics.Canvas +import android.graphics.Picture + +class CanvasRecorderApi23Impl : BaseCanvasRecorder() { + + private var picture: Picture? = null + + override val width get() = picture?.width ?: -1 + override val height get() = picture?.height ?: -1 + + private fun initPicture() { + if (picture == null) { + picture = Picture() + } + } + + override fun beginRecording(width: Int, height: Int): Canvas { + initPicture() + return picture!!.beginRecording(width, height) + } + + override fun endRecording() { + picture!!.endRecording() + super.endRecording() + } + + override fun draw(canvas: Canvas) { + if (picture == null) return + canvas.drawPicture(picture!!) + } + + override fun recycle() { + super.recycle() + picture = null + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt new file mode 100644 index 000000000..98c7a765c --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt @@ -0,0 +1,62 @@ +package io.legado.app.utils.canvasrecorder + +import android.graphics.Canvas +import android.graphics.Picture +import android.graphics.RenderNode +import android.os.Build +import androidx.annotation.RequiresApi + +@RequiresApi(Build.VERSION_CODES.Q) +class CanvasRecorderApi29Impl : BaseCanvasRecorder() { + + private var renderNode: RenderNode? = null + private var picture: Picture? = null + + override val width get() = renderNode?.width ?: -1 + override val height get() = renderNode?.height ?: -1 + + private fun init() { + if (renderNode == null) { + renderNode = RenderNode("CanvasRecorder") + } + if (picture == null) { + picture = Picture() + } + } + + override fun beginRecording(width: Int, height: Int): Canvas { + init() + renderNode!!.setPosition(0, 0, width, height) + return picture!!.beginRecording(width, height) + } + + override fun endRecording() { + picture!!.endRecording() + val rc = renderNode!!.beginRecording() + rc.drawPicture(picture!!) + renderNode!!.endRecording() + super.endRecording() + } + + override fun draw(canvas: Canvas) { + if (renderNode == null || picture == null) return + if (canvas.isHardwareAccelerated) { + if (!renderNode!!.hasDisplayList()) { + val rc = renderNode!!.beginRecording() + rc.drawPicture(picture!!) + renderNode!!.endRecording() + } + canvas.drawRenderNode(renderNode!!) + } else { + canvas.drawPicture(picture!!) + } + } + + override fun recycle() { + super.recycle() + renderNode?.discardDisplayList() + renderNode = null + picture = null + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt new file mode 100644 index 000000000..9e4ddbff7 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt @@ -0,0 +1,35 @@ +package io.legado.app.utils.canvasrecorder + +import android.graphics.Canvas +import androidx.core.graphics.withSave + +inline fun CanvasRecorder.recordIfNeeded( + width: Int, + height: Int, + block: Canvas.() -> Unit +): Boolean { + if (!needRecord()) return false + record(width, height, block) + return true +} + +inline fun CanvasRecorder.record(width: Int, height: Int, block: Canvas.() -> Unit) { + val canvas = beginRecording(width, height) + try { + canvas.withSave { + block() + } + } finally { + endRecording() + } +} + +inline fun CanvasRecorder.recordIfNeededThenDraw( + canvas: Canvas, + width: Int, + height: Int, + block: Canvas.() -> Unit +) { + recordIfNeeded(width, height, block) + draw(canvas) +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt new file mode 100644 index 000000000..038331557 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt @@ -0,0 +1,23 @@ +package io.legado.app.utils.canvasrecorder + +import android.os.Build + +object CanvasRecorderFactory { + + private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + private val atLeastApi29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + + fun create(locked: Boolean = false): CanvasRecorder { + val impl = when { + atLeastApi29 -> CanvasRecorderApi29Impl() + atLeastApi23 -> CanvasRecorderApi23Impl() + else -> CanvasRecorderImpl() + } + return if (locked) { + CanvasRecorderLocked(impl) + } else { + impl + } + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt new file mode 100644 index 000000000..40ad53151 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt @@ -0,0 +1,59 @@ +package io.legado.app.utils.canvasrecorder + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color + +class CanvasRecorderImpl : BaseCanvasRecorder() { + + var bitmap: Bitmap? = null + var canvas: Canvas? = null + + override val width get() = bitmap?.width ?: -1 + override val height get() = bitmap?.height ?: -1 + + private fun init(width: Int, height: Int) { + if (bitmap == null) { + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + } + if (canvas == null) { + canvas = Canvas(bitmap!!) + } + if (bitmap!!.width != width || bitmap!!.height != height) { + if (canReconfigure(width, height)) { + bitmap!!.reconfigure(width, height, Bitmap.Config.ARGB_8888) + } else { + bitmap!!.recycle() + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + } + canvas!!.setBitmap(bitmap!!) + } + } + + private fun canReconfigure(width: Int, height: Int): Boolean { + return bitmap!!.allocationByteCount >= width * height * 4 + } + + override fun beginRecording(width: Int, height: Int): Canvas { + init(width, height) + bitmap!!.eraseColor(Color.TRANSPARENT) + return canvas!! + } + + override fun endRecording() { + bitmap!!.prepareToDraw() + super.endRecording() + } + + override fun draw(canvas: Canvas) { + if (bitmap == null) return + canvas.drawBitmap(bitmap!!, 0f, 0f, null) + } + + override fun recycle() { + super.recycle() + bitmap?.recycle() + bitmap = null + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt new file mode 100644 index 000000000..993491abd --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt @@ -0,0 +1,52 @@ +package io.legado.app.utils.canvasrecorder + +import android.graphics.Canvas +import java.util.concurrent.locks.ReentrantLock + +class CanvasRecorderLocked(private val delegate: CanvasRecorder) : + CanvasRecorder by delegate { + + var lock: ReentrantLock? = ReentrantLock() + + private fun initLock() { + if (lock == null) { + synchronized(this) { + if (lock == null) { + lock = ReentrantLock() + } + } + } + } + + override fun beginRecording(width: Int, height: Int): Canvas { + initLock() + lock!!.lock() + return delegate.beginRecording(width, height) + } + + override fun endRecording() { + delegate.endRecording() + lock!!.unlock() + } + + override fun draw(canvas: Canvas) { + if (lock == null) return + if (!lock!!.tryLock()) return + try { + delegate.draw(canvas) + } finally { + lock!!.unlock() + } + } + + override fun isLocked(): Boolean { + if (lock == null) return false + return lock!!.isLocked + } + + override fun recycle() { + delegate.recycle() + lock = null + } + +} From a7d1f2157a4acb18e8c30ff023ee7cc075821137 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Sun, 18 Feb 2024 23:48:59 +0800 Subject: [PATCH 25/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/ui/book/read/page/ContentTextView.kt | 2 +- .../legado/app/ui/book/read/page/provider/ChapterProvider.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 3efff6a90..50180f174 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -85,7 +85,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - ChapterProvider.upViewSize(w, h, isMainView) + ChapterProvider.upViewSize(w, h) textPage.format() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 3754893ee..2e62fb07c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -850,12 +850,12 @@ object ChapterProvider { /** * 更新View尺寸 */ - fun upViewSize(width: Int, height: Int, postEvent: Boolean) { + fun upViewSize(width: Int, height: Int) { if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) { viewWidth = width viewHeight = height upLayout() - if (postEvent) postEvent(EventBus.UP_CONFIG, arrayOf(2)) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } } From 0a90151ddd5ae0c2e45c8d0126e5411630b114c4 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Mon, 19 Feb 2024 11:13:57 +0800 Subject: [PATCH 26/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../read/page/provider/ChapterProvider.kt | 36 ++++++++++++------- .../ui/book/read/page/provider/ZhLayout.kt | 3 +- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 2e62fb07c..af63741a2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -137,9 +137,6 @@ object ChapterProvider { @JvmStatic var visibleRect = RectF() - private val titleMeasureHelper = TextMeasure(titlePaint) - private val contentMeasureHelper = TextMeasure(contentPaint) - init { upStyle() } @@ -169,7 +166,6 @@ object ChapterProvider { textPages, stringBuilder, titlePaint, - titleMeasureHelper, titlePaintTextHeight, titlePaintFontMetrics, isTitle = true, @@ -207,7 +203,6 @@ object ChapterProvider { textPages, stringBuilder, contentPaint, - contentMeasureHelper, contentPaintTextHeight, contentPaintFontMetrics, srcList = srcList @@ -229,7 +224,6 @@ object ChapterProvider { textPages, stringBuilder, contentPaint, - contentMeasureHelper, contentPaintTextHeight, contentPaintFontMetrics ).let { @@ -261,7 +255,6 @@ object ChapterProvider { textPages, stringBuilder, contentPaint, - contentMeasureHelper, contentPaintTextHeight, contentPaintFontMetrics ).let { @@ -403,7 +396,6 @@ object ChapterProvider { textPages: ArrayList, stringBuilder: StringBuilder, textPaint: TextPaint, - measureHelper: TextMeasure, textHeight: Float, fontMetrics: FontMetrics, isTitle: Boolean = false, @@ -413,7 +405,7 @@ object ChapterProvider { ): Pair { var absStartX = x val layout = if (ReadBookConfig.useZhLayout) { - ZhLayout(text, textPaint, visibleWidth, measureHelper) + ZhLayout(text, textPaint, visibleWidth) } else { StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true) } @@ -471,7 +463,7 @@ object ChapterProvider { val lineStart = layout.getLineStart(lineIndex) val lineEnd = layout.getLineEnd(lineIndex) val lineText = text.substring(lineStart, lineEnd) - val (words, widths) = measureHelper.measureTextSplit(lineText) + val (words, widths) = measureTextSplit(lineText, textPaint) val desiredWidth = widths.fastSum() when { lineIndex == 0 && layout.lineCount > 1 && !isTitle -> { @@ -751,6 +743,28 @@ object ChapterProvider { } } + fun measureTextSplit( + text: String, + paint: TextPaint + ): Pair, ArrayList> { + val length = text.length + val widthsArray = FloatArray(length) + paint.getTextWidths(text, widthsArray) + val clusterCount = widthsArray.count { it > 0f } + val widths = ArrayList(clusterCount) + val stringList = ArrayList(clusterCount) + var i = 0 + while (i < length) { + val clusterBaseIndex = i++ + widths.add(widthsArray[clusterBaseIndex]) + while (i < length && widthsArray[i] == 0f) { + i++ + } + stringList.add(text.substring(clusterBaseIndex, i)) + } + return stringList to widths + } + /** * 更新样式 */ @@ -759,8 +773,6 @@ object ChapterProvider { getPaints(typeface).let { titlePaint = it.first contentPaint = it.second - titleMeasureHelper.setPaint(titlePaint) - contentMeasureHelper.setPaint(contentPaint) // reviewPaint.color = contentPaint.color // reviewPaint.textSize = contentPaint.textSize * 0.45f // reviewPaint.textAlign = Paint.Align.CENTER diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt index 688725afa..894c61fc3 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt @@ -16,7 +16,6 @@ class ZhLayout( text: CharSequence, textPaint: TextPaint, width: Int, - measureHelper: TextMeasure, ) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) { companion object { private val postPanc = hashSetOf( @@ -50,7 +49,7 @@ class ZhLayout( init { var line = 0 - val (words, widths) = measureHelper.measureTextSplit(text as String) + val (words, widths) = ChapterProvider.measureTextSplit(text as String, textPaint) var lineW = 0f var cwPre = 0f var length = 0 From 902ab32ceb5d9b8630d12b3805bf675182994991 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Mon, 19 Feb 2024 11:43:48 +0800 Subject: [PATCH 27/31] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canvasrecorder/CanvasRecorderApi29Impl.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt index 98c7a765c..d6d7ddc6f 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt @@ -24,6 +24,12 @@ class CanvasRecorderApi29Impl : BaseCanvasRecorder() { } } + private fun flushRenderNode() { + val rc = renderNode!!.beginRecording() + rc.drawPicture(picture!!) + renderNode!!.endRecording() + } + override fun beginRecording(width: Int, height: Int): Canvas { init() renderNode!!.setPosition(0, 0, width, height) @@ -32,9 +38,7 @@ class CanvasRecorderApi29Impl : BaseCanvasRecorder() { override fun endRecording() { picture!!.endRecording() - val rc = renderNode!!.beginRecording() - rc.drawPicture(picture!!) - renderNode!!.endRecording() + flushRenderNode() super.endRecording() } @@ -42,9 +46,7 @@ class CanvasRecorderApi29Impl : BaseCanvasRecorder() { if (renderNode == null || picture == null) return if (canvas.isHardwareAccelerated) { if (!renderNode!!.hasDisplayList()) { - val rc = renderNode!!.beginRecording() - rc.drawPicture(picture!!) - renderNode!!.endRecording() + flushRenderNode() } canvas.drawRenderNode(renderNode!!) } else { From 8d86ce4c13238cab26b50da161269d63d1f04a40 Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Mon, 19 Feb 2024 13:29:03 +0800 Subject: [PATCH 28/31] [skip ci] update test.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 构建失败时继续其他步骤 --- .github/workflows/test.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30eb577ef..b677b0ef9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Build With Gradle + continue-on-error: true run: | if [ ${{ env.type }} == 'release' ]; then typeName="原包名" @@ -91,12 +92,15 @@ jobs: echo "开始${{ env.product }}$typeName构建" chmod +x gradlew ./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all - echo "修改文件名" + - name: Rename outputs apks And Move mapping files + run: | + echo "修改APK文件名" mkdir -p ${{ github.workspace }}/apk/ - mkdir -p ${{ github.workspace }}/mapping/ for file in `ls ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk`; do mv "$file" ${{ github.workspace }}/apk/legado_${{ env.product }}_${{ env.VERSIONL }}_$typeName.apk done + echo "移动mapping文件" + mkdir -p ${{ github.workspace }}/mapping/ for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/mapping.txt`; do mv "$file" ${{ github.workspace }}/mapping/mapping.txt done From 5e2dc2972dfc09603de438b03aa516972b539fab Mon Sep 17 00:00:00 2001 From: Xwite <82232510+Xwite@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:30:42 +0800 Subject: [PATCH 29/31] [skip ci]Update test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b677b0ef9..a4485a395 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: echo "开始${{ env.product }}$typeName构建" chmod +x gradlew ./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all - - name: Rename outputs apks And Move mapping files + - name: Rename outputs apks And Move mapping files run: | echo "修改APK文件名" mkdir -p ${{ github.workspace }}/apk/ From d3d65b0416849179df0e11c7859a817103cd708b Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Mon, 19 Feb 2024 13:47:54 +0800 Subject: [PATCH 30/31] =?UTF-8?q?R8=E6=B7=B7=E6=B7=86=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=97=B6=E4=B8=8A=E4=BC=A0missing=5Frules.tx?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4485a395..5a42a7dbf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: echo "开始${{ env.product }}$typeName构建" chmod +x gradlew ./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all - - name: Rename outputs apks And Move mapping files + - name: Rename outputs apks And Move files run: | echo "修改APK文件名" mkdir -p ${{ github.workspace }}/apk/ @@ -104,6 +104,10 @@ jobs: for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/mapping.txt`; do mv "$file" ${{ github.workspace }}/mapping/mapping.txt done + echo "移动missing_rules.txt文件" + for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/missing_rules.txt`; do + mv "$file" ${{ github.workspace }}/mapping/missing_rules.txt + done - name: Upload App To Artifact uses: actions/upload-artifact@v4 with: @@ -116,6 +120,12 @@ jobs: name: legado.${{ env.product }}.${{ env.type }}.mapping path: ${{ github.workspace }}/mapping/mapping.txt + - name: Upload Missing Rules File To Artifact + uses: actions/upload-artifact@v4 + with: + name: legado.${{ env.product }}.${{ env.type }}.mapping.missing_rules + path: ${{ github.workspace }}/mapping/missing_rules.txt + lanzou: needs: [ prepare, build ] if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.lanzou == 'yes' }} From 1094e13367e6a276ba9e7c477469b9adaa1c680b Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Mon, 19 Feb 2024 14:03:56 +0800 Subject: [PATCH 31/31] [skip ci] fix not needed type cast --- .../java/io/legado/app/model/analyzeRule/AnalyzeRule.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index af2eb9cb7..f3162f325 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -197,7 +197,7 @@ class AnalyzeRule( } if (sourceRule.replaceRegex.isNotEmpty() && result is List<*>) { val newList = ArrayList() - for (item in result as List<*>) { + for (item in result) { newList.add(replaceRegex(item.toString(), sourceRule)) } result = newList @@ -210,12 +210,12 @@ class AnalyzeRule( } if (result == null) return null if (result is String) { - result = (result as String).split("\n") + result = result.split("\n") } if (isUrl) { val urlList = ArrayList() if (result is List<*>) { - for (url in result as List<*>) { + for (url in result) { val absoluteURL = NetworkUtils.getAbsoluteURL(redirectUrl, url.toString()) if (absoluteURL.isNotEmpty() && !urlList.contains(absoluteURL)) { urlList.add(absoluteURL)