From ce4e1b39d2be59411ed18efe5e03a30f21e6061d Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Sun, 16 Nov 2025 17:48:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=90=E5=8F=98=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/constant/PageAnim.kt | 3 +- .../ui/book/read/config/ReadStyleDialog.kt | 3 +- .../legado/app/ui/book/read/page/ReadView.kt | 5 + .../read/page/delegate/FadePageDelegate.kt | 216 ++++++++++++++++++ .../book/read/page/delegate/PageDelegate.kt | 2 +- .../ui/replace/edit/ReplaceEditActivity.kt | 1 - .../res/layout/dialog_read_book_style.xml | 7 + app/src/main/res/values-zh/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 9 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt diff --git a/app/src/main/java/io/legado/app/constant/PageAnim.kt b/app/src/main/java/io/legado/app/constant/PageAnim.kt index ec505b455..68cec4376 100644 --- a/app/src/main/java/io/legado/app/constant/PageAnim.kt +++ b/app/src/main/java/io/legado/app/constant/PageAnim.kt @@ -13,7 +13,8 @@ object PageAnim { const val scrollPageAnim = 3 - const val noAnim = 4 + const val fadePageAnim = 4 + const val noAnim = 5 @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.SOURCE) 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 947722522..16551635c 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 @@ -133,7 +133,8 @@ class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_ R.id.rb_anim1 -> 1 // 滑动动画 R.id.rb_simulation_anim -> 2 // 仿真翻页 R.id.rb_scroll_anim -> 3 // 滚动动画 - R.id.rb_no_anim -> 4 // 无动画 + R.id.rb_fade_anim -> 4 + R.id.rb_no_anim -> 5 // 无动画 else -> 0 } callBack?.upPageAnim() 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 f3ccb1845..e6f204640 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,6 +21,7 @@ import io.legado.app.service.BaseReadAloudService 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.CoverPageDelegate +import io.legado.app.ui.book.read.page.delegate.FadePageDelegate import io.legado.app.ui.book.read.page.delegate.HorizontalPageDelegate import io.legado.app.ui.book.read.page.delegate.NoAnimPageDelegate import io.legado.app.ui.book.read.page.delegate.PageDelegate @@ -537,6 +538,10 @@ class ReadView(context: Context, attrs: AttributeSet) : pageDelegate = ScrollPageDelegate(this) } + PageAnim.fadePageAnim -> if (pageDelegate !is FadePageDelegate) { + pageDelegate = FadePageDelegate(this) + } + else -> if (pageDelegate !is NoAnimPageDelegate) { pageDelegate = NoAnimPageDelegate(this) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt new file mode 100644 index 000000000..f2935db83 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt @@ -0,0 +1,216 @@ +package io.legado.app.ui.book.read.page.delegate + +import android.graphics.Canvas +import android.graphics.Paint +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 + +class FadePageDelegate(readView: ReadView) : PageDelegate(readView) { + + private var curRecorder = CanvasRecorderFactory.create() + private var prevRecorder = CanvasRecorderFactory.create() + private var nextRecorder = CanvasRecorderFactory.create() + private val slopSquare get() = readView.pageSlopSquare2 + + private var fadeProgress = 0f + private val flipThreshold = 0.2f + + override fun setDirection(direction: PageDirection) { + super.setDirection(direction) + fadeProgress = 0f + setBitmap() + } + + private fun setBitmap() { + when (mDirection) { + PageDirection.PREV -> { + prevPage.screenshot(prevRecorder) + curPage.screenshot(curRecorder) + } + PageDirection.NEXT -> { + nextPage.screenshot(nextRecorder) + curPage.screenshot(curRecorder) + } + else -> { + curPage.screenshot(curRecorder) + } + } + } + + private fun upRecorder() { + curRecorder.recycle() + prevRecorder.recycle() + nextRecorder.recycle() + curRecorder = CanvasRecorderFactory.create() + prevRecorder = CanvasRecorderFactory.create() + nextRecorder = CanvasRecorderFactory.create() + } + + override fun onTouch(event: MotionEvent) { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + abortAnim() + readView.setStartPoint(event.x, event.y, false) + } + MotionEvent.ACTION_MOVE -> onScroll(event) + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + if (!isMoved || mDirection == PageDirection.NONE) return + // 超过阈值自动翻页,否则回弹 + val shouldFlip = fadeProgress >= flipThreshold + if (shouldFlip) 1f else 0f + isCancel = !shouldFlip + onAnimStart(readView.defaultAnimationSpeed) + } + } + } + + private fun onScroll(event: MotionEvent) { + val focusX = event.x + val deltaX = focusX - startX + if (!isMoved) { + val distance = deltaX * deltaX + isMoved = distance > slopSquare + if (isMoved) { + animDirectionByDelta(deltaX) + readView.setStartPoint(event.x, event.y, false) + } + } + + if (isMoved && mDirection != PageDirection.NONE) { + fadeProgress = (kotlin.math.abs(deltaX) / viewWidth).coerceIn(0f, 1f) + isRunning = true + readView.setTouchPoint(focusX, startY) + readView.invalidate() + } + } + + private fun animDirectionByDelta(deltaX: Float) { + mDirection = if (deltaX > 0) { + if (!hasPrev()) PageDirection.NONE else PageDirection.PREV + } else { + if (!hasNext()) PageDirection.NONE else PageDirection.NEXT + } + setBitmap() + } + + override fun onDraw(canvas: Canvas) { + curRecorder.draw(canvas) + if (mDirection == PageDirection.NONE) { + // 直接绘制 curPage,不使用 curRecorder + curPage.draw(canvas) + return + } + + val alpha = (fadeProgress * 255).toInt().coerceIn(0, 255) + val paint = Paint().apply { this.alpha = alpha } + + val save = canvas.saveLayer(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat(), paint) + when (mDirection) { + PageDirection.PREV -> prevRecorder.draw(canvas) + PageDirection.NEXT -> nextRecorder.draw(canvas) + else -> {} + } + canvas.restoreToCount(save) + } + + override fun onAnimStart(animationSpeed: Int) { + if (mDirection == PageDirection.NONE) return + // 使用 scroller 做松手后的自动动画 + val start = (fadeProgress * 1000).toInt() + val end = if (isCancel) 0 else 1000 + scroller.startScroll(start, 0, end - start, 0, animationSpeed) + isStarted = true + isRunning = true + readView.invalidate() + } + + override fun computeScroll() { + if (scroller.computeScrollOffset()) { + fadeProgress = scroller.currX / 1000f + readView.invalidate() + } else if (isStarted) { + onAnimStop() + stopScroll() + } + } + + override fun onAnimStop() { + if (!isCancel) { + // 更新当前页显示 + readView.fillPage(mDirection) + // 延迟更新 curRecorder,确保 curPage 已经刷新 + readView.post { + try { + curPage.screenshot(curRecorder) + readView.invalidate() + } catch (_: Exception) { + // 捕获可能的异常,避免闪退 + } + } + } + + // 重置状态 + fadeProgress = 0f + isRunning = false + isMoved = false + + mDirection = PageDirection.NONE + readView.setStartPoint(0f, 0f, false) + readView.setTouchPoint(0f, 0f) + } + + + override fun abortAnim() { + isStarted = false + isMoved = false + isRunning = false + if (!scroller.isFinished) { + scroller.abortAnimation() + if (!isCancel) { + readView.fillPage(mDirection) + curPage.screenshot(curRecorder) // 同步 curRecorder + readView.invalidate() + } + } + } + + override fun nextPageByAnim(animationSpeed: Int) { + if (!hasNext()) return + setDirection(PageDirection.NEXT) + fadeProgress = 0f + isCancel = false + onAnimStart(animationSpeed) + } + + override fun prevPageByAnim(animationSpeed: Int) { + if (!hasPrev()) return + setDirection(PageDirection.PREV) + fadeProgress = 0f + isCancel = false + onAnimStart(animationSpeed) + } + + override fun onDown() { + super.onDown() + fadeProgress = 0f + mDirection = PageDirection.NONE + isMoved = false + isCancel = false + readView.setStartPoint(0f, 0f, false) + } + + override fun setViewSize(width: Int, height: Int) { + super.setViewSize(width, height) + upRecorder() + } + + override fun onDestroy() { + super.onDestroy() + curRecorder.recycle() + prevRecorder.recycle() + nextRecorder.recycle() + } +} 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 374622a1b..fbfc9097f 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 @@ -141,7 +141,7 @@ abstract class PageDelegate(protected val readView: ReadView) { /** * 按下 */ - fun onDown() { + open fun onDown() { //是否移动 isMoved = false //是否存在下一章 diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt index a0836b403..22daf1e7d 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt @@ -21,7 +21,6 @@ import io.legado.app.utils.sendToClip import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat import io.legado.app.utils.showHelp import io.legado.app.utils.viewbindingdelegate.viewBinding -import androidx.core.content.edit import io.legado.app.data.appDb import io.legado.app.lib.dialogs.alert import io.legado.app.utils.toastOnUi diff --git a/app/src/main/res/layout/dialog_read_book_style.xml b/app/src/main/res/layout/dialog_read_book_style.xml index 374d79a54..4db140d18 100644 --- a/app/src/main/res/layout/dialog_read_book_style.xml +++ b/app/src/main/res/layout/dialog_read_book_style.xml @@ -216,6 +216,13 @@ android:checkable="true" android:text="@string/page_anim_scroll" /> + + 菜单标题显示方式 是否确认开始缓存? 计数替换净化 + 渐变 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8842e1b7b..5ff981746 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1348,4 +1348,5 @@ 菜单标题显示方式 是否确认开始缓存? 计数替换净化 + 渐变