Просмотр исходного кода

feat: 滚动时上下边距问题

HapeLee 2 месяцев назад
Родитель
Сommit
a75ee67921

+ 12 - 7
app/src/main/java/io/legado/app/feature/reader/core/layout/ReaderPaginator.kt

@@ -228,15 +228,16 @@ object ReaderPaginator {
             is ReaderElement.ParagraphMarker -> element.copy(bounds = element.bounds.offsetY(deltaY))
         }
 
-        fun justifyColumnBottom() {
-            if (!config.textBottomJustify || columnRows.size <= 1) return
+        /** 返回最后一行实际下移量,滚动模式必须把它计入页高。 */
+        fun justifyColumnBottom(): Float {
+            if (!config.textBottomJustify || columnRows.size <= 1) return 0f
             val last = columnRows.last()
-            if (last.standaloneImage) return
+            if (last.standaloneImage) return 0f
             val lastHeight = last.bottom - last.top
             val reservedLineSpacing = config.lineHeightPx * config.lineSpacingMultiplier
-            if (config.contentBottomPx - (last.bottom + reservedLineSpacing) >= lastHeight) return
+            if (config.contentBottomPx - (last.bottom + reservedLineSpacing) >= lastHeight) return 0f
             val surplus = config.contentBottomPx - last.bottom
-            if (surplus <= 0f) return
+            if (surplus <= 0f) return 0f
             val gap = surplus / (columnRows.size - 1)
             columnRows.forEachIndexed { rowIndex, row ->
                 if (rowIndex == 0) return@forEachIndexed
@@ -245,10 +246,11 @@ object ReaderPaginator {
                     elements[elementIndex] = shiftElement(elements[elementIndex], deltaY)
                 }
             }
+            return surplus
         }
 
         fun finishPage() {
-            justifyColumnBottom()
+            val bottomJustifyShift = justifyColumnBottom()
             if (elements.isNotEmpty()) {
                 pages += elements
                 pageTexts += pageText
@@ -259,7 +261,10 @@ object ReaderPaginator {
                     // viewport here: the next scroll page must begin immediately after
                     // that spacing, including when a paragraph boundary is also a page
                     // boundary.
-                    (y - config.paddingTopPx).coerceAtLeast(0f)
+                    // 底部对齐把行整体下移后,页高必须同样增加(对照旧 View
+                    // TextPage.upLinesPosition 的 `height += surplus`),否则滚动模式下
+                    // 下一页会压在当前页最后几行上。
+                    (y + bottomJustifyShift - config.paddingTopPx).coerceAtLeast(0f)
                 } else config.viewportHeightPx.toFloat()
                 elements = mutableListOf()
                 pageText = StringBuilder()

+ 43 - 0
app/src/test/java/io/legado/app/feature/reader/core/layout/ReaderBottomJustifyTest.kt

@@ -3,6 +3,7 @@ package io.legado.app.feature.reader.core.layout
 import io.legado.app.feature.reader.core.model.ReaderElement
 import io.legado.app.feature.reader.core.model.ReaderTextStyle
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
 import org.junit.Test
 
 class ReaderBottomJustifyTest {
@@ -68,4 +69,46 @@ class ReaderBottomJustifyTest {
         assertEquals(listOf(0f, 10f), text.map { it.bounds.top })
         assertEquals(20f, page.elements.filterIsInstance<ReaderElement.Image>().single().bounds.top)
     }
+
+    /**
+     * 旧 View 在 TextPage.upLinesPosition 里对拉伸量做了 `height += surplus`,
+     * ContentTextView 再按该页高堆叠相邻页。滚动模式的 scrollExtentPx 必须保持同一语义,
+     * 否则下一页会压在当前页被下移的最后几行上。
+     */
+    @Test
+    fun continuousScrollExtentCoversJustifiedBottomLikeLegacyPageHeight() {
+        val base = ReaderPaginationConfig(
+            0, "", 40, 110, 0f, 0f, 0f, 0f, 20f, 15f,
+            continuousScroll = true,
+        )
+        // 40px 宽、每字 10px:每行 4 字。7 行正文在 110px 内容区里断成 5 行 + 2 行两页。
+        val text = "字".repeat(28)
+        val ordinary = ReaderPaginator.paginateBlocks(listOf(paragraph(text)), base)
+        val justified = ReaderPaginator.paginateBlocks(
+            listOf(paragraph(text)),
+            base.copy(textBottomJustify = true),
+        )
+
+        // 未拉伸时页高就是排版游标;拉伸后最后一行底边落到内容区底部,页高同步增加 10f。
+        assertEquals(2, ordinary.size)
+        assertEquals(100f, ordinary.first().scrollExtentPx, 0f)
+        assertEquals(2, justified.size)
+        assertEquals(110f, justified.first().scrollExtentPx, 0f)
+
+        // 滚动栈把下一页画在上一页 scrollExtentPx 处(ScrollPageStack),因此页高必须
+        // 覆盖拉伸后的行底,否则两页重叠。
+        val justifiedNextTop = justified.first().scrollExtentPx +
+                justified[1].elements.minOf { it.bounds.top }
+        val justifiedBottom = justified.first().elements.maxOf { it.bounds.bottom }
+        assertTrue(
+            "滚动下一页顶部 $justifiedNextTop 不应早于上一页底部 $justifiedBottom",
+            justifiedNextTop >= justifiedBottom,
+        )
+        val ordinaryNextTop = ordinary.first().scrollExtentPx +
+                ordinary[1].elements.minOf { it.bounds.top }
+        assertTrue(
+            "未拉伸时相邻页也应无缝衔接",
+            ordinaryNextTop >= ordinary.first().elements.maxOf { it.bounds.bottom },
+        )
+    }
 }