This commit is contained in:
Horis
2024-02-18 23:21:19 +08:00
parent e69391be03
commit 0312975d4a
36 changed files with 683 additions and 463 deletions
@@ -567,7 +567,6 @@ object ReadBookConfig {
textColorInt = color textColorInt = color
} }
} }
ChapterProvider.upStyle()
} }
fun curTextColor(): Int { fun curTextColor(): Int {
@@ -274,10 +274,10 @@ object ReadBook : CoroutineScope by MainScope() {
if (textChapter != null) { if (textChapter != null) {
val pageIndex = durPageIndex val pageIndex = durPageIndex
if (index > pageIndex) { if (index > pageIndex) {
textChapter.getPage(index - 2)?.recyclePictures() textChapter.getPage(index - 2)?.recycleRecorders()
} }
if (index < pageIndex) { if (index < pageIndex) {
textChapter.getPage(index + 3)?.recyclePictures() textChapter.getPage(index + 3)?.recycleRecorders()
} }
} }
durChapterPos = curTextChapter?.getReadLength(index) ?: index durChapterPos = curTextChapter?.getReadLength(index) ?: index
@@ -6,6 +6,7 @@ import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Looper
import android.view.Gravity import android.view.Gravity
import android.view.InputDevice import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
@@ -76,6 +77,7 @@ import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_C
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.searchContent.SearchContentActivity import io.legado.app.ui.book.searchContent.SearchContentActivity
import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.searchContent.SearchResult
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
@@ -116,7 +118,6 @@ import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -217,7 +218,6 @@ class ReadBookActivity : BaseReadBookActivity(),
private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } } private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }
private var bookChanged = false private var bookChanged = false
private var pageChanged = false private var pageChanged = false
private var reloadContent = false
private val handler by lazy { buildMainHandler() } private val handler by lazy { buildMainHandler() }
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
private val executor = ReadBook.executor private val executor = ReadBook.executor
@@ -264,23 +264,9 @@ class ReadBookActivity : BaseReadBookActivity(),
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState) super.onPostCreate(savedInstanceState)
viewModel.initData(intent) { Looper.myQueue().addIdleHandler {
initDataSuccess() viewModel.initData(intent) { upMenu() }
} false
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
viewModel.initData(intent ?: return) {
initDataSuccess()
}
}
private fun initDataSuccess() {
upMenu()
if (reloadContent) {
reloadContent = false
ReadBook.loadContent(resetPageOffset = false)
} }
} }
@@ -1312,24 +1298,24 @@ class ReadBookActivity : BaseReadBookActivity(),
when (dialogId) { when (dialogId) {
TEXT_COLOR -> { TEXT_COLOR -> {
setCurTextColor(color) setCurTextColor(color)
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
} }
BG_COLOR -> { BG_COLOR -> {
setCurBg(0, "#${color.hexString}") setCurBg(0, "#${color.hexString}")
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(1))
} }
TIP_COLOR -> { TIP_COLOR -> {
ReadTipConfig.tipColor = color ReadTipConfig.tipColor = color
postEvent(EventBus.TIP_COLOR, "") postEvent(EventBus.TIP_COLOR, "")
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
TIP_DIVIDER_COLOR -> { TIP_DIVIDER_COLOR -> {
ReadTipConfig.tipDividerColor = color ReadTipConfig.tipDividerColor = color
postEvent(EventBus.TIP_COLOR, "") postEvent(EventBus.TIP_COLOR, "")
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
} }
@@ -1493,22 +1479,21 @@ class ReadBookActivity : BaseReadBookActivity(),
ReadBook.readAloud(!BaseReadAloudService.pause) ReadBook.readAloud(!BaseReadAloudService.pause)
} }
} }
observeEvent<Boolean>(EventBus.UP_CONFIG) { observeEvent<Array<Int>>(EventBus.UP_CONFIG) {
upSystemUiVisibility() it.forEach { value ->
readView.upPageSlopSquare() when (value) {
readView.upBg() 0 -> upSystemUiVisibility()
readView.upStyle() 1 -> readView.upBg()
readView.upBgAlpha() 2 -> readView.upStyle()
if (it) { // 更新内容排版布局 3 -> readView.upBgAlpha()
if (isInitFinish) { 4 -> readView.upPageSlopSquare()
ReadBook.loadContent(resetPageOffset = false) 5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false)
} else { 6 -> readView.upContent(resetPageOffset = false)
reloadContent = true 8 -> ChapterProvider.upStyle()
9 -> binding.readView.invalidateTextPage()
10 -> ChapterProvider.upLayout()
} }
} else {
readView.upContent(resetPageOffset = false)
} }
binding.readMenu.reset()
} }
observeEvent<Int>(EventBus.ALOUD_STATE) { observeEvent<Int>(EventBus.ALOUD_STATE) {
if (it == Status.STOP || it == Status.PAUSE) { if (it == Status.STOP || it == Status.PAUSE) {
@@ -41,7 +41,7 @@ class BgAdapter(context: Context, val textColor: Int) :
this.setOnClickListener { this.setOnClickListener {
getItemByLayoutPosition(holder.layoutPosition)?.let { getItemByLayoutPosition(holder.layoutPosition)?.let {
ReadBookConfig.durConfig.setCurBg(1, it) ReadBookConfig.durConfig.setCurBg(1, it)
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(1))
} }
} }
} }
@@ -33,11 +33,32 @@ import io.legado.app.lib.theme.getSecondaryTextColor
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.file.HandleFileContract import io.legado.app.ui.file.HandleFileContract
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
import io.legado.app.utils.* import io.legado.app.utils.ColorUtils
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.SelectImageContract
import io.legado.app.utils.compress.ZipUtils import io.legado.app.utils.compress.ZipUtils
import io.legado.app.utils.createFileReplace
import io.legado.app.utils.createFolderReplace
import io.legado.app.utils.externalCache
import io.legado.app.utils.externalFiles
import io.legado.app.utils.getFile
import io.legado.app.utils.inputStream
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.launch
import io.legado.app.utils.longToast
import io.legado.app.utils.openOutputStream
import io.legado.app.utils.outputStream
import io.legado.app.utils.parseToUri
import io.legado.app.utils.postEvent
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.readBytes
import io.legado.app.utils.readUri
import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -168,7 +189,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
if (i >= 0) { if (i >= 0) {
ReadBookConfig.durConfig = defaultConfigs[i].copy() ReadBookConfig.durConfig = defaultConfigs[i].copy()
initData() initData()
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5))
} }
} }
} }
@@ -178,7 +199,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
} }
binding.swUnderline.setOnCheckedChangeListener { _, isChecked -> binding.swUnderline.setOnCheckedChangeListener { _, isChecked ->
underline = isChecked underline = isChecked
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(9))
} }
binding.tvTextColor.setOnClickListener { binding.tvTextColor.setOnClickListener {
ColorPickerDialog.newBuilder() ColorPickerDialog.newBuilder()
@@ -214,7 +235,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
} }
binding.ivDelete.setOnClickListener { binding.ivDelete.setOnClickListener {
if (ReadBookConfig.deleteDur()) { if (ReadBookConfig.deleteDur()) {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5))
dismissAllowingStateLoss() dismissAllowingStateLoss()
} else { } else {
toastOnUi("数量已是最少,不能删除.") toastOnUi("数量已是最少,不能删除.")
@@ -223,11 +244,11 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
binding.sbBgAlpha.setOnSeekBarChangeListener(object : SeekBarChangeListener { binding.sbBgAlpha.setOnSeekBarChangeListener(object : SeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
ReadBookConfig.bgAlpha = progress ReadBookConfig.bgAlpha = progress
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(3))
} }
override fun onStopTrackingTouch(seekBar: SeekBar) { override fun onStopTrackingTouch(seekBar: SeekBar) {
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(3))
} }
}) })
} }
@@ -357,7 +378,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
ReadBookConfig.import(byteArray).getOrThrow() ReadBookConfig.import(byteArray).getOrThrow()
}.onSuccess { }.onSuccess {
ReadBookConfig.durConfig = it ReadBookConfig.durConfig = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5))
toastOnUi("导入成功") toastOnUi("导入成功")
}.onError { }.onError {
it.printOnDebug() it.printOnDebug()
@@ -378,7 +399,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
} }
ReadBookConfig.durConfig.setCurBg(2, fileName) ReadBookConfig.durConfig.setCurBg(2, fileName)
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(1))
}.onFailure { }.onFailure {
appCtx.toastOnUi(it.localizedMessage) appCtx.toastOnUi(it.localizedMessage)
} }
@@ -107,39 +107,48 @@ class MoreConfigDialog : DialogFragment() {
PreferKey.readBodyToLh -> activity?.recreate() PreferKey.readBodyToLh -> activity?.recreate()
PreferKey.hideStatusBar -> { PreferKey.hideStatusBar -> {
ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(0))
} }
PreferKey.hideNavigationBar -> { PreferKey.hideNavigationBar -> {
ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar) ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(0))
} }
PreferKey.keepLight -> postEvent(key, true) PreferKey.keepLight -> postEvent(key, true)
PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key))
PreferKey.screenOrientation -> { PreferKey.screenOrientation -> {
(activity as? ReadBookActivity)?.setOrientation() (activity as? ReadBookActivity)?.setOrientation()
} }
PreferKey.textFullJustify, PreferKey.textFullJustify,
PreferKey.textBottomJustify, PreferKey.textBottomJustify,
PreferKey.useZhLayout -> { PreferKey.useZhLayout -> {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(5))
} }
PreferKey.showBrightnessView -> { PreferKey.showBrightnessView -> {
postEvent(PreferKey.showBrightnessView, "") postEvent(PreferKey.showBrightnessView, "")
} }
PreferKey.expandTextMenu -> { PreferKey.expandTextMenu -> {
(activity as? ReadBookActivity)?.textActionMenu?.upMenu() (activity as? ReadBookActivity)?.textActionMenu?.upMenu()
} }
PreferKey.doublePageHorizontal -> { PreferKey.doublePageHorizontal -> {
ChapterProvider.upLayout() ChapterProvider.upLayout()
ReadBook.loadContent(false) ReadBook.loadContent(false)
} }
PreferKey.showReadTitleAddition, PreferKey.showReadTitleAddition,
PreferKey.readBarStyleFollowPage -> { PreferKey.readBarStyleFollowPage -> {
postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)
} }
PreferKey.progressBarBehavior -> { PreferKey.progressBarBehavior -> {
postEvent(EventBus.UP_SEEK_BAR, true) postEvent(EventBus.UP_SEEK_BAR, true)
} }
PreferKey.noAnimScrollPage -> { PreferKey.noAnimScrollPage -> {
ReadBook.callBack?.upPageAnim() ReadBook.callBack?.upPageAnim()
} }
@@ -152,6 +161,7 @@ class MoreConfigDialog : DialogFragment() {
"clickRegionalConfig" -> { "clickRegionalConfig" -> {
(activity as? ReadBookActivity)?.showClickRegionalConfig() (activity as? ReadBookActivity)?.showClickRegionalConfig()
} }
PreferKey.pageTouchSlop -> { PreferKey.pageTouchSlop -> {
NumberPickerDialog(requireContext()) NumberPickerDialog(requireContext())
.setTitle(getString(R.string.page_touch_slop_dialog_title)) .setTitle(getString(R.string.page_touch_slop_dialog_title))
@@ -160,7 +170,7 @@ class MoreConfigDialog : DialogFragment() {
.setValue(AppConfig.pageTouchSlop) .setValue(AppConfig.pageTouchSlop)
.show { .show {
AppConfig.pageTouchSlop = it AppConfig.pageTouchSlop = it
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, arrayOf(4))
} }
} }
} }
@@ -63,61 +63,61 @@ class PaddingConfigDialog : BaseDialogFragment(R.layout.dialog_read_padding) {
//正文 //正文
dsbPaddingTop.onChanged = { dsbPaddingTop.onChanged = {
ReadBookConfig.paddingTop = it ReadBookConfig.paddingTop = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(10, 5))
} }
dsbPaddingBottom.onChanged = { dsbPaddingBottom.onChanged = {
ReadBookConfig.paddingBottom = it ReadBookConfig.paddingBottom = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(10, 5))
} }
dsbPaddingLeft.onChanged = { dsbPaddingLeft.onChanged = {
ReadBookConfig.paddingLeft = it ReadBookConfig.paddingLeft = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(10, 5))
} }
dsbPaddingRight.onChanged = { dsbPaddingRight.onChanged = {
ReadBookConfig.paddingRight = it ReadBookConfig.paddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(10, 5))
} }
//页眉 //页眉
dsbHeaderPaddingTop.onChanged = { dsbHeaderPaddingTop.onChanged = {
ReadBookConfig.headerPaddingTop = it ReadBookConfig.headerPaddingTop = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
dsbHeaderPaddingBottom.onChanged = { dsbHeaderPaddingBottom.onChanged = {
ReadBookConfig.headerPaddingBottom = it ReadBookConfig.headerPaddingBottom = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
dsbHeaderPaddingLeft.onChanged = { dsbHeaderPaddingLeft.onChanged = {
ReadBookConfig.headerPaddingLeft = it ReadBookConfig.headerPaddingLeft = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
dsbHeaderPaddingRight.onChanged = { dsbHeaderPaddingRight.onChanged = {
ReadBookConfig.headerPaddingRight = it ReadBookConfig.headerPaddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
//页脚 //页脚
dsbFooterPaddingTop.onChanged = { dsbFooterPaddingTop.onChanged = {
ReadBookConfig.footerPaddingTop = it ReadBookConfig.footerPaddingTop = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
dsbFooterPaddingBottom.onChanged = { dsbFooterPaddingBottom.onChanged = {
ReadBookConfig.footerPaddingBottom = it ReadBookConfig.footerPaddingBottom = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
dsbFooterPaddingLeft.onChanged = { dsbFooterPaddingLeft.onChanged = {
ReadBookConfig.footerPaddingLeft = it ReadBookConfig.footerPaddingLeft = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
dsbFooterPaddingRight.onChanged = { dsbFooterPaddingRight.onChanged = {
ReadBookConfig.footerPaddingRight = it ReadBookConfig.footerPaddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
cbShowTopLine.onCheckedChangeListener = { _, isChecked -> cbShowTopLine.onCheckedChangeListener = { _, isChecked ->
ReadBookConfig.showHeaderLine = isChecked ReadBookConfig.showHeaderLine = isChecked
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
cbShowBottomLine.onCheckedChangeListener = { _, isChecked -> cbShowBottomLine.onCheckedChangeListener = { _, isChecked ->
ReadBookConfig.showFooterLine = isChecked ReadBookConfig.showFooterLine = isChecked
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
@@ -108,10 +108,10 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),
private fun initViewEvent() = binding.run { private fun initViewEvent() = binding.run {
chineseConverter.onChanged { chineseConverter.onChanged {
ChineseUtils.unLoad(*TransType.entries.toTypedArray()) ChineseUtils.unLoad(*TransType.entries.toTypedArray())
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(5))
} }
textFontWeightConverter.onChanged { textFontWeightConverter.onChanged {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8))
} }
tvTextFont.setOnClickListener { tvTextFont.setOnClickListener {
showDialogFragment<FontSelectDialog>() showDialogFragment<FontSelectDialog>()
@@ -122,7 +122,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),
items = resources.getStringArray(R.array.indent).toList() items = resources.getStringArray(R.array.indent).toList()
) { _, index -> ) { _, index ->
ReadBookConfig.paragraphIndent = " ".repeat(index) ReadBookConfig.paragraphIndent = " ".repeat(index)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(5))
} }
} }
tvPadding.setOnClickListener { tvPadding.setOnClickListener {
@@ -141,40 +141,40 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),
cbShareLayout.onCheckedChangeListener = { _, isChecked -> cbShareLayout.onCheckedChangeListener = { _, isChecked ->
ReadBookConfig.shareLayout = isChecked ReadBookConfig.shareLayout = isChecked
upView() upView()
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5))
} }
dsbTextSize.onChanged = { dsbTextSize.onChanged = {
ReadBookConfig.textSize = it + 5 ReadBookConfig.textSize = it + 5
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
dsbTextLetterSpacing.onChanged = { dsbTextLetterSpacing.onChanged = {
ReadBookConfig.letterSpacing = (it - 50) / 100f ReadBookConfig.letterSpacing = (it - 50) / 100f
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
dsbLineSize.onChanged = { dsbLineSize.onChanged = {
ReadBookConfig.lineSpacingExtra = it ReadBookConfig.lineSpacingExtra = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
dsbParagraphSpacing.onChanged = { dsbParagraphSpacing.onChanged = {
ReadBookConfig.paragraphSpacing = it ReadBookConfig.paragraphSpacing = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
} }
private fun changeBg(index: Int) { private fun changeBgTextConfig(index: Int) {
val oldIndex = ReadBookConfig.styleSelect val oldIndex = ReadBookConfig.styleSelect
if (index != oldIndex) { if (index != oldIndex) {
ReadBookConfig.styleSelect = index ReadBookConfig.styleSelect = index
upView() upView()
styleAdapter.notifyItemChanged(oldIndex) styleAdapter.notifyItemChanged(oldIndex)
styleAdapter.notifyItemChanged(index) styleAdapter.notifyItemChanged(index)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5))
} }
} }
private fun showBgTextConfig(index: Int): Boolean { private fun showBgTextConfig(index: Int): Boolean {
dismissAllowingStateLoss() dismissAllowingStateLoss()
changeBg(index) changeBgTextConfig(index)
callBack?.showBgTextConfig() callBack?.showBgTextConfig()
return true return true
} }
@@ -200,7 +200,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),
override fun selectFont(path: String) { override fun selectFont(path: String) {
if (path != ReadBookConfig.textFont) { if (path != ReadBookConfig.textFont) {
ReadBookConfig.textFont = path ReadBookConfig.textFont = path
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
} }
@@ -235,7 +235,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),
binding.apply { binding.apply {
ivStyle.setOnClickListener { ivStyle.setOnClickListener {
if (ivStyle.isInView) { if (ivStyle.isInView) {
changeBg(holder.layoutPosition) changeBgTextConfig(holder.layoutPosition)
} }
} }
ivStyle.onLongClick(ivStyle.isInView) { ivStyle.onLongClick(ivStyle.isInView) {
@@ -11,7 +11,12 @@ import io.legado.app.databinding.DialogTipConfigBinding
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.config.ReadTipConfig import io.legado.app.help.config.ReadTipConfig
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.utils.* import io.legado.app.utils.checkByIndex
import io.legado.app.utils.getIndexById
import io.legado.app.utils.hexString
import io.legado.app.utils.observeEvent
import io.legado.app.utils.postEvent
import io.legado.app.utils.setLayout
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -89,26 +94,26 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
private fun initEvent() = binding.run { private fun initEvent() = binding.run {
rgTitleMode.setOnCheckedChangeListener { _, checkedId -> rgTitleMode.setOnCheckedChangeListener { _, checkedId ->
ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId) ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(5))
} }
dsbTitleSize.onChanged = { dsbTitleSize.onChanged = {
ReadBookConfig.titleSize = it ReadBookConfig.titleSize = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
dsbTitleTop.onChanged = { dsbTitleTop.onChanged = {
ReadBookConfig.titleTopSpacing = it ReadBookConfig.titleTopSpacing = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
dsbTitleBottom.onChanged = { dsbTitleBottom.onChanged = {
ReadBookConfig.titleBottomSpacing = it ReadBookConfig.titleBottomSpacing = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(8, 5))
} }
llHeaderShow.setOnClickListener { llHeaderShow.setOnClickListener {
val headerModes = ReadTipConfig.getHeaderModes(requireContext()) val headerModes = ReadTipConfig.getHeaderModes(requireContext())
context?.selector(items = headerModes.values.toList()) { _, i -> context?.selector(items = headerModes.values.toList()) { _, i ->
ReadTipConfig.headerMode = headerModes.keys.toList()[i] ReadTipConfig.headerMode = headerModes.keys.toList()[i]
tvHeaderShow.text = headerModes[ReadTipConfig.headerMode] tvHeaderShow.text = headerModes[ReadTipConfig.headerMode]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llFooterShow.setOnClickListener { llFooterShow.setOnClickListener {
@@ -116,7 +121,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
context?.selector(items = footerModes.values.toList()) { _, i -> context?.selector(items = footerModes.values.toList()) { _, i ->
ReadTipConfig.footerMode = footerModes.keys.toList()[i] ReadTipConfig.footerMode = footerModes.keys.toList()[i]
tvFooterShow.text = footerModes[ReadTipConfig.footerMode] tvFooterShow.text = footerModes[ReadTipConfig.footerMode]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llHeaderLeft.setOnClickListener { llHeaderLeft.setOnClickListener {
@@ -125,7 +130,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
clearRepeat(tipValue) clearRepeat(tipValue)
ReadTipConfig.tipHeaderLeft = tipValue ReadTipConfig.tipHeaderLeft = tipValue
tvHeaderLeft.text = ReadTipConfig.tipNames[i] tvHeaderLeft.text = ReadTipConfig.tipNames[i]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llHeaderMiddle.setOnClickListener { llHeaderMiddle.setOnClickListener {
@@ -134,7 +139,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
clearRepeat(tipValue) clearRepeat(tipValue)
ReadTipConfig.tipHeaderMiddle = tipValue ReadTipConfig.tipHeaderMiddle = tipValue
tvHeaderMiddle.text = ReadTipConfig.tipNames[i] tvHeaderMiddle.text = ReadTipConfig.tipNames[i]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llHeaderRight.setOnClickListener { llHeaderRight.setOnClickListener {
@@ -143,7 +148,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
clearRepeat(tipValue) clearRepeat(tipValue)
ReadTipConfig.tipHeaderRight = tipValue ReadTipConfig.tipHeaderRight = tipValue
tvHeaderRight.text = ReadTipConfig.tipNames[i] tvHeaderRight.text = ReadTipConfig.tipNames[i]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llFooterLeft.setOnClickListener { llFooterLeft.setOnClickListener {
@@ -152,7 +157,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
clearRepeat(tipValue) clearRepeat(tipValue)
ReadTipConfig.tipFooterLeft = tipValue ReadTipConfig.tipFooterLeft = tipValue
tvFooterLeft.text = ReadTipConfig.tipNames[i] tvFooterLeft.text = ReadTipConfig.tipNames[i]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llFooterMiddle.setOnClickListener { llFooterMiddle.setOnClickListener {
@@ -161,7 +166,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
clearRepeat(tipValue) clearRepeat(tipValue)
ReadTipConfig.tipFooterMiddle = tipValue ReadTipConfig.tipFooterMiddle = tipValue
tvFooterMiddle.text = ReadTipConfig.tipNames[i] tvFooterMiddle.text = ReadTipConfig.tipNames[i]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llFooterRight.setOnClickListener { llFooterRight.setOnClickListener {
@@ -170,7 +175,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
clearRepeat(tipValue) clearRepeat(tipValue)
ReadTipConfig.tipFooterRight = tipValue ReadTipConfig.tipFooterRight = tipValue
tvFooterRight.text = ReadTipConfig.tipNames[i] tvFooterRight.text = ReadTipConfig.tipNames[i]
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
llTipColor.setOnClickListener { llTipColor.setOnClickListener {
@@ -179,7 +184,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
0 -> { 0 -> {
ReadTipConfig.tipColor = 0 ReadTipConfig.tipColor = 0
upTvTipColor() upTvTipColor()
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
1 -> ColorPickerDialog.newBuilder() 1 -> ColorPickerDialog.newBuilder()
.setShowAlphaSlider(false) .setShowAlphaSlider(false)
@@ -195,7 +200,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
0, 1 -> { 0, 1 -> {
ReadTipConfig.tipDividerColor = i - 1 ReadTipConfig.tipDividerColor = i - 1
upTvTipDividerColor() upTvTipDividerColor()
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
2 -> ColorPickerDialog.newBuilder() 2 -> ColorPickerDialog.newBuilder()
.setShowAlphaSlider(false) .setShowAlphaSlider(false)
@@ -82,7 +82,6 @@ class AutoPager(private val readView: ReadView) {
} }
if (readView.isScroll) { if (readView.isScroll) {
computeOffset()
if (!isPausing) readView.curPage.scroll(-scrollOffset) if (!isPausing) readView.curPage.scroll(-scrollOffset)
} else { } else {
val bottom = progress val bottom = progress
@@ -112,13 +111,15 @@ class AutoPager(private val readView: ReadView) {
bottom.toFloat(), bottom.toFloat(),
paint paint
) )
if (!isPausing) readView.invalidate() if (!isPausing) readView.postInvalidate()
computeOffset()
} }
} }
private fun computeOffset() { fun computeOffset() {
if (!isRunning) {
return
}
val currentTime = SystemClock.uptimeMillis() val currentTime = SystemClock.uptimeMillis()
val elapsedTime = currentTime - lastTimeMillis val elapsedTime = currentTime - lastTimeMillis
@@ -22,7 +22,6 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.widget.dialog.PhotoDialog import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.PictureMirror
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
import io.legado.app.utils.showDialogFragment import io.legado.app.utils.showDialogFragment
@@ -57,6 +56,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private val pageDelegate get() = callBack.pageDelegate private val pageDelegate get() = callBack.pageDelegate
private var pageOffset = 0 private var pageOffset = 0
private var autoPager: AutoPager? = null private var autoPager: AutoPager? = null
private var isScroll = false
private val renderRunnable by lazy { Runnable { preRenderPage() } } private val renderRunnable by lazy { Runnable { preRenderPage() } }
//绘制图片的paint //绘制图片的paint
@@ -75,14 +75,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
*/ */
fun setContent(textPage: TextPage) { fun setContent(textPage: TextPage) {
this.textPage = textPage this.textPage = textPage
imagePaint.isAntiAlias = AppConfig.useAntiAlias // 非滑动翻页动画需要同步重绘,不然翻页可能会出现闪烁
invalidate() if (isScroll) {
postInvalidate()
} else {
invalidate()
}
} }
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh) super.onSizeChanged(w, h, oldw, oldh)
if (!isMainView) return ChapterProvider.upViewSize(w, h, isMainView)
ChapterProvider.upViewSize(w, h)
textPage.format() textPage.format()
} }
@@ -107,16 +110,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
//滚动翻页 //滚动翻页
if (!pageFactory.hasNext()) return if (!pageFactory.hasNext()) return
val textPage1 = relativePage(1) val textPage1 = relativePage(1)
relativeOffset = relativeOffset(1) relativeOffset += textPage.height
textPage1.draw(this, canvas, relativeOffset) textPage1.draw(this, canvas, relativeOffset)
if (!pageFactory.hasNextPlus()) return if (!pageFactory.hasNextPlus()) return
relativeOffset = relativeOffset(2) relativeOffset += textPage1.height
if (relativeOffset < ChapterProvider.visibleHeight) { if (relativeOffset < ChapterProvider.visibleHeight) {
val textPage2 = relativePage(2) val textPage2 = relativePage(2)
textPage2.draw(this, canvas, relativeOffset) textPage2.draw(this, canvas, relativeOffset)
} }
} }
override fun computeScroll() {
pageDelegate?.computeScroll()
autoPager?.computeOffset()
}
/** /**
* 滚动事件 * 滚动事件
* pageOffset 向上滚动 减小 向下滚动 增大 * pageOffset 向上滚动 减小 向下滚动 增大
@@ -125,7 +133,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
* pageOffset + textPage.height 为 textPage 下方的高度 * pageOffset + textPage.height 为 textPage 下方的高度
*/ */
fun scroll(mOffset: Int) { fun scroll(mOffset: Int) {
if (mOffset == 0) return
pageOffset += mOffset pageOffset += mOffset
if (longScreenshot) { if (longScreenshot) {
scrollY += -mOffset scrollY += -mOffset
@@ -156,33 +163,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
pageDelegate?.abortAnim() pageDelegate?.abortAnim()
} }
} }
invalidate() postInvalidate()
} }
fun submitPreRenderTask() { fun submitRenderTask() {
if (PictureMirror.atLeastApi23) { renderThread.submit(renderRunnable)
renderThread.submit(renderRunnable)
}
} }
private fun preRenderPage() { private fun preRenderPage() {
val view = this val view = this
var invalidate = false var invalidate = false
pageFactory.run { pageFactory.run {
hasPrev() && prevPage.preRender(view) if (hasPrev() && prevPage.render(view)) {
if (curPage.preRender(view)) {
invalidate = true invalidate = true
} }
if (hasNext() && nextPage.preRender(view) && callBack.isScroll) { if (curPage.render(view)) {
invalidate = true invalidate = true
} }
if (hasNextPlus() && nextPlusPage.preRender(view) && callBack.isScroll if (hasNext() && nextPage.render(view) && callBack.isScroll) {
invalidate = true
}
if (hasNextPlus() && nextPlusPage.render(view) && callBack.isScroll
&& relativeOffset(2) < ChapterProvider.visibleHeight && relativeOffset(2) < ChapterProvider.visibleHeight
) { ) {
invalidate = true invalidate = true
} }
if (invalidate) { if (invalidate) {
postInvalidate() postInvalidate()
pageDelegate?.postInvalidate()
} }
} }
} }
@@ -254,7 +262,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
touchRough(x, y) { _, textPos, _, _, column -> touchRough(x, y) { _, textPos, _, _, column ->
if (column is TextColumn) { if (column is TextColumn) {
column.selected = true column.selected = true
invalidate()
select(textPos) select(textPos)
} }
} }
@@ -557,7 +564,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
} }
} }
invalidate() // 由后台线程完成渲染后通知视图重绘
submitRenderTask()
} }
private fun upSelectedStart(x: Float, y: Float, top: Float) { private fun upSelectedStart(x: Float, y: Float, top: Float) {
@@ -681,6 +689,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
this.autoPager = autoPager this.autoPager = autoPager
} }
fun setIsScroll(value: Boolean) {
isScroll = value
}
override fun canScrollVertically(direction: Int): Boolean { override fun canScrollVertically(direction: Int): Boolean {
return callBack.isScroll && pageFactory.hasNext() return callBack.isScroll && pageFactory.hasNext()
} }
@@ -321,7 +321,12 @@ class PageView(context: Context) : FrameLayout(context) {
} }
fun submitPreRenderTask() { fun submitPreRenderTask() {
binding.contentTextView.submitPreRenderTask() binding.contentTextView.submitRenderTask()
}
fun setIsScroll(value: Boolean) {
isScroll = value
binding.contentTextView.setIsScroll(value)
} }
/** /**
@@ -148,6 +148,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
override fun computeScroll() { override fun computeScroll() {
pageDelegate?.computeScroll() pageDelegate?.computeScroll()
autoPager.computeOffset()
} }
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
@@ -510,7 +511,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
} else { } else {
curPage.setAutoPager(null) curPage.setAutoPager(null)
} }
curPage.isScroll = isScroll curPage.setIsScroll(isScroll)
} }
/** /**
@@ -634,6 +635,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
nextPage.invalidateAll() nextPage.invalidateAll()
nextPlusPage.invalidateAll() nextPlusPage.invalidateAll()
} }
upContent()
} }
fun onScrollAnimStart() { fun onScrollAnimStart() {
@@ -1,10 +1,7 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Picture
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Build
import androidx.core.graphics.withClip import androidx.core.graphics.withClip
import androidx.core.graphics.withTranslation import androidx.core.graphics.withTranslation
import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.ReadView
@@ -12,26 +9,14 @@ import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.screenshot import io.legado.app.utils.screenshot
class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
private val bitmapMatrix = Matrix()
private val shadowDrawableR: GradientDrawable private val shadowDrawableR: GradientDrawable
private lateinit var curPicture: Picture
private lateinit var prevPicture: Picture
private lateinit var nextPicture: Picture
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
init { init {
val shadowColors = intArrayOf(0x66111111, 0x00000000) val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawableR = GradientDrawable( shadowDrawableR = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
) )
shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT
if (atLeastApi23) {
curPicture = Picture()
prevPicture = Picture()
nextPicture = Picture()
}
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
@@ -47,42 +32,21 @@ class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
if (mDirection == PageDirection.PREV) { if (mDirection == PageDirection.PREV) {
if (offsetX <= viewWidth) { if (offsetX <= viewWidth) {
if (!atLeastApi23) { canvas.withTranslation(distanceX) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat()) prevRecorder.draw(canvas)
prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
} else {
canvas.withTranslation(distanceX) {
drawPicture(prevPicture)
}
} }
addShadow(distanceX, canvas) addShadow(distanceX, canvas)
} else { } else {
if (!atLeastApi23) { prevRecorder.draw(canvas)
prevBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
} else {
canvas.drawPicture(prevPicture)
}
} }
} else if (mDirection == PageDirection.NEXT) { } else if (mDirection == PageDirection.NEXT) {
if (!atLeastApi23) { val width = nextRecorder.width.toFloat()
bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) val height = nextRecorder.height.toFloat()
nextBitmap?.let { canvas.withClip(width + offsetX, 0f, width, height) {
val width = it.width.toFloat() nextRecorder.draw(this)
val height = it.height.toFloat() }
canvas.withClip(width + offsetX, 0f, width, height) { canvas.withTranslation(distanceX - viewWidth) {
drawBitmap(it, 0f, 0f, null) curRecorder.draw(this)
}
}
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
} else {
val width = nextPicture.width.toFloat()
val height = nextPicture.height.toFloat()
canvas.withClip(width + offsetX, 0f, width, height) {
drawPicture(nextPicture)
}
canvas.withTranslation(distanceX - viewWidth) {
drawPicture(curPicture)
}
} }
addShadow(distanceX, canvas) addShadow(distanceX, canvas)
} }
@@ -90,18 +54,13 @@ class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
override fun setBitmap() { override fun setBitmap() {
when (mDirection) { when (mDirection) {
PageDirection.PREV -> if (!atLeastApi23) { PageDirection.PREV -> {
prevBitmap = prevPage.screenshot(prevBitmap, canvas) prevPage.screenshot(prevRecorder)
} else {
prevPage.screenshot(prevPicture)
} }
PageDirection.NEXT -> if (!atLeastApi23) { PageDirection.NEXT -> {
nextBitmap = nextPage.screenshot(nextBitmap, canvas) nextPage.screenshot(nextRecorder)
curBitmap = curPage.screenshot(curBitmap, canvas) curPage.screenshot(curRecorder)
} else {
nextPage.screenshot(nextPicture)
curPage.screenshot(curPicture)
} }
else -> Unit else -> Unit
@@ -1,18 +1,16 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Bitmap
import android.graphics.Canvas
import android.view.MotionEvent import android.view.MotionEvent
import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.screenshot import io.legado.app.utils.screenshot
abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readView) { abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readView) {
protected var curBitmap: Bitmap? = null protected val curRecorder = CanvasRecorderFactory.create()
protected var prevBitmap: Bitmap? = null protected val prevRecorder = CanvasRecorderFactory.create()
protected var nextBitmap: Bitmap? = null protected val nextRecorder = CanvasRecorderFactory.create()
protected var canvas: Canvas = Canvas()
private val slopSquare get() = readView.pageSlopSquare2 private val slopSquare get() = readView.pageSlopSquare2
override fun setDirection(direction: PageDirection) { override fun setDirection(direction: PageDirection) {
@@ -23,13 +21,13 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
open fun setBitmap() { open fun setBitmap() {
when (mDirection) { when (mDirection) {
PageDirection.PREV -> { PageDirection.PREV -> {
prevBitmap = prevPage.screenshot(prevBitmap, canvas) prevPage.screenshot(prevRecorder)
curBitmap = curPage.screenshot(curBitmap, canvas) curPage.screenshot(curRecorder)
} }
PageDirection.NEXT -> { PageDirection.NEXT -> {
nextBitmap = nextPage.screenshot(nextBitmap, canvas) nextPage.screenshot(nextRecorder)
curBitmap = curPage.screenshot(curBitmap, canvas) curPage.screenshot(curRecorder)
} }
else -> Unit else -> Unit
@@ -140,12 +138,9 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
prevBitmap?.recycle() prevRecorder.recycle()
prevBitmap = null curRecorder.recycle()
curBitmap?.recycle() nextRecorder.recycle()
curBitmap = null
nextBitmap?.recycle()
nextBitmap = null
} }
} }
@@ -96,7 +96,7 @@ abstract class PageDelegate(protected val readView: ReadView) {
viewHeight = height viewHeight = height
} }
fun computeScroll() { open fun computeScroll() {
if (scroller.computeScrollOffset()) { if (scroller.computeScrollOffset()) {
readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat())
} else if (isStarted) { } else if (isStarted) {
@@ -190,6 +190,15 @@ abstract class PageDelegate(protected val readView: ReadView) {
} }
} }
fun postInvalidate() {
if (isRunning && this is HorizontalPageDelegate) {
readView.post {
setBitmap()
readView.invalidate()
}
}
}
open fun onDestroy() { open fun onDestroy() {
// run on destroy // run on destroy
} }
@@ -79,7 +79,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {
val pointX = event.getX(event.pointerCount - 1) val pointX = event.getX(event.pointerCount - 1)
val pointY = event.getY(event.pointerCount - 1) val pointY = event.getY(event.pointerCount - 1)
if (isMoved) { if (isMoved) {
readView.setTouchPoint(pointX, pointY) readView.setTouchPoint(pointX, pointY, false)
} }
if (!isMoved) { if (!isMoved) {
val deltaX = (pointX - startX).toInt() val deltaX = (pointX - startX).toInt()
@@ -95,12 +95,22 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {
} }
} }
override fun computeScroll() {
if (scroller.computeScrollOffset()) {
readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat(), false)
} else if (isStarted) {
onAnimStop()
stopScroll()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
mVelocity.recycle() mVelocity.recycle()
} }
override fun abortAnim() { override fun abortAnim() {
readView.onScrollAnimStop()
isStarted = false isStarted = false
isMoved = false isMoved = false
isRunning = false isRunning = false
@@ -1,13 +1,27 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.* import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PointF
import android.graphics.Region
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Build import android.os.Build
import android.view.MotionEvent import android.view.MotionEvent
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import kotlin.math.* import io.legado.app.utils.screenshot
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.hypot
import kotlin.math.min
import kotlin.math.sin
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
@@ -86,6 +100,11 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi
private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL } private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL }
private var curBitmap: Bitmap? = null
private var prevBitmap: Bitmap? = null
private var nextBitmap: Bitmap? = null
private var canvas: Canvas = Canvas()
init { init {
//设置颜色数组 //设置颜色数组
val color = intArrayOf(0x333333, -0x4fcccccd) val color = intArrayOf(0x333333, -0x4fcccccd)
@@ -122,6 +141,22 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi
mFrontShadowDrawableHBT.gradientType = GradientDrawable.LINEAR_GRADIENT mFrontShadowDrawableHBT.gradientType = GradientDrawable.LINEAR_GRADIENT
} }
override fun setBitmap() {
when (mDirection) {
PageDirection.PREV -> {
prevBitmap = prevPage.screenshot(prevBitmap, canvas)
curBitmap = curPage.screenshot(curBitmap, canvas)
}
PageDirection.NEXT -> {
nextBitmap = nextPage.screenshot(nextBitmap, canvas)
curBitmap = curPage.screenshot(curBitmap, canvas)
}
else -> Unit
}
}
override fun setViewSize(width: Int, height: Int) { override fun setViewSize(width: Int, height: Int) {
super.setViewSize(width, height) super.setViewSize(width, height)
mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat()
@@ -133,6 +168,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
calcCornerXY(event.x, event.y) calcCornerXY(event.x, event.y)
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3) if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3)
|| mDirection == PageDirection.PREV || mDirection == PageDirection.PREV
@@ -159,10 +195,12 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi
} else { } else {
calcCornerXY(viewWidth - startX, viewHeight.toFloat()) calcCornerXY(viewWidth - startX, viewHeight.toFloat())
} }
PageDirection.NEXT -> PageDirection.NEXT ->
if (viewWidth / 2 > startX) { if (viewWidth / 2 > startX) {
calcCornerXY(viewWidth - startX, startY) calcCornerXY(viewWidth - startX, startY)
} }
else -> Unit else -> Unit
} }
} }
@@ -216,6 +254,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi
drawCurrentPageShadow(canvas) drawCurrentPageShadow(canvas)
drawCurrentBackArea(canvas, curBitmap) drawCurrentBackArea(canvas, curBitmap)
} }
PageDirection.PREV -> { PageDirection.PREV -> {
calcPoints() calcPoints()
drawCurrentPageArea(canvas, prevBitmap) drawCurrentPageArea(canvas, prevBitmap)
@@ -223,6 +262,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi
drawCurrentPageShadow(canvas) drawCurrentPageShadow(canvas)
drawCurrentBackArea(canvas, prevBitmap) drawCurrentBackArea(canvas, prevBitmap)
} }
else -> return else -> return
} }
} }
@@ -1,51 +1,12 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Picture
import android.os.Build
import androidx.core.graphics.withTranslation import androidx.core.graphics.withTranslation
import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.screenshot
class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
private val bitmapMatrix = Matrix()
private lateinit var curPicture: Picture
private lateinit var prevPicture: Picture
private lateinit var nextPicture: Picture
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
init {
if (atLeastApi23) {
curPicture = Picture()
prevPicture = Picture()
nextPicture = Picture()
}
}
override fun setBitmap() {
if (!atLeastApi23) {
return super.setBitmap()
}
when (mDirection) {
PageDirection.PREV -> {
prevPage.screenshot(prevPicture)
curPage.screenshot(curPicture)
}
PageDirection.NEXT -> {
nextPage.screenshot(nextPicture)
curPage.screenshot(curPicture)
}
else -> Unit
}
}
override fun onAnimStart(animationSpeed: Int) { override fun onAnimStart(animationSpeed: Int) {
val distanceX: Float val distanceX: Float
when (mDirection) { when (mDirection) {
@@ -79,32 +40,18 @@ class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
if (!isRunning) return if (!isRunning) return
if (mDirection == PageDirection.PREV) { if (mDirection == PageDirection.PREV) {
if (!atLeastApi23) { canvas.withTranslation(distanceX + viewWidth) {
bitmapMatrix.setTranslate(distanceX + viewWidth, 0.toFloat()) curRecorder.draw(this)
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } }
bitmapMatrix.setTranslate(distanceX, 0.toFloat()) canvas.withTranslation(distanceX) {
prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } prevRecorder.draw(this)
} else {
canvas.withTranslation(distanceX + viewWidth) {
drawPicture(curPicture)
}
canvas.withTranslation(distanceX) {
drawPicture(prevPicture)
}
} }
} else if (mDirection == PageDirection.NEXT) { } else if (mDirection == PageDirection.NEXT) {
if (!atLeastApi23) { canvas.withTranslation(distanceX) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat()) nextRecorder.draw(this)
nextBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } }
bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) canvas.withTranslation(distanceX - viewWidth) {
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } curRecorder.draw(this)
} else {
canvas.withTranslation(distanceX) {
drawPicture(nextPicture)
}
canvas.withTranslation(distanceX - viewWidth) {
drawPicture(curPicture)
}
} }
} }
} }
@@ -10,7 +10,8 @@ import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage
import io.legado.app.ui.book.read.page.entities.column.BaseColumn import io.legado.app.ui.book.read.page.entities.column.BaseColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.utils.PictureMirror import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
/** /**
@@ -39,7 +40,7 @@ data class TextLine(
val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f
val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize
val height: Float inline get() = lineBottom - lineTop val height: Float inline get() = lineBottom - lineTop
val pictureMirror: PictureMirror = PictureMirror() val canvasRecorder = CanvasRecorderFactory.create()
var isReadAloud: Boolean = false var isReadAloud: Boolean = false
set(value) { set(value) {
if (field != value) { if (field != value) {
@@ -122,7 +123,7 @@ data class TextLine(
} }
fun draw(view: ContentTextView, canvas: Canvas) { fun draw(view: ContentTextView, canvas: Canvas) {
pictureMirror.draw(canvas, view.width, height.toInt()) { canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) {
drawTextLine(view, this) drawTextLine(view, this)
} }
} }
@@ -156,11 +157,11 @@ data class TextLine(
} }
fun invalidateSelf() { fun invalidateSelf() {
pictureMirror.invalidate() canvasRecorder.invalidate()
} }
fun recyclePicture() { fun recycleRecorder() {
pictureMirror.recycle() canvasRecorder.recycle()
} }
companion object { companion object {
@@ -11,7 +11,8 @@ import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.utils.PictureMirror import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeeded
import splitties.init.appCtx import splitties.init.appCtx
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.min import kotlin.math.min
@@ -43,7 +44,7 @@ data class TextPage(
val charSize: Int get() = text.length.coerceAtLeast(1) val charSize: Int get() = text.length.coerceAtLeast(1)
val searchResult = hashSetOf<TextColumn>() val searchResult = hashSetOf<TextColumn>()
var isMsgPage: Boolean = false var isMsgPage: Boolean = false
var pictureMirror: PictureMirror = PictureMirror() var canvasRecorder = CanvasRecorderFactory.create(true)
var doublePage = false var doublePage = false
var paddingTop = 0 var paddingTop = 0
@@ -267,11 +268,9 @@ data class TextPage(
} }
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
val height = height.toInt() render(view)
canvas.withTranslation(0f, relativeOffset + paddingTop) { canvas.withTranslation(0f, relativeOffset + paddingTop) {
pictureMirror.drawLocked(canvas, view.width, height) { canvasRecorder.draw(this)
drawPage(view, this)
}
} }
} }
@@ -284,20 +283,14 @@ data class TextPage(
} }
} }
fun preRender(view: ContentTextView): Boolean { fun render(view: ContentTextView): Boolean {
if (!pictureMirror.isDirty) return false return canvasRecorder.recordIfNeeded(view.width, height.toInt()) {
pictureMirror.drawLocked(null, view.width, height.toInt()) {
drawPage(view, this) drawPage(view, this)
} }
return true
}
fun isDirty(): Boolean {
return pictureMirror.isDirty
} }
fun invalidate() { fun invalidate() {
pictureMirror.invalidate() canvasRecorder.invalidate()
} }
fun invalidateAll() { fun invalidateAll() {
@@ -307,10 +300,10 @@ data class TextPage(
invalidate() invalidate()
} }
fun recyclePictures() { fun recycleRecorders() {
pictureMirror.recycle() canvasRecorder.recycle()
for (i in lines.indices) { for (i in lines.indices) {
lines[i].recyclePicture() lines[i].recycleRecorder()
} }
} }
@@ -42,15 +42,15 @@ data class TextColumn(
} else { } else {
ChapterProvider.contentPaint ChapterProvider.contentPaint
} }
val textColor = if (textLine.isReadAloud || isSearchResult) { if (textLine.isReadAloud || isSearchResult) {
ThemeStore.accentColor synchronized(textPaint) {
textPaint.color = ThemeStore.accentColor
canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint)
textPaint.color = ReadBookConfig.textColor
}
} else { } else {
ReadBookConfig.textColor canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint)
} }
if (textPaint.color != textColor) {
textPaint.color = textColor
}
canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint)
if (selected) { if (selected) {
canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint) canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint)
} }
@@ -690,39 +690,6 @@ object ChapterProvider {
exceed(absStartX, textLine, words) exceed(absStartX, textLine, words)
} }
fun getStringArrayAndTextWidths(
text: String,
textWidths: List<Float>,
textPaint: TextPaint
): Pair<List<String>, List<Float>> {
val charArray = text.toCharArray()
val strList = ArrayList<String>(text.length)
val textWidthList = ArrayList<Float>(text.length)
val lastIndex = charArray.lastIndex
var ca: CharArray? = null
for (i in textWidths.indices) {
if (charArray[i].isLowSurrogate()) {
continue
}
val char = if (i + 1 <= lastIndex && charArray[i + 1].isLowSurrogate()) {
if (ca == null) ca = CharArray(2)
System.arraycopy(charArray, i, ca, 0, 2)
String(ca)
} else {
charArray[i].toString()
}
val w = textWidths[i]
if (w == 0f && textWidthList.size > 0) {
textWidthList[textWidthList.lastIndex] = textPaint.measureText(strList.last())
textWidthList.add(textPaint.measureText(char))
} else {
textWidthList.add(w)
}
strList.add(char)
}
return strList to textWidthList
}
/** /**
* 添加字符 * 添加字符
*/ */
@@ -883,12 +850,12 @@ object ChapterProvider {
/** /**
* 更新View尺寸 * 更新View尺寸
*/ */
fun upViewSize(width: Int, height: Int) { fun upViewSize(width: Int, height: Int, postEvent: Boolean) {
if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) { if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) {
viewWidth = width viewWidth = width
viewHeight = height viewHeight = height
upLayout() upLayout()
postEvent(EventBus.UP_CONFIG, true) if (postEvent) postEvent(EventBus.UP_CONFIG, arrayOf(2))
} }
} }
@@ -3,13 +3,11 @@ package io.legado.app.ui.book.read.page.provider
import android.text.TextPaint import android.text.TextPaint
import android.util.SparseArray import android.util.SparseArray
import androidx.core.util.getOrDefault import androidx.core.util.getOrDefault
import java.util.BitSet
import kotlin.math.ceil import kotlin.math.ceil
class TextMeasure(private var paint: TextPaint) { class TextMeasure(private var paint: TextPaint) {
private var chineseCommonWidth = paint.measureText("") private var chineseCommonWidth = paint.measureText("")
private val chineseCommonWidthBitSet = BitSet()
private val asciiWidths = FloatArray(128) { -1f } private val asciiWidths = FloatArray(128) { -1f }
private val codePointWidths = SparseArray<Float>() private val codePointWidths = SparseArray<Float>()
@@ -17,7 +15,8 @@ class TextMeasure(private var paint: TextPaint) {
if (codePoint < 128) { if (codePoint < 128) {
return asciiWidths[codePoint] return asciiWidths[codePoint]
} }
if (chineseCommonWidthBitSet[codePoint]) { // 中文 Unicode 范围 U+4E00 - U+9FA5
if (codePoint in 19968 .. 40869) {
return chineseCommonWidth return chineseCommonWidth
} }
return codePointWidths.getOrDefault(codePoint, -1f) return codePointWidths.getOrDefault(codePoint, -1f)
@@ -33,7 +32,8 @@ class TextMeasure(private var paint: TextPaint) {
if (charArray[i].isLowSurrogate()) continue if (charArray[i].isLowSurrogate()) continue
val width = ceil(widths[i]) val width = ceil(widths[i])
widthsList.add(width) widthsList.add(width)
if (width == 0f && widthsList.size > 0) { // 可能需要检查是否不可见字符
if (width == 0f && widthsList.size > 1) {
val lastIndex = widthsList.lastIndex val lastIndex = widthsList.lastIndex
buf[0] = codePoints[lastIndex - 1] buf[0] = codePoints[lastIndex - 1]
widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1)) widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1))
@@ -46,8 +46,6 @@ class TextMeasure(private var paint: TextPaint) {
val width = widthsList[i] val width = widthsList[i]
if (codePoint < 128) { if (codePoint < 128) {
asciiWidths[codePoint] = width asciiWidths[codePoint] = width
} else if (width == chineseCommonWidth) {
chineseCommonWidthBitSet.set(codePoint)
} else { } else {
codePointWidths[codePoint] = width codePointWidths[codePoint] = width
} }
@@ -138,7 +136,6 @@ class TextMeasure(private var paint: TextPaint) {
private fun invalidate() { private fun invalidate() {
chineseCommonWidth = paint.measureText("") chineseCommonWidth = paint.measureText("")
chineseCommonWidthBitSet.clear()
codePointWidths.clear() codePointWidths.clear()
asciiWidths.fill(-1f) asciiWidths.fill(-1f)
} }
@@ -10,6 +10,8 @@ import android.text.StaticLayout
import android.util.AttributeSet import android.util.AttributeSet
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
import io.legado.app.utils.dpToPx import io.legado.app.utils.dpToPx
class BatteryView @JvmOverloads constructor( class BatteryView @JvmOverloads constructor(
@@ -22,6 +24,7 @@ class BatteryView @JvmOverloads constructor(
private val batteryPaint = Paint() private val batteryPaint = Paint()
private val outFrame = Rect() private val outFrame = Rect()
private val polar = Rect() private val polar = Rect()
private val canvasRecorder = CanvasRecorderFactory.create()
var isBattery = false var isBattery = false
set(value) { set(value) {
field = value field = value
@@ -62,31 +65,40 @@ class BatteryView @JvmOverloads constructor(
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
if (!isBattery) return super.onDraw(this)
layout.getLineBounds(0, outFrame) if (!isBattery) return@recordIfNeededThenDraw
val batteryStart = layout layout.getLineBounds(0, outFrame)
.getPrimaryHorizontal(text.length - battery.toString().length) val batteryStart = layout
.toInt() + 2.dpToPx() .getPrimaryHorizontal(text.length - battery.toString().length)
val batteryEnd = batteryStart + .toInt() + 2.dpToPx()
StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx() val batteryEnd = batteryStart +
outFrame.set( StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx()
batteryStart, outFrame.set(
2.dpToPx(), batteryStart,
batteryEnd, 2.dpToPx(),
height - 2.dpToPx() batteryEnd,
) height - 2.dpToPx()
val dj = (outFrame.bottom - outFrame.top) / 3 )
polar.set( val dj = (outFrame.bottom - outFrame.top) / 3
batteryEnd, polar.set(
outFrame.top + dj, batteryEnd,
batteryEnd + 2.dpToPx(), outFrame.top + dj,
outFrame.bottom - dj batteryEnd + 2.dpToPx(),
) outFrame.bottom - dj
batteryPaint.style = Paint.Style.STROKE )
canvas.drawRect(outFrame, batteryPaint) batteryPaint.style = Paint.Style.STROKE
batteryPaint.style = Paint.Style.FILL drawRect(outFrame, batteryPaint)
canvas.drawRect(polar, batteryPaint) batteryPaint.style = Paint.Style.FILL
drawRect(polar, batteryPaint)
}
}
override fun invalidate() {
super.invalidate()
kotlin.runCatching {
canvasRecorder.invalidate()
}
} }
} }
@@ -32,26 +32,19 @@ object ChineseUtils {
fun fixT2sDict() { fun fixT2sDict() {
val dict = DictionaryContainer.getInstance().getDictionary(TransType.TRADITIONAL_TO_SIMPLE) val dict = DictionaryContainer.getInstance().getDictionary(TransType.TRADITIONAL_TO_SIMPLE)
dict.run { dict.run {
remove("") remove("", "")
remove("") remove("支援", "沈默", "類比", "模擬", "划槳", "列根", "先進")
remove("支援") remove("路易斯", "非同步", "出租车", "周杰倫")
remove("沈默")
remove("類比")
remove("模擬")
remove("划槳")
remove("列根")
remove("路易斯")
remove("非同步")
remove("出租车")
remove("周杰倫")
} }
} }
fun BasicDictionary.remove(key: String) { fun BasicDictionary.remove(vararg keys: String) {
if (key.length == 1) { for (key in keys) {
chars.remove(key[0]) if (key.length == 1) {
} else { chars.remove(key[0])
dict.remove(key) } else {
dict.remove(key)
}
} }
} }
@@ -1,88 +0,0 @@
package io.legado.app.utils
import android.graphics.Canvas
import android.graphics.Picture
import android.os.Build
import androidx.core.graphics.record
import java.util.concurrent.locks.ReentrantLock
class PictureMirror {
@Volatile
var picture: Picture? = null
@Volatile
var lock: ReentrantLock? = null
@Volatile
var isDirty = true
inline fun drawLocked(
canvas: Canvas?,
width: Int,
height: Int,
block: Canvas.() -> Unit
) {
if (atLeastApi23) {
if (picture == null || lock == null) {
synchronized(this) {
if (picture == null) {
picture = Picture()
}
if (lock == null) {
lock = ReentrantLock()
}
}
}
val picture = picture!!
val lock = lock!!
if (isDirty) {
if (!lock.tryLock()) return
try {
picture.record(width, height, block)
isDirty = false
} finally {
lock.unlock()
}
}
canvas?.drawPicture(picture)
} else {
canvas?.block()
}
}
/**
* 非线程安全,多线程调用可能会崩溃
*/
inline fun draw(
canvas: Canvas?,
width: Int,
height: Int,
block: Canvas.() -> Unit
) {
if (atLeastApi23) {
if (picture == null) picture = Picture()
val picture = picture!!
if (isDirty) {
picture.record(width, height, block)
isDirty = false
}
canvas?.drawPicture(picture)
} else {
canvas?.block()
}
}
fun invalidate() {
isDirty = true
}
fun recycle() {
picture = null
isDirty = true
}
companion object {
val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
}
}
@@ -37,6 +37,8 @@ import androidx.viewpager.widget.ViewPager
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.globalExecutor import io.legado.app.help.globalExecutor
import io.legado.app.lib.theme.TintHelper import io.legado.app.lib.theme.TintHelper
import io.legado.app.utils.canvasrecorder.CanvasRecorder
import io.legado.app.utils.canvasrecorder.record
import splitties.systemservices.inputMethodManager import splitties.systemservices.inputMethodManager
import java.lang.reflect.Field import java.lang.reflect.Field
@@ -180,6 +182,16 @@ fun View.screenshot(picture: Picture) {
} }
} }
fun View.screenshot(canvasRecorder: CanvasRecorder) {
if (width > 0 && height > 0) {
canvasRecorder.record(width, height) {
withTranslation(-scrollX.toFloat(), -scrollY.toFloat()) {
draw(this)
}
}
}
}
fun View.setPaddingBottom(bottom: Int) { fun View.setPaddingBottom(bottom: Int) {
setPadding(paddingLeft, paddingTop, paddingRight, bottom) setPadding(paddingLeft, paddingTop, paddingRight, bottom)
} }
@@ -0,0 +1,36 @@
package io.legado.app.utils.canvasrecorder
import androidx.annotation.CallSuper
abstract class BaseCanvasRecorder : CanvasRecorder {
@JvmField
protected var isDirty = true
override fun invalidate() {
isDirty = true
}
@CallSuper
override fun recycle() {
isDirty = true
}
@CallSuper
override fun endRecording() {
isDirty = false
}
override fun isDirty(): Boolean {
return isDirty
}
override fun isLocked(): Boolean {
return false
}
override fun needRecord(): Boolean {
return isDirty() && !isLocked()
}
}
@@ -0,0 +1,27 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas
interface CanvasRecorder {
val width: Int
val height: Int
fun beginRecording(width: Int, height: Int): Canvas
fun endRecording()
fun draw(canvas: Canvas)
fun invalidate()
fun recycle()
fun isDirty(): Boolean
fun isLocked(): Boolean
fun needRecord(): Boolean
}
@@ -0,0 +1,39 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas
import android.graphics.Picture
class CanvasRecorderApi23Impl : BaseCanvasRecorder() {
private var picture: Picture? = null
override val width get() = picture?.width ?: -1
override val height get() = picture?.height ?: -1
private fun initPicture() {
if (picture == null) {
picture = Picture()
}
}
override fun beginRecording(width: Int, height: Int): Canvas {
initPicture()
return picture!!.beginRecording(width, height)
}
override fun endRecording() {
picture!!.endRecording()
super.endRecording()
}
override fun draw(canvas: Canvas) {
if (picture == null) return
canvas.drawPicture(picture!!)
}
override fun recycle() {
super.recycle()
picture = null
}
}
@@ -0,0 +1,62 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas
import android.graphics.Picture
import android.graphics.RenderNode
import android.os.Build
import androidx.annotation.RequiresApi
@RequiresApi(Build.VERSION_CODES.Q)
class CanvasRecorderApi29Impl : BaseCanvasRecorder() {
private var renderNode: RenderNode? = null
private var picture: Picture? = null
override val width get() = renderNode?.width ?: -1
override val height get() = renderNode?.height ?: -1
private fun init() {
if (renderNode == null) {
renderNode = RenderNode("CanvasRecorder")
}
if (picture == null) {
picture = Picture()
}
}
override fun beginRecording(width: Int, height: Int): Canvas {
init()
renderNode!!.setPosition(0, 0, width, height)
return picture!!.beginRecording(width, height)
}
override fun endRecording() {
picture!!.endRecording()
val rc = renderNode!!.beginRecording()
rc.drawPicture(picture!!)
renderNode!!.endRecording()
super.endRecording()
}
override fun draw(canvas: Canvas) {
if (renderNode == null || picture == null) return
if (canvas.isHardwareAccelerated) {
if (!renderNode!!.hasDisplayList()) {
val rc = renderNode!!.beginRecording()
rc.drawPicture(picture!!)
renderNode!!.endRecording()
}
canvas.drawRenderNode(renderNode!!)
} else {
canvas.drawPicture(picture!!)
}
}
override fun recycle() {
super.recycle()
renderNode?.discardDisplayList()
renderNode = null
picture = null
}
}
@@ -0,0 +1,35 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas
import androidx.core.graphics.withSave
inline fun CanvasRecorder.recordIfNeeded(
width: Int,
height: Int,
block: Canvas.() -> Unit
): Boolean {
if (!needRecord()) return false
record(width, height, block)
return true
}
inline fun CanvasRecorder.record(width: Int, height: Int, block: Canvas.() -> Unit) {
val canvas = beginRecording(width, height)
try {
canvas.withSave {
block()
}
} finally {
endRecording()
}
}
inline fun CanvasRecorder.recordIfNeededThenDraw(
canvas: Canvas,
width: Int,
height: Int,
block: Canvas.() -> Unit
) {
recordIfNeeded(width, height, block)
draw(canvas)
}
@@ -0,0 +1,23 @@
package io.legado.app.utils.canvasrecorder
import android.os.Build
object CanvasRecorderFactory {
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
private val atLeastApi29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
fun create(locked: Boolean = false): CanvasRecorder {
val impl = when {
atLeastApi29 -> CanvasRecorderApi29Impl()
atLeastApi23 -> CanvasRecorderApi23Impl()
else -> CanvasRecorderImpl()
}
return if (locked) {
CanvasRecorderLocked(impl)
} else {
impl
}
}
}
@@ -0,0 +1,59 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
class CanvasRecorderImpl : BaseCanvasRecorder() {
var bitmap: Bitmap? = null
var canvas: Canvas? = null
override val width get() = bitmap?.width ?: -1
override val height get() = bitmap?.height ?: -1
private fun init(width: Int, height: Int) {
if (bitmap == null) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
}
if (canvas == null) {
canvas = Canvas(bitmap!!)
}
if (bitmap!!.width != width || bitmap!!.height != height) {
if (canReconfigure(width, height)) {
bitmap!!.reconfigure(width, height, Bitmap.Config.ARGB_8888)
} else {
bitmap!!.recycle()
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
}
canvas!!.setBitmap(bitmap!!)
}
}
private fun canReconfigure(width: Int, height: Int): Boolean {
return bitmap!!.allocationByteCount >= width * height * 4
}
override fun beginRecording(width: Int, height: Int): Canvas {
init(width, height)
bitmap!!.eraseColor(Color.TRANSPARENT)
return canvas!!
}
override fun endRecording() {
bitmap!!.prepareToDraw()
super.endRecording()
}
override fun draw(canvas: Canvas) {
if (bitmap == null) return
canvas.drawBitmap(bitmap!!, 0f, 0f, null)
}
override fun recycle() {
super.recycle()
bitmap?.recycle()
bitmap = null
}
}
@@ -0,0 +1,52 @@
package io.legado.app.utils.canvasrecorder
import android.graphics.Canvas
import java.util.concurrent.locks.ReentrantLock
class CanvasRecorderLocked(private val delegate: CanvasRecorder) :
CanvasRecorder by delegate {
var lock: ReentrantLock? = ReentrantLock()
private fun initLock() {
if (lock == null) {
synchronized(this) {
if (lock == null) {
lock = ReentrantLock()
}
}
}
}
override fun beginRecording(width: Int, height: Int): Canvas {
initLock()
lock!!.lock()
return delegate.beginRecording(width, height)
}
override fun endRecording() {
delegate.endRecording()
lock!!.unlock()
}
override fun draw(canvas: Canvas) {
if (lock == null) return
if (!lock!!.tryLock()) return
try {
delegate.draw(canvas)
} finally {
lock!!.unlock()
}
}
override fun isLocked(): Boolean {
if (lock == null) return false
return lock!!.isLocked
}
override fun recycle() {
delegate.recycle()
lock = null
}
}