This commit is contained in:
Horis 2024-02-18 23:21:19 +08:00
parent e8d3d98238
commit f93084f3f1
36 changed files with 683 additions and 463 deletions

View File

@ -567,7 +567,6 @@ object ReadBookConfig {
textColorInt = color
}
}
ChapterProvider.upStyle()
}
fun curTextColor(): Int {

View File

@ -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

View File

@ -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<Boolean>(EventBus.UP_CONFIG) {
upSystemUiVisibility()
readView.upPageSlopSquare()
readView.upBg()
readView.upStyle()
readView.upBgAlpha()
if (it) { // 更新内容排版布局
if (isInitFinish) {
ReadBook.loadContent(resetPageOffset = false)
} else {
reloadContent = true
observeEvent<Array<Int>>(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<Int>(EventBus.ALOUD_STATE) {
if (it == Status.STOP || it == Status.PAUSE) {

View File

@ -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))
}
}
}

View File

@ -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)
}

View File

@ -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))
}
}
}

View File

@ -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))
}
}

View File

@ -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<FontSelectDialog>()
@ -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) {

View File

@ -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)

View File

@ -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

View File

@ -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()
}

View File

@ -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)
}
/**

View File

@ -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() {

View File

@ -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

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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 {

View File

@ -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<TextColumn>()
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()
}
}

View File

@ -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)
}

View File

@ -690,39 +690,6 @@ object ChapterProvider {
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尺寸
*/
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))
}
}

View File

@ -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<Float>()
@ -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)
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}