Преглед на файлове

[优化] 优化标题分段功能,现在提供完整的次行缩放、距离、段距等调整

HapeLee преди 9 месеца
родител
ревизия
f70efb584c

+ 8 - 0
app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt

@@ -328,6 +328,12 @@ object ReadBookConfig {
             config.titleLineSpacingExtra = value
         }
 
+    var titleLineSpacingSub: Int
+        get() = config.titleLineSpacingSub
+        set(value) {
+            config.titleLineSpacingSub = value
+        }
+
     var paragraphSpacing: Int
         get() = config.paragraphSpacing
         set(value) {
@@ -577,6 +583,7 @@ object ReadBookConfig {
             exportConfig.shadowDy = shareConfig.shadowDy
             exportConfig.titleBold = shareConfig.titleBold
             exportConfig.titleLineSpacingExtra = shareConfig.titleLineSpacingExtra
+            exportConfig.titleLineSpacingSub = shareConfig.titleLineSpacingSub
             exportConfig.titleSegType = shareConfig.titleSegType
             exportConfig.titleSegScaling = shareConfig.titleSegScaling
             exportConfig.titleSegDistance = shareConfig.titleSegDistance
@@ -694,6 +701,7 @@ object ReadBookConfig {
         var titleBottomSpacing: Int = 0,
         var titleBold: Int = 500,//是否粗体字 0:正常, 1:粗体, 2:细体
         var titleLineSpacingExtra: Int = 12,
+        var titleLineSpacingSub: Int = 12,
         var titleSegType: Int = 0,//分段模式
         var titleSegScaling: Float = 1f,//分段缩放,第二段与第一段的字体大小比例
         var titleSegDistance: Int = 4,//分段判断,第几个字符开始分段

+ 5 - 0
app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt

@@ -173,6 +173,7 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
 
         binding.dsbTitleSegScaling.progress = ReadBookConfig.titleSegScaling.toInt() * 10
         binding.dsbTitleLineSpacingExtra.progress = ReadBookConfig.titleLineSpacingExtra
+        binding.dsbTitleLineSpacingSub.progress = ReadBookConfig.titleLineSpacingSub
         binding.dsbTitleSize.progress = ReadBookConfig.titleSize
         binding.dsbTitleTop.progress = ReadBookConfig.titleTopSpacing
         binding.dsbTitleBottom.progress = ReadBookConfig.titleBottomSpacing
@@ -193,6 +194,10 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
             ReadBookConfig.titleLineSpacingExtra = it
             postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
         }
+        binding.dsbTitleLineSpacingSub.onChanged = {
+            ReadBookConfig.titleLineSpacingSub = it
+            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
+        }
         dsbTitleSize.onChanged = {
             ReadBookConfig.titleSize = it
             postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))

+ 1 - 0
app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt

@@ -35,6 +35,7 @@ data class TextLine(
     var chapterPosition: Int = 0,
     var pagePosition: Int = 0,
     val isTitle: Boolean = false,
+    var titleTextSize: Float? = null,
     var isParagraphEnd: Boolean = false,
     var isImage: Boolean = false,
     var isHtml: Boolean = false,

+ 7 - 0
app/src/main/java/io/legado/app/ui/book/read/page/entities/TitleSegment.kt

@@ -0,0 +1,7 @@
+package io.legado.app.ui.book.read.page.entities
+
+data class TitleSegment(
+    val text: String,
+    val isMainTitle: Boolean,
+    val scale: Float
+)

+ 5 - 0
app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt

@@ -52,6 +52,10 @@ data class TextColumn(
         } else {
             ReadBookConfig.textColor
         }
+        val originalSize = textPaint.textSize
+        textLine.titleTextSize?.let {
+            textPaint.textSize = it
+        }
         if (textPaint.color != textColor) {
             textPaint.color = textColor
         }
@@ -66,6 +70,7 @@ data class TextColumn(
         if (selected) {
             canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint)
         }
+        textPaint.textSize = originalSize
     }
 
 }

+ 5 - 1
app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt

@@ -114,7 +114,10 @@ object ChapterProvider {
     var lineSpacingExtra = 0f
         private set
 
-    var titleLineSpacingExtra  = 0f
+    var titleLineSpacingExtra = 0f
+        private set
+
+    var titleLineSpacingSub = 0f
         private set
 
     @JvmStatic
@@ -886,6 +889,7 @@ object ChapterProvider {
         //间距
         lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f
         titleLineSpacingExtra = ReadBookConfig.titleLineSpacingExtra / 10f
+        titleLineSpacingSub = ReadBookConfig.titleLineSpacingSub / 10f
         paragraphSpacing = ReadBookConfig.paragraphSpacing
         titleTopSpacing = ReadBookConfig.titleTopSpacing.dpToPx()
         titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dpToPx()

+ 57 - 72
app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt

@@ -77,6 +77,8 @@ class TextChapterLayout(
 
     private val titleTopSpacing = ChapterProvider.titleTopSpacing
     private val titleBottomSpacing = ChapterProvider.titleBottomSpacing
+    private val titleLineSpacingExtra = ChapterProvider.titleLineSpacingExtra
+    private val titleLineSpacingSub = ChapterProvider.titleLineSpacingSub
     private val lineSpacingExtra = ChapterProvider.lineSpacingExtra
     private val paragraphSpacing = ChapterProvider.paragraphSpacing
 
@@ -209,44 +211,6 @@ class TextChapterLayout(
         }
     }
 
-    private fun parseTitleSegments(
-        rawTitle: String,
-        segType: Int,
-        segDistance: Int,
-        segFlag: String
-    ): List<String> {
-        if (rawTitle.isBlank()) return emptyList()
-
-        return when (segType) {
-            0 -> listOf(rawTitle)
-
-            1 -> {
-                if (segDistance <= 0 || segDistance >= rawTitle.length)
-                    listOf(rawTitle)
-                else
-                    listOf(
-                        rawTitle.take(segDistance),
-                        rawTitle.substring(segDistance)
-                    )
-            }
-
-            2 -> {
-                val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
-                if (flags.isEmpty()) return listOf(rawTitle)
-
-                val pattern = flags.joinToString("|") { Regex.escape(it) }
-                val regex = Regex("(?<=$pattern)")
-                val segments = rawTitle.split(regex)
-                    .map { it.trim() }
-                    .filter { it.isNotEmpty() }
-
-                segments.ifEmpty { listOf(rawTitle) }
-            }
-
-            else -> listOf(rawTitle)
-        }
-    }
-
     /**
      * 获取拆分完的章节数据
      */
@@ -262,45 +226,66 @@ class TextChapterLayout(
         val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true)
 
         if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {
-            val baseTitles = displayTitle.splitNotBlank("\n")
-            baseTitles.forEach { rawTitle ->
-                val titleSegments = parseTitleSegments(
+            val allTitleSegments = displayTitle.splitNotBlank("\n").flatMap { rawTitle ->
+                TitleStyleParser.getSegments(
                     rawTitle,
                     titleSegType,
                     titleSegDistance,
-                    titleSegFlag
+                    titleSegFlag,
+                    titleSegScaling
                 )
-                titleSegments.forEachIndexed { index, segment ->
-                    val srcList = LinkedList<String>()
-                    val reviewImg = bookChapter.reviewImg
-                    var reviewTxt = ""
-                    if (reviewImg != null) {
-                        srcList.add(reviewImg)
-                        reviewTxt = if (reviewImg.contains("TEXT")) {
-                            reviewChar
-                        } else {
-                            srcReplaceChar
-                        }
+            }
+
+            allTitleSegments.forEachIndexed { index, segment ->
+                val currentPaint: TextPaint
+                val currentHeight: Float
+                val currentMetrics: Paint.FontMetrics
+                val lineIndexBefore = pendingTextPage.lines.size
+                if (segment.isMainTitle) {
+                    currentPaint = titlePaint
+                    currentHeight = titlePaintTextHeight
+                    currentMetrics = titlePaintFontMetrics
+                } else {
+                    currentPaint = TextPaint(titlePaint).apply {
+                        textSize = titlePaint.textSize * segment.scale
                     }
-                    val paint = if (index == 0) titlePaint else {
-                        TextPaint(titlePaint).apply {
-                            textSize = titlePaint.textSize * titleSegScaling
-                        }
+                    currentMetrics = currentPaint.fontMetrics
+                    currentHeight = currentMetrics.bottom - currentMetrics.top
+                }
+
+                val srcList = LinkedList<String>()
+                val reviewImg = bookChapter.reviewImg
+                var reviewTxt = ""
+                if (index == allTitleSegments.lastIndex && reviewImg != null) {
+                    srcList.add(reviewImg)
+                    reviewTxt = if (reviewImg.contains("TEXT")) reviewChar else srcReplaceChar
+                }
+
+                setTypeText(
+                    book = book,
+                    text = segment.text + reviewTxt,
+                    textPaint = currentPaint,
+                    textHeight = currentHeight,
+                    fontMetrics = currentMetrics,
+                    imageStyle = imageStyle,
+                    srcList = srcList.ifEmpty { null },
+                    isTitle = true,
+                    emptyContent = contents.isEmpty(),
+                    isVolumeTitle = bookChapter.isVolume
+                )
+
+                if (segment.scale != 1.0f) {
+                    val currentLines = pendingTextPage.lines
+                    for (i in lineIndexBefore until currentLines.size) {
+                        currentLines[i].titleTextSize = currentPaint.textSize
                     }
-                    setTypeText(
-                        book,
-                        segment + reviewTxt,
-                        paint,
-                        titlePaintTextHeight,
-                        titlePaintFontMetrics,
-                        imageStyle,
-                        srcList = srcList.ifEmpty { null },
-                        isTitle = true,
-                        emptyContent = contents.isEmpty(),
-                        isVolumeTitle = bookChapter.isVolume
-                    )
-                    pendingTextPage.lines.last().isParagraphEnd = true
-                    stringBuilder.append("\n")
+                }
+
+                pendingTextPage.lines.last().isParagraphEnd = true
+                stringBuilder.append("\n")
+
+                if (index < allTitleSegments.lastIndex) {
+                    durY += currentHeight * titleLineSpacingSub
                 }
             }
             durY += titleBottomSpacing
@@ -901,7 +886,7 @@ class TextChapterLayout(
             textLine.upTopBottom(durY, textHeight, fontMetrics)
             val textPage = pendingTextPage
             textPage.addLine(textLine)
-            durY += textHeight * lineSpacingExtra
+            durY += textHeight * if (isTitle) titleLineSpacingExtra else lineSpacingExtra
             if (textPage.height < durY) {
                 textPage.height = durY
             }

+ 45 - 0
app/src/main/java/io/legado/app/ui/book/read/page/provider/TitleStyleParser.kt

@@ -0,0 +1,45 @@
+package io.legado.app.ui.book.read.page.provider
+
+import io.legado.app.ui.book.read.page.entities.TitleSegment
+
+object TitleStyleParser {
+    fun getSegments(
+        rawTitle: String,
+        segType: Int,
+        segDistance: Int,
+        segFlag: String,
+        scaling: Float
+    ): List<TitleSegment> {
+        if (rawTitle.isBlank()) return emptyList()
+
+        val results: List<String> = when (segType) {
+            1 -> {
+                if (segDistance <= 0 || segDistance >= rawTitle.length)
+                    listOf(rawTitle)
+                else
+                    listOf(rawTitle.take(segDistance), rawTitle.substring(segDistance))
+            }
+
+            2 -> {
+                val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+                if (flags.isEmpty()) listOf(rawTitle)
+                else {
+                    val pattern = flags.joinToString("|") { Regex.escape(it) }
+                    val regex = Regex("(?<=$pattern)")
+                    rawTitle.split(regex).map { it.trim() }.filter { it.isNotEmpty() }
+                }
+            }
+
+            else -> listOf(rawTitle)
+        }
+
+        // 统一包装成对象返回
+        return results.mapIndexed { index, text ->
+            TitleSegment(
+                text = text,
+                isMainTitle = index == 0,
+                scale = if (index == 0) 1.0f else scaling
+            )
+        }
+    }
+}

+ 59 - 32
app/src/main/res/layout/dialog_tip_config.xml

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
@@ -37,45 +36,58 @@
 
         </LinearLayout>
 
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/body_title"
-            android:textSize="16sp"
-            android:textStyle="bold" />
-
-        <com.google.android.material.chip.ChipGroup
-            android:id="@+id/rg_title_mode"
-            android:layout_width="wrap_content"
+        <LinearLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="end"
-            app:singleLine="true"
-            app:singleSelection="true"
-            app:selectionRequired="true">
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
 
-            <com.google.android.material.chip.Chip
-                android:id="@+id/rb_title_mode1"
+            <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:checkable="true"
-                android:text="@string/title_left" />
+                android:text="@string/body_title"
+                android:textSize="16sp"
+                android:textStyle="bold" />
 
-            <com.google.android.material.chip.Chip
-                android:id="@+id/rb_title_mode2"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:checkable="true"
-                android:text="@string/title_center" />
+            <View
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1" />
 
-            <com.google.android.material.chip.Chip
-                android:id="@+id/rb_title_mode3"
+            <com.google.android.material.chip.ChipGroup
+                android:id="@+id/rg_title_mode"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:checkable="true"
-                android:text="@string/title_hide" />
+                android:layout_marginVertical="8dp"
+                app:selectionRequired="true"
+                app:singleLine="true"
+                app:singleSelection="true">
+
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/rb_title_mode1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:checkable="true"
+                    android:text="@string/title_left" />
+
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/rb_title_mode2"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:checkable="true"
+                    android:text="@string/title_center" />
+
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/rb_title_mode3"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:checkable="true"
+                    android:text="@string/title_hide" />
+
+            </com.google.android.material.chip.ChipGroup>
 
+        </LinearLayout>
 
-        </com.google.android.material.chip.ChipGroup>
 
         <com.google.android.material.button.MaterialButtonGroup
             android:layout_width="match_parent"
@@ -114,7 +126,7 @@
                 android:layout_width="108dp"
                 android:layout_height="wrap_content"
                 app:max="20"
-                app:title="次行缩放(未完成)" />
+                app:title="次行缩放" />
 
             <View
                 android:layout_width="0dp"
@@ -128,10 +140,24 @@
                 app:max="20"
                 app:title="@string/title_font_size" />
 
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <io.legado.app.ui.widget.SimpleCounterView
+                android:id="@+id/dsb_title_line_spacing_sub"
+                android:layout_width="108dp"
+                android:layout_height="wrap_content"
+                app:max="40"
+                app:title="次行距离" />
+
             <View
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
-                android:layout_weight="1"/>
+                android:layout_weight="1" />
 
             <io.legado.app.ui.widget.SimpleCounterView
                 android:id="@+id/dsb_title_line_spacing_extra"
@@ -181,6 +207,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="end"
+            android:layout_marginVertical="8dp"
             app:singleLine="true"
             app:singleSelection="true"
             app:selectionRequired="true">