diff --git a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt index db5ab007e..eb38c7e78 100644 --- a/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt @@ -567,7 +567,6 @@ object ReadBookConfig { textColorInt = color } } - ChapterProvider.upStyle() } fun curTextColor(): Int { diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 2fee95c8d..c5062cfef 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -274,10 +274,10 @@ object ReadBook : CoroutineScope by MainScope() { if (textChapter != null) { val pageIndex = durPageIndex if (index > pageIndex) { - textChapter.getPage(index - 2)?.recyclePictures() + textChapter.getPage(index - 2)?.recycleRecorders() } if (index < pageIndex) { - textChapter.getPage(index + 3)?.recyclePictures() + textChapter.getPage(index + 3)?.recycleRecorders() } } durChapterPos = curTextChapter?.getReadLength(index) ?: index diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 55573449e..6a12f28a4 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.res.Configuration import android.net.Uri import android.os.Bundle +import android.os.Looper import android.view.Gravity import android.view.InputDevice 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.ReadView 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.SearchResult 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 kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job -import kotlinx.coroutines.asExecutor import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -217,7 +218,6 @@ class ReadBookActivity : BaseReadBookActivity(), private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } } private var bookChanged = false private var pageChanged = false - private var reloadContent = false private val handler by lazy { buildMainHandler() } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } private val executor = ReadBook.executor @@ -264,23 +264,9 @@ class ReadBookActivity : BaseReadBookActivity(), override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - viewModel.initData(intent) { - initDataSuccess() - } - } - - 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) + Looper.myQueue().addIdleHandler { + viewModel.initData(intent) { upMenu() } + false } } @@ -1312,24 +1298,24 @@ class ReadBookActivity : BaseReadBookActivity(), when (dialogId) { TEXT_COLOR -> { setCurTextColor(color) - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(2, 6)) } BG_COLOR -> { setCurBg(0, "#${color.hexString}") - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(1)) } TIP_COLOR -> { ReadTipConfig.tipColor = color postEvent(EventBus.TIP_COLOR, "") - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } TIP_DIVIDER_COLOR -> { ReadTipConfig.tipDividerColor = 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) } } - observeEvent(EventBus.UP_CONFIG) { - upSystemUiVisibility() - readView.upPageSlopSquare() - readView.upBg() - readView.upStyle() - readView.upBgAlpha() - if (it) { // 更新内容排版布局 - if (isInitFinish) { - ReadBook.loadContent(resetPageOffset = false) - } else { - reloadContent = true + observeEvent>(EventBus.UP_CONFIG) { + it.forEach { value -> + when (value) { + 0 -> upSystemUiVisibility() + 1 -> readView.upBg() + 2 -> readView.upStyle() + 3 -> readView.upBgAlpha() + 4 -> readView.upPageSlopSquare() + 5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false) + 6 -> readView.upContent(resetPageOffset = false) + 8 -> ChapterProvider.upStyle() + 9 -> binding.readView.invalidateTextPage() + 10 -> ChapterProvider.upLayout() } - } else { - readView.upContent(resetPageOffset = false) } - binding.readMenu.reset() } observeEvent(EventBus.ALOUD_STATE) { if (it == Status.STOP || it == Status.PAUSE) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt index 1dfb26983..1d3d41d71 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt @@ -41,7 +41,7 @@ class BgAdapter(context: Context, val textColor: Int) : this.setOnClickListener { getItemByLayoutPosition(holder.layoutPosition)?.let { ReadBookConfig.durConfig.setCurBg(1, it) - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(1)) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 2f16f4628..6ecf6c849 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -33,11 +33,32 @@ import io.legado.app.lib.theme.getSecondaryTextColor import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.file.HandleFileContract 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.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 splitties.init.appCtx - import java.io.File import java.io.FileOutputStream @@ -168,7 +189,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { if (i >= 0) { ReadBookConfig.durConfig = defaultConfigs[i].copy() 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 -> underline = isChecked - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(9)) } binding.tvTextColor.setOnClickListener { ColorPickerDialog.newBuilder() @@ -214,7 +235,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { } binding.ivDelete.setOnClickListener { if (ReadBookConfig.deleteDur()) { - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) dismissAllowingStateLoss() } else { toastOnUi("数量已是最少,不能删除.") @@ -223,11 +244,11 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { binding.sbBgAlpha.setOnSeekBarChangeListener(object : SeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { ReadBookConfig.bgAlpha = progress - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(3)) } 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() }.onSuccess { ReadBookConfig.durConfig = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) toastOnUi("导入成功") }.onError { it.printOnDebug() @@ -378,7 +399,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) { inputStream.copyTo(outputStream) } ReadBookConfig.durConfig.setCurBg(2, fileName) - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(1)) }.onFailure { appCtx.toastOnUi(it.localizedMessage) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index a4ed8c938..1ef45ae8b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt @@ -107,39 +107,48 @@ class MoreConfigDialog : DialogFragment() { PreferKey.readBodyToLh -> activity?.recreate() PreferKey.hideStatusBar -> { ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(0)) } + PreferKey.hideNavigationBar -> { ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(0)) } + PreferKey.keepLight -> postEvent(key, true) PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) PreferKey.screenOrientation -> { (activity as? ReadBookActivity)?.setOrientation() } + PreferKey.textFullJustify, PreferKey.textBottomJustify, PreferKey.useZhLayout -> { - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } + PreferKey.showBrightnessView -> { postEvent(PreferKey.showBrightnessView, "") } + PreferKey.expandTextMenu -> { (activity as? ReadBookActivity)?.textActionMenu?.upMenu() } + PreferKey.doublePageHorizontal -> { ChapterProvider.upLayout() ReadBook.loadContent(false) } + PreferKey.showReadTitleAddition, PreferKey.readBarStyleFollowPage -> { postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) } + PreferKey.progressBarBehavior -> { postEvent(EventBus.UP_SEEK_BAR, true) } + PreferKey.noAnimScrollPage -> { ReadBook.callBack?.upPageAnim() } @@ -152,6 +161,7 @@ class MoreConfigDialog : DialogFragment() { "clickRegionalConfig" -> { (activity as? ReadBookActivity)?.showClickRegionalConfig() } + PreferKey.pageTouchSlop -> { NumberPickerDialog(requireContext()) .setTitle(getString(R.string.page_touch_slop_dialog_title)) @@ -160,7 +170,7 @@ class MoreConfigDialog : DialogFragment() { .setValue(AppConfig.pageTouchSlop) .show { AppConfig.pageTouchSlop = it - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, arrayOf(4)) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index d55f28f32..debf0c2a5 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -63,61 +63,61 @@ class PaddingConfigDialog : BaseDialogFragment(R.layout.dialog_read_padding) { //正文 dsbPaddingTop.onChanged = { ReadBookConfig.paddingTop = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } dsbPaddingBottom.onChanged = { ReadBookConfig.paddingBottom = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } dsbPaddingLeft.onChanged = { ReadBookConfig.paddingLeft = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } dsbPaddingRight.onChanged = { ReadBookConfig.paddingRight = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(10, 5)) } //页眉 dsbHeaderPaddingTop.onChanged = { ReadBookConfig.headerPaddingTop = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbHeaderPaddingBottom.onChanged = { ReadBookConfig.headerPaddingBottom = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbHeaderPaddingLeft.onChanged = { ReadBookConfig.headerPaddingLeft = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbHeaderPaddingRight.onChanged = { ReadBookConfig.headerPaddingRight = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } //页脚 dsbFooterPaddingTop.onChanged = { ReadBookConfig.footerPaddingTop = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbFooterPaddingBottom.onChanged = { ReadBookConfig.footerPaddingBottom = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbFooterPaddingLeft.onChanged = { ReadBookConfig.footerPaddingLeft = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } dsbFooterPaddingRight.onChanged = { ReadBookConfig.footerPaddingRight = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } cbShowTopLine.onCheckedChangeListener = { _, isChecked -> ReadBookConfig.showHeaderLine = isChecked - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } cbShowBottomLine.onCheckedChangeListener = { _, isChecked -> ReadBookConfig.showFooterLine = isChecked - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index 1db0195d3..1201cea16 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -108,10 +108,10 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), private fun initViewEvent() = binding.run { chineseConverter.onChanged { ChineseUtils.unLoad(*TransType.entries.toTypedArray()) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } textFontWeightConverter.onChanged { - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8)) } tvTextFont.setOnClickListener { showDialogFragment() @@ -122,7 +122,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), items = resources.getStringArray(R.array.indent).toList() ) { _, index -> ReadBookConfig.paragraphIndent = " ".repeat(index) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } } tvPadding.setOnClickListener { @@ -141,40 +141,40 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), cbShareLayout.onCheckedChangeListener = { _, isChecked -> ReadBookConfig.shareLayout = isChecked upView() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) } dsbTextSize.onChanged = { ReadBookConfig.textSize = it + 5 - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbTextLetterSpacing.onChanged = { ReadBookConfig.letterSpacing = (it - 50) / 100f - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbLineSize.onChanged = { ReadBookConfig.lineSpacingExtra = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbParagraphSpacing.onChanged = { 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 if (index != oldIndex) { ReadBookConfig.styleSelect = index upView() styleAdapter.notifyItemChanged(oldIndex) styleAdapter.notifyItemChanged(index) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(1, 2, 5)) } } private fun showBgTextConfig(index: Int): Boolean { dismissAllowingStateLoss() - changeBg(index) + changeBgTextConfig(index) callBack?.showBgTextConfig() return true } @@ -200,7 +200,7 @@ class ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style), override fun selectFont(path: String) { if (path != ReadBookConfig.textFont) { 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 { ivStyle.setOnClickListener { if (ivStyle.isInView) { - changeBg(holder.layoutPosition) + changeBgTextConfig(holder.layoutPosition) } } ivStyle.onLongClick(ivStyle.isInView) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt index cca5a2798..6862c7734 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt @@ -11,7 +11,12 @@ import io.legado.app.databinding.DialogTipConfigBinding import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadTipConfig 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 @@ -89,26 +94,26 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { private fun initEvent() = binding.run { rgTitleMode.setOnCheckedChangeListener { _, checkedId -> ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId) - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(5)) } dsbTitleSize.onChanged = { ReadBookConfig.titleSize = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbTitleTop.onChanged = { ReadBookConfig.titleTopSpacing = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } dsbTitleBottom.onChanged = { ReadBookConfig.titleBottomSpacing = it - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(8, 5)) } llHeaderShow.setOnClickListener { val headerModes = ReadTipConfig.getHeaderModes(requireContext()) context?.selector(items = headerModes.values.toList()) { _, i -> ReadTipConfig.headerMode = headerModes.keys.toList()[i] tvHeaderShow.text = headerModes[ReadTipConfig.headerMode] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterShow.setOnClickListener { @@ -116,7 +121,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { context?.selector(items = footerModes.values.toList()) { _, i -> ReadTipConfig.footerMode = footerModes.keys.toList()[i] tvFooterShow.text = footerModes[ReadTipConfig.footerMode] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llHeaderLeft.setOnClickListener { @@ -125,7 +130,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipHeaderLeft = tipValue tvHeaderLeft.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llHeaderMiddle.setOnClickListener { @@ -134,7 +139,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipHeaderMiddle = tipValue tvHeaderMiddle.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llHeaderRight.setOnClickListener { @@ -143,7 +148,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipHeaderRight = tipValue tvHeaderRight.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterLeft.setOnClickListener { @@ -152,7 +157,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipFooterLeft = tipValue tvFooterLeft.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterMiddle.setOnClickListener { @@ -161,7 +166,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipFooterMiddle = tipValue tvFooterMiddle.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llFooterRight.setOnClickListener { @@ -170,7 +175,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { clearRepeat(tipValue) ReadTipConfig.tipFooterRight = tipValue tvFooterRight.text = ReadTipConfig.tipNames[i] - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } llTipColor.setOnClickListener { @@ -179,7 +184,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { 0 -> { ReadTipConfig.tipColor = 0 upTvTipColor() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } 1 -> ColorPickerDialog.newBuilder() .setShowAlphaSlider(false) @@ -195,7 +200,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) { 0, 1 -> { ReadTipConfig.tipDividerColor = i - 1 upTvTipDividerColor() - postEvent(EventBus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, arrayOf(2)) } 2 -> ColorPickerDialog.newBuilder() .setShowAlphaSlider(false) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt index 7d5ebbd0a..5b7b3990b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt @@ -82,7 +82,6 @@ class AutoPager(private val readView: ReadView) { } if (readView.isScroll) { - computeOffset() if (!isPausing) readView.curPage.scroll(-scrollOffset) } else { val bottom = progress @@ -112,13 +111,15 @@ class AutoPager(private val readView: ReadView) { bottom.toFloat(), paint ) - if (!isPausing) readView.invalidate() - computeOffset() + if (!isPausing) readView.postInvalidate() } } - private fun computeOffset() { + fun computeOffset() { + if (!isRunning) { + return + } val currentTime = SystemClock.uptimeMillis() val elapsedTime = currentTime - lastTimeMillis diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index f79ff5f92..3efff6a90 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -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.TextPageFactory 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.getCompatColor 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 var pageOffset = 0 private var autoPager: AutoPager? = null + private var isScroll = false private val renderRunnable by lazy { Runnable { preRenderPage() } } //绘制图片的paint @@ -75,14 +75,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at */ fun setContent(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) { super.onSizeChanged(w, h, oldw, oldh) - if (!isMainView) return - ChapterProvider.upViewSize(w, h) + ChapterProvider.upViewSize(w, h, isMainView) textPage.format() } @@ -107,16 +110,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at //滚动翻页 if (!pageFactory.hasNext()) return val textPage1 = relativePage(1) - relativeOffset = relativeOffset(1) + relativeOffset += textPage.height textPage1.draw(this, canvas, relativeOffset) if (!pageFactory.hasNextPlus()) return - relativeOffset = relativeOffset(2) + relativeOffset += textPage1.height if (relativeOffset < ChapterProvider.visibleHeight) { val textPage2 = relativePage(2) textPage2.draw(this, canvas, relativeOffset) } } + override fun computeScroll() { + pageDelegate?.computeScroll() + autoPager?.computeOffset() + } + /** * 滚动事件 * pageOffset 向上滚动 减小 向下滚动 增大 @@ -125,7 +133,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at * pageOffset + textPage.height 为 textPage 下方的高度 */ fun scroll(mOffset: Int) { - if (mOffset == 0) return pageOffset += mOffset if (longScreenshot) { scrollY += -mOffset @@ -156,33 +163,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at pageDelegate?.abortAnim() } } - invalidate() + postInvalidate() } - fun submitPreRenderTask() { - if (PictureMirror.atLeastApi23) { - renderThread.submit(renderRunnable) - } + fun submitRenderTask() { + renderThread.submit(renderRunnable) } private fun preRenderPage() { val view = this var invalidate = false pageFactory.run { - hasPrev() && prevPage.preRender(view) - if (curPage.preRender(view)) { + if (hasPrev() && prevPage.render(view)) { invalidate = true } - if (hasNext() && nextPage.preRender(view) && callBack.isScroll) { + if (curPage.render(view)) { 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 ) { invalidate = true } if (invalidate) { postInvalidate() + pageDelegate?.postInvalidate() } } } @@ -254,7 +262,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at touchRough(x, y) { _, textPos, _, _, column -> if (column is TextColumn) { column.selected = true - invalidate() 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) { @@ -681,6 +689,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at this.autoPager = autoPager } + fun setIsScroll(value: Boolean) { + isScroll = value + } + override fun canScrollVertically(direction: Int): Boolean { return callBack.isScroll && pageFactory.hasNext() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index b66e25138..f450cb746 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -321,7 +321,12 @@ class PageView(context: Context) : FrameLayout(context) { } fun submitPreRenderTask() { - binding.contentTextView.submitPreRenderTask() + binding.contentTextView.submitRenderTask() + } + + fun setIsScroll(value: Boolean) { + isScroll = value + binding.contentTextView.setIsScroll(value) } /** diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 0b21995d8..21b175fff 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -148,6 +148,7 @@ class ReadView(context: Context, attrs: AttributeSet) : override fun computeScroll() { pageDelegate?.computeScroll() + autoPager.computeOffset() } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { @@ -510,7 +511,7 @@ class ReadView(context: Context, attrs: AttributeSet) : } else { curPage.setAutoPager(null) } - curPage.isScroll = isScroll + curPage.setIsScroll(isScroll) } /** @@ -634,6 +635,7 @@ class ReadView(context: Context, attrs: AttributeSet) : nextPage.invalidateAll() nextPlusPage.invalidateAll() } + upContent() } fun onScrollAnimStart() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt index f9fb0695c..5e8303312 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt @@ -1,10 +1,7 @@ package io.legado.app.ui.book.read.page.delegate import android.graphics.Canvas -import android.graphics.Matrix -import android.graphics.Picture import android.graphics.drawable.GradientDrawable -import android.os.Build import androidx.core.graphics.withClip import androidx.core.graphics.withTranslation 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 class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { - private val bitmapMatrix = Matrix() 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 { val shadowColors = intArrayOf(0x66111111, 0x00000000) shadowDrawableR = GradientDrawable( GradientDrawable.Orientation.LEFT_RIGHT, shadowColors ) shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT - if (atLeastApi23) { - curPicture = Picture() - prevPicture = Picture() - nextPicture = Picture() - } } 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 if (mDirection == PageDirection.PREV) { if (offsetX <= viewWidth) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - canvas.withTranslation(distanceX) { - drawPicture(prevPicture) - } + canvas.withTranslation(distanceX) { + prevRecorder.draw(canvas) } addShadow(distanceX, canvas) } else { - if (!atLeastApi23) { - prevBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } - } else { - canvas.drawPicture(prevPicture) - } + prevRecorder.draw(canvas) } } else if (mDirection == PageDirection.NEXT) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) - nextBitmap?.let { - val width = it.width.toFloat() - val height = it.height.toFloat() - canvas.withClip(width + offsetX, 0f, width, height) { - drawBitmap(it, 0f, 0f, null) - } - } - 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) - } + val width = nextRecorder.width.toFloat() + val height = nextRecorder.height.toFloat() + canvas.withClip(width + offsetX, 0f, width, height) { + nextRecorder.draw(this) + } + canvas.withTranslation(distanceX - viewWidth) { + curRecorder.draw(this) } addShadow(distanceX, canvas) } @@ -90,18 +54,13 @@ class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { override fun setBitmap() { when (mDirection) { - PageDirection.PREV -> if (!atLeastApi23) { - prevBitmap = prevPage.screenshot(prevBitmap, canvas) - } else { - prevPage.screenshot(prevPicture) + PageDirection.PREV -> { + prevPage.screenshot(prevRecorder) } - PageDirection.NEXT -> if (!atLeastApi23) { - nextBitmap = nextPage.screenshot(nextBitmap, canvas) - curBitmap = curPage.screenshot(curBitmap, canvas) - } else { - nextPage.screenshot(nextPicture) - curPage.screenshot(curPicture) + PageDirection.NEXT -> { + nextPage.screenshot(nextRecorder) + curPage.screenshot(curRecorder) } else -> Unit diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt index 9e8e30f2f..f0f91b3ca 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt @@ -1,18 +1,16 @@ package io.legado.app.ui.book.read.page.delegate -import android.graphics.Bitmap -import android.graphics.Canvas import android.view.MotionEvent import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection +import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.screenshot abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readView) { - protected var curBitmap: Bitmap? = null - protected var prevBitmap: Bitmap? = null - protected var nextBitmap: Bitmap? = null - protected var canvas: Canvas = Canvas() + protected val curRecorder = CanvasRecorderFactory.create() + protected val prevRecorder = CanvasRecorderFactory.create() + protected val nextRecorder = CanvasRecorderFactory.create() private val slopSquare get() = readView.pageSlopSquare2 override fun setDirection(direction: PageDirection) { @@ -23,13 +21,13 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie open fun setBitmap() { when (mDirection) { PageDirection.PREV -> { - prevBitmap = prevPage.screenshot(prevBitmap, canvas) - curBitmap = curPage.screenshot(curBitmap, canvas) + prevPage.screenshot(prevRecorder) + curPage.screenshot(curRecorder) } PageDirection.NEXT -> { - nextBitmap = nextPage.screenshot(nextBitmap, canvas) - curBitmap = curPage.screenshot(curBitmap, canvas) + nextPage.screenshot(nextRecorder) + curPage.screenshot(curRecorder) } else -> Unit @@ -140,12 +138,9 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie override fun onDestroy() { super.onDestroy() - prevBitmap?.recycle() - prevBitmap = null - curBitmap?.recycle() - curBitmap = null - nextBitmap?.recycle() - nextBitmap = null + prevRecorder.recycle() + curRecorder.recycle() + nextRecorder.recycle() } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index 31fd7e8db..38e51d6e7 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -96,7 +96,7 @@ abstract class PageDelegate(protected val readView: ReadView) { viewHeight = height } - fun computeScroll() { + open fun computeScroll() { if (scroller.computeScrollOffset()) { readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) } 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() { // run on destroy } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index 232aaa925..2d32b1c92 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -79,7 +79,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) { val pointX = event.getX(event.pointerCount - 1) val pointY = event.getY(event.pointerCount - 1) if (isMoved) { - readView.setTouchPoint(pointX, pointY) + readView.setTouchPoint(pointX, pointY, false) } if (!isMoved) { 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() { super.onDestroy() mVelocity.recycle() } override fun abortAnim() { + readView.onScrollAnimStop() isStarted = false isMoved = false isRunning = false diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt index 04b7bde03..ebadb8dad 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt @@ -1,13 +1,27 @@ 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.os.Build import android.view.MotionEvent import io.legado.app.help.config.ReadBookConfig import io.legado.app.ui.book.read.page.ReadView 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") 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 var curBitmap: Bitmap? = null + private var prevBitmap: Bitmap? = null + private var nextBitmap: Bitmap? = null + private var canvas: Canvas = Canvas() + init { //设置颜色数组 val color = intArrayOf(0x333333, -0x4fcccccd) @@ -122,6 +141,22 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi 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) { super.setViewSize(width, height) mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() @@ -133,6 +168,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi MotionEvent.ACTION_DOWN -> { calcCornerXY(event.x, event.y) } + MotionEvent.ACTION_MOVE -> { if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3) || mDirection == PageDirection.PREV @@ -159,10 +195,12 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi } else { calcCornerXY(viewWidth - startX, viewHeight.toFloat()) } + PageDirection.NEXT -> if (viewWidth / 2 > startX) { calcCornerXY(viewWidth - startX, startY) } + else -> Unit } } @@ -216,6 +254,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi drawCurrentPageShadow(canvas) drawCurrentBackArea(canvas, curBitmap) } + PageDirection.PREV -> { calcPoints() drawCurrentPageArea(canvas, prevBitmap) @@ -223,6 +262,7 @@ class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readVi drawCurrentPageShadow(canvas) drawCurrentBackArea(canvas, prevBitmap) } + else -> return } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt index 103cf3a09..33c6480cb 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt @@ -1,51 +1,12 @@ package io.legado.app.ui.book.read.page.delegate import android.graphics.Canvas -import android.graphics.Matrix -import android.graphics.Picture -import android.os.Build import androidx.core.graphics.withTranslation import io.legado.app.ui.book.read.page.ReadView import io.legado.app.ui.book.read.page.entities.PageDirection -import io.legado.app.utils.screenshot 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) { val distanceX: Float when (mDirection) { @@ -79,32 +40,18 @@ class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) { val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth if (!isRunning) return if (mDirection == PageDirection.PREV) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX + viewWidth, 0.toFloat()) - curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - canvas.withTranslation(distanceX + viewWidth) { - drawPicture(curPicture) - } - canvas.withTranslation(distanceX) { - drawPicture(prevPicture) - } + canvas.withTranslation(distanceX + viewWidth) { + curRecorder.draw(this) + } + canvas.withTranslation(distanceX) { + prevRecorder.draw(this) } } else if (mDirection == PageDirection.NEXT) { - if (!atLeastApi23) { - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - nextBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) - curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } - } else { - canvas.withTranslation(distanceX) { - drawPicture(nextPicture) - } - canvas.withTranslation(distanceX - viewWidth) { - drawPicture(curPicture) - } + canvas.withTranslation(distanceX) { + nextRecorder.draw(this) + } + canvas.withTranslation(distanceX - viewWidth) { + curRecorder.draw(this) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 0f89a6e32..2adc81ea6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -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.column.BaseColumn 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 /** @@ -39,7 +40,7 @@ data class TextLine( val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize val height: Float inline get() = lineBottom - lineTop - val pictureMirror: PictureMirror = PictureMirror() + val canvasRecorder = CanvasRecorderFactory.create() var isReadAloud: Boolean = false set(value) { if (field != value) { @@ -122,7 +123,7 @@ data class TextLine( } fun draw(view: ContentTextView, canvas: Canvas) { - pictureMirror.draw(canvas, view.width, height.toInt()) { + canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) { drawTextLine(view, this) } } @@ -156,11 +157,11 @@ data class TextLine( } fun invalidateSelf() { - pictureMirror.invalidate() + canvasRecorder.invalidate() } - fun recyclePicture() { - pictureMirror.recycle() + fun recycleRecorder() { + canvasRecorder.recycle() } companion object { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index 4926007b6..ef8032b95 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -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.entities.column.TextColumn 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 java.text.DecimalFormat import kotlin.math.min @@ -43,7 +44,7 @@ data class TextPage( val charSize: Int get() = text.length.coerceAtLeast(1) val searchResult = hashSetOf() var isMsgPage: Boolean = false - var pictureMirror: PictureMirror = PictureMirror() + var canvasRecorder = CanvasRecorderFactory.create(true) var doublePage = false var paddingTop = 0 @@ -267,11 +268,9 @@ data class TextPage( } fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { - val height = height.toInt() + render(view) canvas.withTranslation(0f, relativeOffset + paddingTop) { - pictureMirror.drawLocked(canvas, view.width, height) { - drawPage(view, this) - } + canvasRecorder.draw(this) } } @@ -284,20 +283,14 @@ data class TextPage( } } - fun preRender(view: ContentTextView): Boolean { - if (!pictureMirror.isDirty) return false - pictureMirror.drawLocked(null, view.width, height.toInt()) { + fun render(view: ContentTextView): Boolean { + return canvasRecorder.recordIfNeeded(view.width, height.toInt()) { drawPage(view, this) } - return true - } - - fun isDirty(): Boolean { - return pictureMirror.isDirty } fun invalidate() { - pictureMirror.invalidate() + canvasRecorder.invalidate() } fun invalidateAll() { @@ -307,10 +300,10 @@ data class TextPage( invalidate() } - fun recyclePictures() { - pictureMirror.recycle() + fun recycleRecorders() { + canvasRecorder.recycle() for (i in lines.indices) { - lines[i].recyclePicture() + lines[i].recycleRecorder() } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt index 7096c55d1..de204e42d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt @@ -42,15 +42,15 @@ data class TextColumn( } else { ChapterProvider.contentPaint } - val textColor = if (textLine.isReadAloud || isSearchResult) { - ThemeStore.accentColor + if (textLine.isReadAloud || isSearchResult) { + synchronized(textPaint) { + textPaint.color = ThemeStore.accentColor + canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) + textPaint.color = ReadBookConfig.textColor + } } 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) { canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 368528f8d..3754893ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -690,39 +690,6 @@ object ChapterProvider { exceed(absStartX, textLine, words) } - fun getStringArrayAndTextWidths( - text: String, - textWidths: List, - textPaint: TextPaint - ): Pair, List> { - val charArray = text.toCharArray() - val strList = ArrayList(text.length) - val textWidthList = ArrayList(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尺寸 */ - fun upViewSize(width: Int, height: Int) { + fun upViewSize(width: Int, height: Int, postEvent: Boolean) { if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) { viewWidth = width viewHeight = height upLayout() - postEvent(EventBus.UP_CONFIG, true) + if (postEvent) postEvent(EventBus.UP_CONFIG, arrayOf(2)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt index 27329769a..4c8e51e9b 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt @@ -3,13 +3,11 @@ package io.legado.app.ui.book.read.page.provider import android.text.TextPaint import android.util.SparseArray import androidx.core.util.getOrDefault -import java.util.BitSet import kotlin.math.ceil class TextMeasure(private var paint: TextPaint) { private var chineseCommonWidth = paint.measureText("一") - private val chineseCommonWidthBitSet = BitSet() private val asciiWidths = FloatArray(128) { -1f } private val codePointWidths = SparseArray() @@ -17,7 +15,8 @@ class TextMeasure(private var paint: TextPaint) { if (codePoint < 128) { return asciiWidths[codePoint] } - if (chineseCommonWidthBitSet[codePoint]) { + // 中文 Unicode 范围 U+4E00 - U+9FA5 + if (codePoint in 19968 .. 40869) { return chineseCommonWidth } return codePointWidths.getOrDefault(codePoint, -1f) @@ -33,7 +32,8 @@ class TextMeasure(private var paint: TextPaint) { if (charArray[i].isLowSurrogate()) continue val width = ceil(widths[i]) widthsList.add(width) - if (width == 0f && widthsList.size > 0) { + // 可能需要检查是否不可见字符 + if (width == 0f && widthsList.size > 1) { val lastIndex = widthsList.lastIndex buf[0] = codePoints[lastIndex - 1] widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1)) @@ -46,8 +46,6 @@ class TextMeasure(private var paint: TextPaint) { val width = widthsList[i] if (codePoint < 128) { asciiWidths[codePoint] = width - } else if (width == chineseCommonWidth) { - chineseCommonWidthBitSet.set(codePoint) } else { codePointWidths[codePoint] = width } @@ -138,7 +136,6 @@ class TextMeasure(private var paint: TextPaint) { private fun invalidate() { chineseCommonWidth = paint.measureText("一") - chineseCommonWidthBitSet.clear() codePointWidths.clear() asciiWidths.fill(-1f) } diff --git a/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt b/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt index 3606b6d72..eb1dca7f5 100644 --- a/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/BatteryView.kt @@ -10,6 +10,8 @@ import android.text.StaticLayout import android.util.AttributeSet import androidx.annotation.ColorInt 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 class BatteryView @JvmOverloads constructor( @@ -22,6 +24,7 @@ class BatteryView @JvmOverloads constructor( private val batteryPaint = Paint() private val outFrame = Rect() private val polar = Rect() + private val canvasRecorder = CanvasRecorderFactory.create() var isBattery = false set(value) { field = value @@ -62,31 +65,40 @@ class BatteryView @JvmOverloads constructor( } override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - if (!isBattery) return - layout.getLineBounds(0, outFrame) - val batteryStart = layout - .getPrimaryHorizontal(text.length - battery.toString().length) - .toInt() + 2.dpToPx() - val batteryEnd = batteryStart + - StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx() - outFrame.set( - batteryStart, - 2.dpToPx(), - batteryEnd, - height - 2.dpToPx() - ) - val dj = (outFrame.bottom - outFrame.top) / 3 - polar.set( - batteryEnd, - outFrame.top + dj, - batteryEnd + 2.dpToPx(), - outFrame.bottom - dj - ) - batteryPaint.style = Paint.Style.STROKE - canvas.drawRect(outFrame, batteryPaint) - batteryPaint.style = Paint.Style.FILL - canvas.drawRect(polar, batteryPaint) + canvasRecorder.recordIfNeededThenDraw(canvas, width, height) { + super.onDraw(this) + if (!isBattery) return@recordIfNeededThenDraw + layout.getLineBounds(0, outFrame) + val batteryStart = layout + .getPrimaryHorizontal(text.length - battery.toString().length) + .toInt() + 2.dpToPx() + val batteryEnd = batteryStart + + StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx() + outFrame.set( + batteryStart, + 2.dpToPx(), + batteryEnd, + height - 2.dpToPx() + ) + val dj = (outFrame.bottom - outFrame.top) / 3 + polar.set( + batteryEnd, + outFrame.top + dj, + batteryEnd + 2.dpToPx(), + outFrame.bottom - dj + ) + batteryPaint.style = Paint.Style.STROKE + drawRect(outFrame, batteryPaint) + batteryPaint.style = Paint.Style.FILL + drawRect(polar, batteryPaint) + } + } + + override fun invalidate() { + super.invalidate() + kotlin.runCatching { + canvasRecorder.invalidate() + } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/ChineseUtils.kt b/app/src/main/java/io/legado/app/utils/ChineseUtils.kt index d7d01bda9..34744e32c 100644 --- a/app/src/main/java/io/legado/app/utils/ChineseUtils.kt +++ b/app/src/main/java/io/legado/app/utils/ChineseUtils.kt @@ -32,26 +32,19 @@ object ChineseUtils { fun fixT2sDict() { val dict = DictionaryContainer.getInstance().getDictionary(TransType.TRADITIONAL_TO_SIMPLE) dict.run { - remove("劈") - remove("脊") - remove("支援") - remove("沈默") - remove("類比") - remove("模擬") - remove("划槳") - remove("列根") - remove("路易斯") - remove("非同步") - remove("出租车") - remove("周杰倫") + remove("劈", "脊") + remove("支援", "沈默", "類比", "模擬", "划槳", "列根", "先進") + remove("路易斯", "非同步", "出租车", "周杰倫") } } - fun BasicDictionary.remove(key: String) { - if (key.length == 1) { - chars.remove(key[0]) - } else { - dict.remove(key) + fun BasicDictionary.remove(vararg keys: String) { + for (key in keys) { + if (key.length == 1) { + chars.remove(key[0]) + } else { + dict.remove(key) + } } } diff --git a/app/src/main/java/io/legado/app/utils/PictureMirror.kt b/app/src/main/java/io/legado/app/utils/PictureMirror.kt deleted file mode 100644 index 21aebec25..000000000 --- a/app/src/main/java/io/legado/app/utils/PictureMirror.kt +++ /dev/null @@ -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 - } - -} diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index 9dc94f8bc..89a8887bf 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -37,6 +37,8 @@ import androidx.viewpager.widget.ViewPager import io.legado.app.help.config.AppConfig import io.legado.app.help.globalExecutor 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 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) { setPadding(paddingLeft, paddingTop, paddingRight, bottom) } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt new file mode 100644 index 000000000..ee521b0b4 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt @@ -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() + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt new file mode 100644 index 000000000..904d1bf56 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt @@ -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 + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt new file mode 100644 index 000000000..b23024e85 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt @@ -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 + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt new file mode 100644 index 000000000..98c7a765c --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt @@ -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 + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt new file mode 100644 index 000000000..9e4ddbff7 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt @@ -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) +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt new file mode 100644 index 000000000..038331557 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt @@ -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 + } + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt new file mode 100644 index 000000000..40ad53151 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt @@ -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 + } + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt new file mode 100644 index 000000000..993491abd --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt @@ -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 + } + +}