HapeLee 11 mesi fa
parent
commit
ce4e1b39d2

+ 2 - 1
app/src/main/java/io/legado/app/constant/PageAnim.kt

@@ -13,7 +13,8 @@ object PageAnim {
 
 
     const val scrollPageAnim = 3
     const val scrollPageAnim = 3
 
 
-    const val noAnim = 4
+    const val fadePageAnim = 4
+    const val noAnim = 5
 
 
     @Target(AnnotationTarget.VALUE_PARAMETER)
     @Target(AnnotationTarget.VALUE_PARAMETER)
     @Retention(AnnotationRetention.SOURCE)
     @Retention(AnnotationRetention.SOURCE)

+ 2 - 1
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_anim1 -> 1  // 滑动动画
                 R.id.rb_simulation_anim -> 2  // 仿真翻页
                 R.id.rb_simulation_anim -> 2  // 仿真翻页
                 R.id.rb_scroll_anim -> 3  // 滚动动画
                 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
                 else -> 0
             }
             }
             callBack?.upPageAnim()
             callBack?.upPageAnim()

+ 5 - 0
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.ContentEditDialog
 import io.legado.app.ui.book.read.page.api.DataSource
 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.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.HorizontalPageDelegate
 import io.legado.app.ui.book.read.page.delegate.NoAnimPageDelegate
 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.PageDelegate
@@ -537,6 +538,10 @@ class ReadView(context: Context, attrs: AttributeSet) :
                 pageDelegate = ScrollPageDelegate(this)
                 pageDelegate = ScrollPageDelegate(this)
             }
             }
 
 
+            PageAnim.fadePageAnim -> if (pageDelegate !is FadePageDelegate) {
+                pageDelegate = FadePageDelegate(this)
+            }
+
             else -> if (pageDelegate !is NoAnimPageDelegate) {
             else -> if (pageDelegate !is NoAnimPageDelegate) {
                 pageDelegate = NoAnimPageDelegate(this)
                 pageDelegate = NoAnimPageDelegate(this)
             }
             }

+ 216 - 0
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()
+    }
+}

+ 1 - 1
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
         isMoved = false
         //是否存在下一章
         //是否存在下一章

+ 0 - 1
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.setOnApplyWindowInsetsListenerCompat
 import io.legado.app.utils.showHelp
 import io.legado.app.utils.showHelp
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
-import androidx.core.content.edit
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi

+ 7 - 0
app/src/main/res/layout/dialog_read_book_style.xml

@@ -216,6 +216,13 @@
                         android:checkable="true"
                         android:checkable="true"
                         android:text="@string/page_anim_scroll" />
                         android:text="@string/page_anim_scroll" />
 
 
+                    <com.google.android.material.chip.Chip
+                        android:id="@+id/rb_fade_anim"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:checkable="true"
+                        android:text="@string/page_anim_fade" />
+
                     <com.google.android.material.chip.Chip
                     <com.google.android.material.chip.Chip
                         android:id="@+id/rb_no_anim"
                         android:id="@+id/rb_no_anim"
                         android:layout_width="wrap_content"
                         android:layout_width="wrap_content"

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -1347,4 +1347,5 @@
     <string name="title_bar_mode">菜单标题显示方式</string>
     <string name="title_bar_mode">菜单标题显示方式</string>
     <string name="sure_cache_book">是否确认开始缓存?</string>
     <string name="sure_cache_book">是否确认开始缓存?</string>
     <string name="replace_purify_badge">计数替换净化</string>
     <string name="replace_purify_badge">计数替换净化</string>
+    <string name="page_anim_fade">渐变</string>
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -1348,4 +1348,5 @@
     <string name="title_bar_mode">菜单标题显示方式</string>
     <string name="title_bar_mode">菜单标题显示方式</string>
     <string name="sure_cache_book">是否确认开始缓存?</string>
     <string name="sure_cache_book">是否确认开始缓存?</string>
     <string name="replace_purify_badge">计数替换净化</string>
     <string name="replace_purify_badge">计数替换净化</string>
+    <string name="page_anim_fade">渐变</string>
 </resources>
 </resources>