feat: 优化代码

pull/106/head
kunfei 5 years ago
parent 38911b6d93
commit db33516ac5
  1. 15
      app/src/main/assets/readConfig.json
  2. 3
      app/src/main/java/io/legado/app/App.kt
  3. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  4. 5
      app/src/main/java/io/legado/app/help/BookHelp.kt
  5. 14
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  6. 2
      app/src/main/java/io/legado/app/ui/book/read/Help.kt
  7. 7
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  8. 10
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  9. 4
      app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
  10. 17
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  11. 286
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  12. 55
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  13. 50
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  14. 4
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  15. 4
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  16. 7
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  17. 3
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt
  18. 7
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  19. 21
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  20. 12
      app/src/main/res/layout/dialog_read_book_style.xml

@ -9,8 +9,7 @@
"darkStatusIcon": true, "darkStatusIcon": true,
"textSize": 24, "textSize": 24,
"letterSpacing": 0, "letterSpacing": 0,
"lineSpacingExtra": 10, "lineSpacingExtra": 10
"lineSpacingMultiplier": 1.2
}, },
{ {
"bgStr": "#DDC090", "bgStr": "#DDC090",
@ -22,8 +21,7 @@
"darkStatusIcon": true, "darkStatusIcon": true,
"textSize": 24, "textSize": 24,
"letterSpacing": 0, "letterSpacing": 0,
"lineSpacingExtra": 10, "lineSpacingExtra": 10
"lineSpacingMultiplier": 1.2
}, },
{ {
"bgStr": "#C2D8AA", "bgStr": "#C2D8AA",
@ -35,8 +33,7 @@
"darkStatusIcon": false, "darkStatusIcon": false,
"textSize": 24, "textSize": 24,
"letterSpacing": 0, "letterSpacing": 0,
"lineSpacingExtra": 10, "lineSpacingExtra": 10
"lineSpacingMultiplier": 1.2
}, },
{ {
"bgStr": "#DBB8E2", "bgStr": "#DBB8E2",
@ -48,8 +45,7 @@
"darkStatusIcon": false, "darkStatusIcon": false,
"textSize": 24, "textSize": 24,
"letterSpacing": 0, "letterSpacing": 0,
"lineSpacingExtra": 10, "lineSpacingExtra": 10
"lineSpacingMultiplier": 1.2
}, },
{ {
"bgStr": "#ABCEE0", "bgStr": "#ABCEE0",
@ -61,7 +57,6 @@
"darkStatusIcon": false, "darkStatusIcon": false,
"textSize": 24, "textSize": 24,
"letterSpacing": 0, "letterSpacing": 0,
"lineSpacingExtra": 10, "lineSpacingExtra": 10
"lineSpacingMultiplier": 1.2
} }
] ]

@ -19,7 +19,6 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.CrashHandler import io.legado.app.help.CrashHandler
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefInt import io.legado.app.utils.getPrefInt
@ -92,7 +91,7 @@ class App : Application() {
) )
.apply() .apply()
} }
ChapterProvider.upReadAloudSpan() // ChapterProvider.upReadAloudSpan()
} }
fun applyDayNight() { fun applyDayNight() {

@ -35,4 +35,5 @@ object PreferKey {
const val launcherIcon = "launcherIcon" const val launcherIcon = "launcherIcon"
const val selectText = "selectText" const val selectText = "selectText"
const val lastBackup = "lastBackup" const val lastBackup = "lastBackup"
const val bodyIndent = "textIndent"
} }

@ -237,6 +237,8 @@ object BookHelp {
private var bookName: String? = null private var bookName: String? = null
private var bookOrigin: String? = null private var bookOrigin: String? = null
private var replaceRules: List<ReplaceRule> = arrayListOf() private var replaceRules: List<ReplaceRule> = arrayListOf()
val bodyIndent
get() = " ".repeat(App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2))
fun disposeContent( fun disposeContent(
title: String, title: String,
@ -273,7 +275,6 @@ object BookHelp {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c)
2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c)
} }
val indent = App.INSTANCE.getPrefInt("textIndent", 2) return c.replace("\\s*\\n+\\s*".toRegex(), "\n$bodyIndent")
return c.replace("\\s*\\n+\\s*".toRegex(), "\n" + " ".repeat(indent))
} }
} }

@ -7,6 +7,7 @@ import android.graphics.drawable.Drawable
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.* import io.legado.app.utils.*
import java.io.File import java.io.File
@ -22,6 +23,8 @@ object ReadBookConfig {
val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes()) val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes())
GSON.fromJsonArray<Config>(json)!! GSON.fromJsonArray<Config>(json)!!
} }
val durConfig
get() = getConfig(styleSelect)
var styleSelect var styleSelect
get() = App.INSTANCE.getPrefInt("readStyleSelect") get() = App.INSTANCE.getPrefInt("readStyleSelect")
@ -33,7 +36,7 @@ object ReadBookConfig {
} }
@Synchronized @Synchronized
fun getConfig(index: Int = styleSelect): Config { fun getConfig(index: Int): Config {
if (configList.size < 5) { if (configList.size < 5) {
resetAll() resetAll()
} }
@ -64,7 +67,7 @@ object ReadBookConfig {
val dm = resources.displayMetrics val dm = resources.displayMetrics
val width = dm.widthPixels val width = dm.widthPixels
val height = dm.heightPixels val height = dm.heightPixels
bg = getConfig().bgDrawable(width, height) bg = durConfig.bgDrawable(width, height)
} }
fun save() { fun save() {
@ -76,8 +79,8 @@ object ReadBookConfig {
fun resetDur() { fun resetDur() {
defaultConfigs[styleSelect].let { defaultConfigs[styleSelect].let {
getConfig().setBg(it.bgType(), it.bgStr()) durConfig.setBg(it.bgType(), it.bgStr())
getConfig().setTextColor(it.textColor()) durConfig.setTextColor(it.textColor())
upBg() upBg()
save() save()
} }
@ -104,7 +107,7 @@ object ReadBookConfig {
var textSize: Int = 15,//文字大小 var textSize: Int = 15,//文字大小
var letterSpacing: Float = 1f,//字间距 var letterSpacing: Float = 1f,//字间距
var lineSpacingExtra: Int = 12,//行间距 var lineSpacingExtra: Int = 12,//行间距
var lineSpacingMultiplier: Float = 1.2f,//行倍距 var paragraphSpacing: Int = 12,
var paddingBottom: Int = 6, var paddingBottom: Int = 6,
var paddingLeft: Int = 16, var paddingLeft: Int = 16,
var paddingRight: Int = 16, var paddingRight: Int = 16,
@ -134,6 +137,7 @@ object ReadBookConfig {
} else { } else {
textColor = "#${color.hexString}" textColor = "#${color.hexString}"
} }
ChapterProvider.upStyle(this)
} }
fun setStatusIconDark(isDark: Boolean) { fun setStatusIconDark(isDark: Boolean) {

@ -56,7 +56,7 @@ object Help {
} }
activity.window.decorView.systemUiVisibility = flag activity.window.decorView.systemUiVisibility = flag
if (toolBarHide) { if (toolBarHide) {
ATH.setLightStatusBar(activity, ReadBookConfig.getConfig().statusIconDark()) ATH.setLightStatusBar(activity, ReadBookConfig.durConfig.statusIconDark())
} else { } else {
ATH.setLightStatusBarAuto( ATH.setLightStatusBarAuto(
activity, activity,

@ -44,7 +44,6 @@ import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.activity_book_read.*
import kotlinx.android.synthetic.main.view_book_page.*
import kotlinx.android.synthetic.main.view_read_menu.* import kotlinx.android.synthetic.main.view_read_menu.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
@ -117,7 +116,6 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
* 初始化View * 初始化View
*/ */
private fun initView() { private fun initView() {
ChapterProvider.textView = content_text_view
tv_chapter_name.onClick { tv_chapter_name.onClick {
ReadBook.webBook?.let { ReadBook.webBook?.let {
startActivityForResult<BookSourceEditActivity>( startActivityForResult<BookSourceEditActivity>(
@ -446,7 +444,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
/** /**
* colorSelectDialog * colorSelectDialog
*/ */
override fun onColorSelected(dialogId: Int, color: Int) = with(ReadBookConfig.getConfig()) { override fun onColorSelected(dialogId: Int, color: Int) = with(ReadBookConfig.durConfig) {
when (dialogId) { when (dialogId) {
TEXT_COLOR -> { TEXT_COLOR -> {
setTextColor(color) setTextColor(color)
@ -507,7 +505,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
ReadBook.curTextChapter?.let { textChapter -> ReadBook.curTextChapter?.let { textChapter ->
val page = textChapter.page(ReadBook.durPageIndex) val page = textChapter.page(ReadBook.durPageIndex)
if (page != null && page.text is SpannableStringBuilder) { if (page != null && page.text is SpannableStringBuilder) {
page.text.removeSpan(ChapterProvider.readAloudSpan) page.removePageAloudSpan()
page_view.upContent() page_view.upContent()
} }
} }
@ -531,6 +529,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
content_view.upStyle() content_view.upStyle()
page_view.upBg() page_view.upBg()
page_view.upStyle() page_view.upStyle()
ChapterProvider.upStyle(ReadBookConfig.durConfig)
if (it) { if (it) {
ReadBook.loadContent() ReadBook.loadContent()
} else { } else {

@ -81,7 +81,7 @@ class BgTextConfigDialog : DialogFragment() {
} }
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private fun initData() = with(ReadBookConfig.getConfig()) { private fun initData() = with(ReadBookConfig.durConfig) {
sw_dark_status_icon.isChecked = statusIconDark() sw_dark_status_icon.isChecked = statusIconDark()
adapter = BgAdapter(requireContext()) adapter = BgAdapter(requireContext())
recycler_view.layoutManager = recycler_view.layoutManager =
@ -99,7 +99,7 @@ class BgTextConfigDialog : DialogFragment() {
} }
} }
private fun initView() = with(ReadBookConfig.getConfig()) { private fun initView() = with(ReadBookConfig.durConfig) {
sw_dark_status_icon.onCheckedChange { buttonView, isChecked -> sw_dark_status_icon.onCheckedChange { buttonView, isChecked ->
if (buttonView?.isPressed == true) { if (buttonView?.isPressed == true) {
setStatusIconDark(isChecked) setStatusIconDark(isChecked)
@ -156,7 +156,7 @@ class BgTextConfigDialog : DialogFragment() {
holder.itemView.apply { holder.itemView.apply {
this.onClick { this.onClick {
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {
ReadBookConfig.getConfig().setBg(1, it) ReadBookConfig.durConfig.setBg(1, it)
ReadBookConfig.upBg() ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, false)
} }
@ -180,7 +180,7 @@ class BgTextConfigDialog : DialogFragment() {
FileUtils.createFileIfNotExist(file.absolutePath + File.separator + "bg" + File.separator + doc.name) FileUtils.createFileIfNotExist(file.absolutePath + File.separator + "bg" + File.separator + doc.name)
DocumentUtils.readBytes(requireContext(), uri)?.let { DocumentUtils.readBytes(requireContext(), uri)?.let {
file.writeBytes(it) file.writeBytes(it)
ReadBookConfig.getConfig().setBg(2, file.absolutePath) ReadBookConfig.durConfig.setBg(2, file.absolutePath)
ReadBookConfig.upBg() ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, false)
} }
@ -194,7 +194,7 @@ class BgTextConfigDialog : DialogFragment() {
.rationale(R.string.bg_image_per) .rationale(R.string.bg_image_per)
.onGranted { .onGranted {
RealPathUtil.getPath(requireContext(), uri)?.let { path -> RealPathUtil.getPath(requireContext(), uri)?.let { path ->
ReadBookConfig.getConfig().setBg(2, path) ReadBookConfig.durConfig.setBg(2, path)
ReadBookConfig.upBg() ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, false)
} }

@ -49,7 +49,7 @@ class PaddingConfigDialog : DialogFragment() {
ReadBookConfig.save() ReadBookConfig.save()
} }
private fun initData() = with(ReadBookConfig.getConfig()) { private fun initData() = with(ReadBookConfig.durConfig) {
//正文 //正文
dsb_padding_top.progress = paddingTop dsb_padding_top.progress = paddingTop
dsb_padding_bottom.progress = paddingBottom dsb_padding_bottom.progress = paddingBottom
@ -67,7 +67,7 @@ class PaddingConfigDialog : DialogFragment() {
dsb_footer_padding_right.progress = footerPaddingRight dsb_footer_padding_right.progress = footerPaddingRight
} }
private fun initView() = with(ReadBookConfig.getConfig()) { private fun initView() = with(ReadBookConfig.durConfig) {
//正文 //正文
dsb_padding_top.onChanged = { dsb_padding_top.onChanged = {
paddingTop = it paddingTop = it

@ -91,7 +91,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
tv_text_bold.onClick { tv_text_bold.onClick {
with(ReadBookConfig.getConfig()) { with(ReadBookConfig.durConfig) {
textBold = !textBold textBold = !textBold
tv_text_bold.isSelected = textBold tv_text_bold.isSelected = textBold
} }
@ -105,7 +105,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
title = getString(R.string.text_indent), title = getString(R.string.text_indent),
items = resources.getStringArray(R.array.indent).toList() items = resources.getStringArray(R.array.indent).toList()
) { _, index -> ) { _, index ->
putPrefInt("textIndent", index) putPrefInt(PreferKey.bodyIndent, index)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
} }
@ -117,15 +117,19 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
} }
dsb_text_size.onChanged = { dsb_text_size.onChanged = {
ReadBookConfig.getConfig().textSize = it + 5 ReadBookConfig.durConfig.textSize = it + 5
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
dsb_text_letter_spacing.onChanged = { dsb_text_letter_spacing.onChanged = {
ReadBookConfig.getConfig().letterSpacing = (it - 5) / 10f ReadBookConfig.durConfig.letterSpacing = (it - 5) / 10f
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
dsb_line_size.onChanged = { dsb_line_size.onChanged = {
ReadBookConfig.getConfig().lineSpacingExtra = it ReadBookConfig.durConfig.lineSpacingExtra = it
postEvent(EventBus.UP_CONFIG, true)
}
dsb_paragraph_spacing.onChanged = {
ReadBookConfig.durConfig.paragraphSpacing = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
rg_page_anim.onCheckedChange { _, checkedId -> rg_page_anim.onCheckedChange { _, checkedId ->
@ -173,11 +177,12 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
private fun upStyle() { private fun upStyle() {
ReadBookConfig.getConfig().let { ReadBookConfig.durConfig.let {
tv_text_bold.isSelected = it.textBold tv_text_bold.isSelected = it.textBold
dsb_text_size.progress = it.textSize - 5 dsb_text_size.progress = it.textSize - 5
dsb_text_letter_spacing.progress = (it.letterSpacing * 10).toInt() + 5 dsb_text_letter_spacing.progress = (it.letterSpacing * 10).toInt() + 5
dsb_line_size.progress = it.lineSpacingExtra dsb_line_size.progress = it.lineSpacingExtra
dsb_paragraph_spacing.progress = it.paragraphSpacing
} }
} }

@ -1,83 +1,261 @@
package io.legado.app.ui.book.read.page package io.legado.app.ui.book.read.page
import android.text.Spannable import android.graphics.Point
import android.text.SpannableStringBuilder import android.text.Layout
import android.text.style.ForegroundColorSpan import android.text.StaticLayout
import android.text.style.RelativeSizeSpan import android.text.TextPaint
import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT
import io.legado.app.App
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.lib.theme.accentColor import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextChar
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.TextPage
import io.legado.app.utils.dp
object ChapterProvider { object ChapterProvider {
var readAloudSpan = ForegroundColorSpan(App.INSTANCE.accentColor) var viewWidth = 0
private val titleSpan = RelativeSizeSpan(1.2f) var viewHeight = 0
private var visibleWidth = 0
private var visibleHeight = 0
private var paddingLeft = 0
private var paddingTop = 0
private var lineSpacingExtra = 0
private var paragraphSpacing = 0
var titlePaint = TextPaint()
var contentPaint = TextPaint()
var textView: ContentTextView? = null init {
upStyle(ReadBookConfig.durConfig)
}
fun upStyle(config: ReadBookConfig.Config) {
titlePaint.color = config.textColor()
titlePaint.letterSpacing = config.letterSpacing
contentPaint.color = config.textColor()
contentPaint.letterSpacing = config.letterSpacing
lineSpacingExtra = config.lineSpacingExtra
paragraphSpacing = config.paragraphSpacing
titlePaint.textSize = (config.textSize + 2).dp.toFloat()
contentPaint.textSize = config.textSize.dp.toFloat()
upSize(config)
}
fun upSize(config: ReadBookConfig.Config) {
paddingLeft = config.paddingLeft.dp
paddingTop = config.paddingTop.dp
visibleWidth = viewWidth - paddingLeft - config.paddingRight.dp
visibleHeight = viewHeight - paddingTop - config.paddingBottom.dp
}
@Suppress("DEPRECATION")
fun getTextChapter( fun getTextChapter(
bookChapter: BookChapter, bookChapter: BookChapter,
content: String, content: String,
chapterSize: Int, chapterSize: Int,
isHtml: Boolean = false isHtml: Boolean = false
): TextChapter { ): TextChapter {
textView?.let { val bodyIndent = BookHelp.bodyIndent
val textPages = arrayListOf<TextPage>() val textPages = arrayListOf<TextPage>()
val pageLines = arrayListOf<Int>() val pageLines = arrayListOf<Int>()
val pageLengths = arrayListOf<Int>() val pageLengths = arrayListOf<Int>()
var surplusText = content var surplusText = content
var pageIndex = 0 var durY = 0
textPages.add(TextPage())
while (surplusText.isNotEmpty()) { while (surplusText.isNotEmpty()) {
val spannableStringBuilder = if (textPages.first().textLines.isEmpty()) {
if (isHtml) { //title
HtmlCompat.fromHtml( val end = surplusText.indexOf("\n")
surplusText, if (end > 0) {
FROM_HTML_MODE_COMPACT val title = surplusText.substring(0, end)
) as SpannableStringBuilder surplusText = surplusText.substring(end + 1)
val layout = StaticLayout(
title, titlePaint, visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra.toFloat(), false
)
for (lineIndex in 0 until layout.lineCount) {
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine(isTitle = true)
if (durY < visibleHeight) {
textPages.last().textLines.add(textLine)
} else {
durY = 0
textPages.add(TextPage())
textPages.last().textLines.add(textLine)
}
textLine.lineBottom = layout.getLineBottom(lineIndex)
textLine.lineTop = layout.getLineTop(lineIndex)
val words = title.substring(
layout.getLineStart(lineIndex),
layout.getLineEnd(lineIndex)
)
val desiredWidth = layout.getLineMax(lineIndex)
if (lineIndex != layout.lineCount - 1) {
val gapCount: Int = words.length - 1
val d = (visibleWidth - desiredWidth) / gapCount
var x = 0
for (i in words.indices) {
val char = words[i].toString()
val cw = StaticLayout.getDesiredWidth(char, titlePaint)
val x1 = if (i != words.lastIndex) {
(x + cw + d).toInt()
} else {
(x + cw).toInt()
}
val textChar = TextChar(
charData = char,
leftBottomPosition = Point(paddingLeft + x, paddingTop + durY),
rightTopPosition = Point(
paddingLeft + x1,
paddingTop + durY - (textLine.lineBottom - textLine.lineTop)
)
)
textLine.textChars.add(textChar)
x = x1
}
} else { } else {
SpannableStringBuilder(surplusText) //最后一行
var x = 0
for (i in words.indices) {
val char = words[i].toString()
val cw = StaticLayout.getDesiredWidth(char, titlePaint)
val x1 = (x + cw).toInt()
val textChar = TextChar(
charData = char,
leftBottomPosition = Point(paddingLeft + x, paddingTop + durY),
rightTopPosition = Point(
paddingLeft + x1,
paddingTop + durY - (textLine.lineBottom - textLine.lineTop)
)
)
textLine.textChars.add(textChar)
x = x1
} }
if (pageIndex == 0) { }
}
durY += paragraphSpacing
}
} else {
//正文
val end = surplusText.indexOf("\n") val end = surplusText.indexOf("\n")
if (end > 0) { val text: String
spannableStringBuilder.setSpan( if (end >= 0) {
titleSpan, text = surplusText.substring(0, end)
0, surplusText = surplusText.substring(end + 1)
end, } else {
Spannable.SPAN_INCLUSIVE_EXCLUSIVE text = surplusText
surplusText = ""
}
val layout = StaticLayout(
text, contentPaint, visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra.toFloat(), false
)
for (lineIndex in 0 until layout.lineCount) {
val textLine = TextLine(isTitle = false)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
if (durY < visibleHeight) {
textPages.last().textLines.add(textLine)
} else {
durY = 0
textPages.add(TextPage())
textPages.last().textLines.add(textLine)
}
textLine.lineBottom = layout.getLineBottom(lineIndex)
textLine.lineTop = layout.getLineTop(lineIndex)
var words =
text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
val desiredWidth = layout.getLineMax(lineIndex)
if (lineIndex == 0 && layout.lineCount > 1) {
//第一行
var x = 0
val icw = StaticLayout.getDesiredWidth(bodyIndent, contentPaint)
var x1 = (x + icw).toInt()
val textChar = TextChar(
charData = bodyIndent,
leftBottomPosition = Point(paddingLeft + x, paddingTop + durY),
rightTopPosition = Point(
paddingLeft + x1,
paddingTop + durY - (textLine.lineBottom - textLine.lineTop)
)
)
textLine.textChars.add(textChar)
x = x1
words = words.replaceFirst(bodyIndent, "")
val gapCount: Int = words.length - 1
val d = (visibleWidth - desiredWidth) / gapCount
for (i in words.indices) {
val char = words[i].toString()
val cw = StaticLayout.getDesiredWidth(char, contentPaint)
x1 = if (i != words.lastIndex) {
(x + cw + d).toInt()
} else {
(x + cw).toInt()
}
val textChar1 = TextChar(
charData = char,
leftBottomPosition = Point(paddingLeft + x, paddingTop + durY),
rightTopPosition = Point(
paddingLeft + x1,
paddingTop + durY - (textLine.lineBottom - textLine.lineTop)
)
) )
textLine.textChars.add(textChar1)
x = x1
} }
} else if (lineIndex == layout.lineCount - 1) {
//最后一行
var x = 0
for (i in words.indices) {
val char = words[i].toString()
val cw = StaticLayout.getDesiredWidth(char, contentPaint)
val x1 = (x + cw).toInt()
val textChar = TextChar(
charData = char,
leftBottomPosition = Point(paddingLeft + x, paddingTop + durY),
rightTopPosition = Point(
paddingLeft + x1,
paddingTop + durY - (textLine.lineBottom - textLine.lineTop)
)
)
textLine.textChars.add(textChar)
x = x1
} }
it.text = spannableStringBuilder
val lastLine = it.getLineNum()
val lastCharNum = it.getCharNum(lastLine)
if (lastCharNum == 0) {
break
} else { } else {
pageLines.add(lastLine) //中间行
pageLengths.add(lastCharNum) val gapCount: Int = words.length - 1
textPages.add( val d = (visibleWidth - desiredWidth) / gapCount
TextPage( var x = 0
index = pageIndex, for (i in words.indices) {
text = spannableStringBuilder.delete( val char = words[i].toString()
lastCharNum, val cw = StaticLayout.getDesiredWidth(char, contentPaint)
spannableStringBuilder.length val x1 = if (i != words.lastIndex) {
), (x + cw + d).toInt()
title = bookChapter.title, } else {
chapterSize = chapterSize, (x + cw).toInt()
chapterIndex = bookChapter.index }
val textChar = TextChar(
charData = char,
leftBottomPosition = Point(paddingLeft + x, paddingTop + durY),
rightTopPosition = Point(
paddingLeft + x1,
paddingTop + durY - (textLine.lineBottom - textLine.lineTop)
) )
) )
surplusText = surplusText.substring(lastCharNum) textLine.textChars.add(textChar)
pageIndex++ x = x1
}
}
} }
durY += paragraphSpacing
} }
for (item in textPages) { }
for ((index, item) in textPages.withIndex()) {
item.index = index
item.pageSize = textPages.size item.pageSize = textPages.size
item.chapterIndex = bookChapter.index
item.chapterSize = chapterSize
item.title = bookChapter.title
} }
return TextChapter( return TextChapter(
bookChapter.index, bookChapter.index,
@ -88,19 +266,5 @@ object ChapterProvider {
pageLengths, pageLengths,
chapterSize chapterSize
) )
} ?: return TextChapter(
bookChapter.index,
bookChapter.title,
bookChapter.url,
arrayListOf(),
arrayListOf(),
arrayListOf(),
chapterSize
)
}
fun upReadAloudSpan() {
readAloudSpan = ForegroundColorSpan(App.INSTANCE.accentColor)
} }
} }

@ -1,11 +1,14 @@
package io.legado.app.ui.book.read.page package io.legado.app.ui.book.read.page
import android.content.Context import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet import android.util.AttributeSet
import io.legado.app.ui.widget.text.InertiaScrollTextView import android.view.View
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextPage
class ContentTextView : InertiaScrollTextView { class ContentTextView : View {
constructor(context: Context) : super(context) constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@ -13,19 +16,45 @@ class ContentTextView : InertiaScrollTextView {
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr) : super(context, attrs, defStyleAttr)
/** var textPage: TextPage? = null
* 获取当前页总字数
*/ fun setContent(textPage: TextPage?) {
fun getCharNum(lineNum: Int = getLineNum()): Int { this.textPage = textPage
return layout?.getLineEnd(lineNum) ?: 0 invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
ReadBookConfig.durConfig.let {
ChapterProvider.viewWidth = w
ChapterProvider.viewHeight = h
ChapterProvider.upSize(ReadBookConfig.durConfig)
}
} }
/** override fun onDraw(canvas: Canvas) {
* 获取当前页总行数 super.onDraw(canvas)
*/ textPage?.let { textPage ->
fun getLineNum(): Int { textPage.textLines.forEach { textLine ->
val topOfLastLine = height - paddingTop - paddingBottom - lineHeight textLine.textChars.forEach {
return layout?.getLineForVertical(topOfLastLine) ?: 0 if (textLine.isTitle) {
canvas.drawText(
it.charData,
it.leftBottomPosition.x.toFloat(),
it.leftBottomPosition.y.toFloat(),
ChapterProvider.titlePaint
)
} else {
canvas.drawText(
it.charData,
it.leftBottomPosition.x.toFloat(),
it.leftBottomPosition.y.toFloat(),
ChapterProvider.contentPaint
)
}
}
}
}
} }
} }

@ -2,10 +2,8 @@ package io.legado.app.ui.book.read.page
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity
import android.widget.FrameLayout import android.widget.FrameLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.TIME_FORMAT import io.legado.app.constant.AppConst.TIME_FORMAT
@ -14,8 +12,6 @@ import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.view_book_page.view.* import kotlinx.android.synthetic.main.view_book_page.view.*
import org.jetbrains.anko.sdk27.listeners.onScrollChange
import java.io.File
import java.util.* import java.util.*
@ -39,20 +35,10 @@ class ContentView : FrameLayout {
inflate(context, R.layout.view_book_page, this) inflate(context, R.layout.view_book_page, this)
upStyle() upStyle()
upTime() upTime()
content_text_view.customSelectionActionModeCallback =
ContentSelectActionCallback(content_text_view)
content_text_view.onScrollChange { _, _, scrollY, _, _ ->
content_text_view.layout?.getLineForVertical(scrollY)?.let { line ->
callBack?.scrollToLine(line)
}
if (content_text_view.atBottom()) {
callBack?.scrollToLast()
}
}
} }
fun upStyle() { fun upStyle() {
ReadBookConfig.getConfig().apply { ReadBookConfig.durConfig.apply {
val rootPaddingTop = if (context.getPrefBoolean(PreferKey.hideStatusBar, false)) { val rootPaddingTop = if (context.getPrefBoolean(PreferKey.hideStatusBar, false)) {
//显示状态栏时隐藏header //显示状态栏时隐藏header
ll_header.visible() ll_header.visible()
@ -82,30 +68,13 @@ class ContentView : FrameLayout {
footerPaddingRight.dp, footerPaddingRight.dp,
footerPaddingBottom.dp footerPaddingBottom.dp
) )
content_text_view.textSize = textSize.toFloat()
content_text_view.setLineSpacing(lineSpacingExtra.toFloat(), lineSpacingMultiplier)
content_text_view.letterSpacing = letterSpacing
content_text_view.paint.isFakeBoldText = textBold
textColor().let { textColor().let {
content_text_view.setTextColor(it)
tv_top_left.setTextColor(it) tv_top_left.setTextColor(it)
tv_top_right.setTextColor(it) tv_top_right.setTextColor(it)
tv_bottom_left.setTextColor(it) tv_bottom_left.setTextColor(it)
tv_bottom_right.setTextColor(it) tv_bottom_right.setTextColor(it)
} }
} }
context.getPrefString(PreferKey.readBookFont)?.let {
if (it.isNotEmpty()) {
val file = File(it)
if (file.exists()) {
content_text_view.typeface = Typeface.createFromFile(it)
return@let
} else {
context.putPrefString(PreferKey.readBookFont, "")
}
}
content_text_view.typeface = Typeface.DEFAULT
}
} }
fun setBg(bg: Drawable?) { fun setBg(bg: Drawable?) {
@ -121,15 +90,11 @@ class ContentView : FrameLayout {
} }
fun setContent(textPage: TextPage?) { fun setContent(textPage: TextPage?) {
content_text_view.setContent(textPage)
if (textPage != null) { if (textPage != null) {
content_text_view.gravity = Gravity.START
content_text_view.text = textPage.text
tv_bottom_left.text = textPage.title tv_bottom_left.text = textPage.title
pageSize = textPage.pageSize pageSize = textPage.pageSize
setPageIndex(textPage.index) setPageIndex(textPage.index)
} else {
content_text_view.gravity = Gravity.CENTER
content_text_view.setText(R.string.data_loading)
} }
} }
@ -141,7 +106,7 @@ class ContentView : FrameLayout {
} }
fun isTextSelected(): Boolean { fun isTextSelected(): Boolean {
return content_text_view.selectionEnd - content_text_view.selectionStart != 0 return false
} }
fun contentTextView(): ContentTextView? { fun contentTextView(): ContentTextView? {
@ -151,19 +116,14 @@ class ContentView : FrameLayout {
fun scrollTo(pos: Int?) { fun scrollTo(pos: Int?) {
if (pos != null) { if (pos != null) {
content_text_view.post { content_text_view.post {
if (content_text_view.layout.lineCount >= pos) {
content_text_view.scrollTo(0, content_text_view.layout.getLineTop(pos))
}
} }
} }
} }
fun scrollToBottom() { fun scrollToBottom() {
content_text_view.post { content_text_view.post {
content_text_view.scrollTo(
0,
content_text_view.layout.getLineTop(content_text_view.lineCount)
)
} }
} }

@ -51,9 +51,9 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
override fun upSelectAble() { override fun upSelectAble() {
pageView.curPage.contentTextView()?.apply { pageView.curPage.contentTextView()?.apply {
if (context.getPrefBoolean(PreferKey.selectText)) { if (context.getPrefBoolean(PreferKey.selectText)) {
setTextIsSelectable(true) // setTextIsSelectable(true)
} else { } else {
setTextIsSelectable(false) // setTextIsSelectable(false)
} }
} }
} }

@ -192,8 +192,8 @@ abstract class PageDelegate(protected val pageView: PageView) {
if (event.action == MotionEvent.ACTION_DOWN) { if (event.action == MotionEvent.ACTION_DOWN) {
curPage.let { curPage.let {
it.contentTextView()?.let { contentTextView -> it.contentTextView()?.let { contentTextView ->
atTop = contentTextView.atTop() // atTop = contentTextView.atTop()
atBottom = contentTextView.atBottom() // atBottom = contentTextView.atBottom()
} }
it.dispatchTouchEvent(event) it.dispatchTouchEvent(event)
} }

@ -2,7 +2,6 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Matrix import android.graphics.Matrix
import android.text.method.ScrollingMovementMethod
import android.view.MotionEvent import android.view.MotionEvent
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
@ -122,10 +121,10 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
override fun upSelectAble() { override fun upSelectAble() {
pageView.curPage.contentTextView()?.apply { pageView.curPage.contentTextView()?.apply {
if (context.getPrefBoolean(PreferKey.selectText)) { if (context.getPrefBoolean(PreferKey.selectText)) {
setTextIsSelectable(true) // setTextIsSelectable(true)
} else { } else {
setTextIsSelectable(false) // setTextIsSelectable(false)
movementMethod = ScrollingMovementMethod.getInstance() // movementMethod = ScrollingMovementMethod.getInstance()
} }
} }
} }

@ -3,9 +3,8 @@ package io.legado.app.ui.book.read.page.entities
import android.graphics.Point import android.graphics.Point
data class TextChar( data class TextChar(
val charData: Char, val charData: String,
var selected: Boolean = false, var selected: Boolean = false,
var isReadAloud: Boolean = false,
val leftBottomPosition: Point, val leftBottomPosition: Point,
val rightTopPosition: Point val rightTopPosition: Point
) )

@ -1,6 +1,9 @@
package io.legado.app.ui.book.read.page.entities package io.legado.app.ui.book.read.page.entities
data class TextLine( data class TextLine(
val textChars: List<TextChar>, val textChars: ArrayList<TextChar> = arrayListOf(),
val lineBottom: Int var lineTop: Int = 0,
var lineBottom: Int = 0,
val isTitle: Boolean = false,
var isReadAloud: Boolean = false
) )

@ -1,40 +1,33 @@
package io.legado.app.ui.book.read.page.entities package io.legado.app.ui.book.read.page.entities
import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.book.read.page.ChapterProvider
data class TextPage( data class TextPage(
val index: Int, var index: Int = 0,
val text: CharSequence = App.INSTANCE.getString(R.string.data_loading), var text: CharSequence = App.INSTANCE.getString(R.string.data_loading),
val title: String, var title: String = "",
val textLines: ArrayList<TextLine> = arrayListOf(),
var pageSize: Int = 0, var pageSize: Int = 0,
var chapterSize: Int = 0, var chapterSize: Int = 0,
var chapterIndex: Int = 0 var chapterIndex: Int = 0
) { ) {
fun removePageAloudSpan(): TextPage { fun removePageAloudSpan(): TextPage {
if (text is SpannableStringBuilder) { textLines.forEach { textLine ->
text.removeSpan(ChapterProvider.readAloudSpan) textLine.isReadAloud = false
} }
return this return this
} }
fun upPageAloudSpan(pageStart: Int) { fun upPageAloudSpan(pageStart: Int) {
if (text is SpannableStringBuilder) { if (text is SpannableStringBuilder) {
text.removeSpan(ChapterProvider.readAloudSpan) removePageAloudSpan()
var end = text.indexOf("\n", pageStart) var end = text.indexOf("\n", pageStart)
if (end == -1) end = text.length if (end == -1) end = text.length
var start = text.lastIndexOf("\n", pageStart) var start = text.lastIndexOf("\n", pageStart)
if (start == -1) start = 0 if (start == -1) start = 0
text.setSpan(
ChapterProvider.readAloudSpan,
start,
end,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
} }
} }
} }

@ -112,12 +112,22 @@
app:title="@string/line_size" app:title="@string/line_size"
app:layout_constraintTop_toBottomOf="@+id/dsb_text_letter_spacing" /> app:layout_constraintTop_toBottomOf="@+id/dsb_text_letter_spacing" />
<io.legado.app.ui.widget.DetailSeekBar
android:id="@+id/dsb_paragraph_spacing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:max="50"
app:title="@string/paragraph_size"
app:layout_constraintTop_toBottomOf="@+id/dsb_line_size" />
<View <View
android:id="@+id/vw_bg_fg" android:id="@+id/vw_bg_fg"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0.8dp" android:layout_height="0.8dp"
android:background="@color/btn_bg_press" android:background="@color/btn_bg_press"
app:layout_constraintTop_toBottomOf="@+id/dsb_line_size" /> app:layout_constraintTop_toBottomOf="@+id/dsb_paragraph_spacing" />
<TextView <TextView
android:id="@+id/tv_page_anim" android:id="@+id/tv_page_anim"

Loading…
Cancel
Save