mirror of
https://github.com/gedoor/legado.git
synced 2024-08-30 09:23:26 +08:00
优化
This commit is contained in:
parent
d115910e9b
commit
7fbc23ab13
@ -137,6 +137,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val textSelectAble: Boolean
|
||||||
|
get() = appCtx.getPrefBoolean(PreferKey.textSelectAble, true)
|
||||||
|
|
||||||
val isTransparentStatusBar: Boolean
|
val isTransparentStatusBar: Boolean
|
||||||
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)
|
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.help.coroutine
|
package io.legado.app.help.coroutine
|
||||||
|
|
||||||
|
import android.os.Looper
|
||||||
import io.legado.app.utils.printOnDebug
|
import io.legado.app.utils.printOnDebug
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CompletionHandler
|
import kotlinx.coroutines.CompletionHandler
|
||||||
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,6 +35,9 @@ class Coroutine<T>(
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val DEFAULT = MainScope()
|
private val DEFAULT = MainScope()
|
||||||
|
private val launchExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
private val mainThread = Looper.getMainLooper().thread
|
||||||
|
private val isMainThread inline get() = mainThread === Thread.currentThread()
|
||||||
|
|
||||||
fun <T> async(
|
fun <T> async(
|
||||||
scope: CoroutineScope = DEFAULT,
|
scope: CoroutineScope = DEFAULT,
|
||||||
@ -46,7 +51,7 @@ class Coroutine<T>(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val job: Job
|
private val job: Job by lazy { executeInternal(context, block) }
|
||||||
|
|
||||||
private var start: VoidCallback? = null
|
private var start: VoidCallback? = null
|
||||||
private var success: Callback<T>? = null
|
private var success: Callback<T>? = null
|
||||||
@ -67,7 +72,13 @@ class Coroutine<T>(
|
|||||||
get() = job.isCompleted
|
get() = job.isCompleted
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.job = executeInternal(context, block)
|
if (context == Dispatchers.Main.immediate && isMainThread) {
|
||||||
|
job
|
||||||
|
} else {
|
||||||
|
launchExecutor.execute {
|
||||||
|
job
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun timeout(timeMillis: () -> Long): Coroutine<T> {
|
fun timeout(timeMillis: () -> Long): Coroutine<T> {
|
||||||
|
@ -275,7 +275,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
textChapter.getPage(index - 2)?.recyclePictures()
|
textChapter.getPage(index - 2)?.recyclePictures()
|
||||||
}
|
}
|
||||||
if (index < pageIndex) {
|
if (index < pageIndex) {
|
||||||
textChapter.getPage(index + 2)?.recyclePictures()
|
textChapter.getPage(index + 3)?.recyclePictures()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
durChapterPos = curTextChapter?.getReadLength(index) ?: index
|
durChapterPos = curTextChapter?.getReadLength(index) ?: index
|
||||||
|
@ -58,6 +58,12 @@ abstract class BaseReadBookActivity :
|
|||||||
override val binding by viewBinding(ActivityBookReadBinding::inflate)
|
override val binding by viewBinding(ActivityBookReadBinding::inflate)
|
||||||
override val viewModel by viewModels<ReadBookViewModel>()
|
override val viewModel by viewModels<ReadBookViewModel>()
|
||||||
var bottomDialog = 0
|
var bottomDialog = 0
|
||||||
|
set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
field = value
|
||||||
|
onBottomDialogChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) {
|
private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) {
|
||||||
it.uri?.let { uri ->
|
it.uri?.let { uri ->
|
||||||
ReadBook.book?.let { book ->
|
ReadBook.book?.let { book ->
|
||||||
@ -94,6 +100,21 @@ abstract class BaseReadBookActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onBottomDialogChange() {
|
||||||
|
when (bottomDialog) {
|
||||||
|
0 -> onMenuHide()
|
||||||
|
1 -> onMenuShow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onMenuShow() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onMenuHide() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun showPaddingConfig() {
|
fun showPaddingConfig() {
|
||||||
showDialogFragment<PaddingConfigDialog>()
|
showDialogFragment<PaddingConfigDialog>()
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ import io.legado.app.utils.ACache
|
|||||||
import io.legado.app.utils.Debounce
|
import io.legado.app.utils.Debounce
|
||||||
import io.legado.app.utils.LogUtils
|
import io.legado.app.utils.LogUtils
|
||||||
import io.legado.app.utils.StartActivityContract
|
import io.legado.app.utils.StartActivityContract
|
||||||
import io.legado.app.utils.SyncedRenderer
|
|
||||||
import io.legado.app.utils.applyOpenTint
|
import io.legado.app.utils.applyOpenTint
|
||||||
import io.legado.app.utils.buildMainHandler
|
import io.legado.app.utils.buildMainHandler
|
||||||
import io.legado.app.utils.getPrefBoolean
|
import io.legado.app.utils.getPrefBoolean
|
||||||
@ -201,8 +200,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
}
|
}
|
||||||
override val isInitFinish: Boolean get() = viewModel.isInitFinish
|
override val isInitFinish: Boolean get() = viewModel.isInitFinish
|
||||||
override val isScroll: Boolean get() = binding.readView.isScroll
|
override val isScroll: Boolean get() = binding.readView.isScroll
|
||||||
override var autoPageProgress = 0
|
private val isAutoPage get() = binding.readView.isAutoPage
|
||||||
override var isAutoPage = false
|
|
||||||
override var isShowingSearchResult = false
|
override var isShowingSearchResult = false
|
||||||
override var isSelectingSearchResult = false
|
override var isSelectingSearchResult = false
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -220,8 +218,6 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
private var bookChanged = false
|
private var bookChanged = false
|
||||||
private var pageChanged = false
|
private var pageChanged = false
|
||||||
private var reloadContent = false
|
private var reloadContent = false
|
||||||
private val autoPageRenderer by lazy { SyncedRenderer { doAutoPage(it) } }
|
|
||||||
private var autoPageScrollOffset = 0.0
|
|
||||||
private val handler by lazy { buildMainHandler() }
|
private val handler by lazy { buildMainHandler() }
|
||||||
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
|
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
|
||||||
|
|
||||||
@ -934,6 +930,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
ReadAloud.upTtsProgress(this)
|
ReadAloud.upTtsProgress(this)
|
||||||
}
|
}
|
||||||
loadStates = true
|
loadStates = true
|
||||||
|
binding.readView.onContentLoadFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -945,9 +942,6 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
success: (() -> Unit)?
|
success: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (relativePosition == 0) {
|
|
||||||
autoPageProgress = 0
|
|
||||||
}
|
|
||||||
binding.readView.upContent(relativePosition, resetPageOffset)
|
binding.readView.upContent(relativePosition, resetPageOffset)
|
||||||
upSeekBarProgress()
|
upSeekBarProgress()
|
||||||
loadStates = false
|
loadStates = false
|
||||||
@ -970,8 +964,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
*/
|
*/
|
||||||
override fun pageChanged() {
|
override fun pageChanged() {
|
||||||
pageChanged = true
|
pageChanged = true
|
||||||
lifecycleScope.launch {
|
handler.post {
|
||||||
autoPageProgress = 0
|
|
||||||
upSeekBarProgress()
|
upSeekBarProgress()
|
||||||
startBackupJob()
|
startBackupJob()
|
||||||
}
|
}
|
||||||
@ -1053,8 +1046,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
if (isAutoPage) {
|
if (isAutoPage) {
|
||||||
autoPageStop()
|
autoPageStop()
|
||||||
} else {
|
} else {
|
||||||
isAutoPage = true
|
binding.readView.autoPager.start()
|
||||||
autoPagePlus()
|
|
||||||
binding.readMenu.setAutoPage(true)
|
binding.readMenu.setAutoPage(true)
|
||||||
screenTimeOut = -1L
|
screenTimeOut = -1L
|
||||||
screenOffTimerStart()
|
screenOffTimerStart()
|
||||||
@ -1063,53 +1055,12 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
|
|
||||||
override fun autoPageStop() {
|
override fun autoPageStop() {
|
||||||
if (isAutoPage) {
|
if (isAutoPage) {
|
||||||
isAutoPage = false
|
binding.readView.autoPager.stop()
|
||||||
autoPageRenderer.stop()
|
|
||||||
binding.readView.invalidate()
|
|
||||||
binding.readView.clearNextPageBitmap()
|
|
||||||
binding.readMenu.setAutoPage(false)
|
binding.readMenu.setAutoPage(false)
|
||||||
upScreenTimeOut()
|
upScreenTimeOut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun autoPagePlus() {
|
|
||||||
autoPageProgress = 0
|
|
||||||
autoPageScrollOffset = 0.0
|
|
||||||
autoPageRenderer.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doAutoPage(frameTime: Double) {
|
|
||||||
if (menuLayoutIsVisible) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (binding.readView.run { isScroll && pageDelegate?.isRunning == true }) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val readTime = ReadBookConfig.autoReadSpeed * 1000.0
|
|
||||||
val height = binding.readView.height
|
|
||||||
autoPageScrollOffset += height / readTime * frameTime
|
|
||||||
if (autoPageScrollOffset < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val scrollOffset = autoPageScrollOffset.toInt()
|
|
||||||
autoPageScrollOffset -= scrollOffset
|
|
||||||
if (binding.readView.isScroll) {
|
|
||||||
binding.readView.curPage.scroll(-scrollOffset)
|
|
||||||
} else {
|
|
||||||
autoPageProgress += scrollOffset
|
|
||||||
if (autoPageProgress >= height) {
|
|
||||||
autoPageProgress = 0
|
|
||||||
if (!binding.readView.fillPage(PageDirection.NEXT)) {
|
|
||||||
autoPageStop()
|
|
||||||
} else {
|
|
||||||
binding.readView.clearNextPageBitmap()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binding.readView.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun openSourceEditActivity() {
|
override fun openSourceEditActivity() {
|
||||||
ReadBook.bookSource?.let {
|
ReadBook.bookSource?.let {
|
||||||
sourceEditActivity.launch {
|
sourceEditActivity.launch {
|
||||||
@ -1414,6 +1365,14 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
skipToSearch(searchResult)
|
skipToSearch(searchResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onMenuShow() {
|
||||||
|
binding.readView.autoPager.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuHide() {
|
||||||
|
binding.readView.autoPager.resume()
|
||||||
|
}
|
||||||
|
|
||||||
/* 全文搜索跳转 */
|
/* 全文搜索跳转 */
|
||||||
private fun skipToSearch(searchResult: SearchResult) {
|
private fun skipToSearch(searchResult: SearchResult) {
|
||||||
val previousResult = binding.searchMenu.previousSearchResult
|
val previousResult = binding.searchMenu.previousSearchResult
|
||||||
|
@ -27,13 +27,38 @@ import io.legado.app.help.config.LocalConfig
|
|||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.help.config.ThemeConfig
|
import io.legado.app.help.config.ThemeConfig
|
||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
import io.legado.app.lib.theme.*
|
import io.legado.app.lib.theme.Selector
|
||||||
|
import io.legado.app.lib.theme.accentColor
|
||||||
|
import io.legado.app.lib.theme.bottomBackground
|
||||||
|
import io.legado.app.lib.theme.buttonDisabledColor
|
||||||
|
import io.legado.app.lib.theme.getPrimaryTextColor
|
||||||
|
import io.legado.app.lib.theme.primaryColor
|
||||||
|
import io.legado.app.lib.theme.primaryTextColor
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.ui.book.info.BookInfoActivity
|
import io.legado.app.ui.book.info.BookInfoActivity
|
||||||
import io.legado.app.ui.browser.WebViewActivity
|
import io.legado.app.ui.browser.WebViewActivity
|
||||||
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
|
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.ColorUtils
|
||||||
import splitties.views.*
|
import io.legado.app.utils.ConstraintModify
|
||||||
|
import io.legado.app.utils.activity
|
||||||
|
import io.legado.app.utils.dpToPx
|
||||||
|
import io.legado.app.utils.getPrefBoolean
|
||||||
|
import io.legado.app.utils.gone
|
||||||
|
import io.legado.app.utils.invisible
|
||||||
|
import io.legado.app.utils.loadAnimation
|
||||||
|
import io.legado.app.utils.modifyBegin
|
||||||
|
import io.legado.app.utils.navigationBarGravity
|
||||||
|
import io.legado.app.utils.navigationBarHeight
|
||||||
|
import io.legado.app.utils.openUrl
|
||||||
|
import io.legado.app.utils.putPrefBoolean
|
||||||
|
import io.legado.app.utils.startActivity
|
||||||
|
import io.legado.app.utils.visible
|
||||||
|
import splitties.views.bottomPadding
|
||||||
|
import splitties.views.leftPadding
|
||||||
|
import splitties.views.onClick
|
||||||
|
import splitties.views.onLongClick
|
||||||
|
import splitties.views.padding
|
||||||
|
import splitties.views.rightPadding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阅读界面菜单
|
* 阅读界面菜单
|
||||||
@ -273,6 +298,7 @@ class ReadMenu @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) {
|
fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) {
|
||||||
|
callBack.onMenuShow()
|
||||||
this.visible()
|
this.visible()
|
||||||
binding.titleBar.visible()
|
binding.titleBar.visible()
|
||||||
binding.bottomMenu.visible()
|
binding.bottomMenu.visible()
|
||||||
@ -286,6 +312,7 @@ class ReadMenu @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode, onMenuOutEnd: (() -> Unit)? = null) {
|
fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode, onMenuOutEnd: (() -> Unit)? = null) {
|
||||||
|
callBack.onMenuHide()
|
||||||
this.onMenuOutEnd = onMenuOutEnd
|
this.onMenuOutEnd = onMenuOutEnd
|
||||||
if (this.isVisible) {
|
if (this.isVisible) {
|
||||||
if (anim) {
|
if (anim) {
|
||||||
@ -558,6 +585,8 @@ class ReadMenu @JvmOverloads constructor(
|
|||||||
fun payAction()
|
fun payAction()
|
||||||
fun disableSource()
|
fun disableSource()
|
||||||
fun skipToChapter(index: Int)
|
fun skipToChapter(index: Int)
|
||||||
|
fun onMenuShow()
|
||||||
|
fun onMenuHide()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,13 @@ import io.legado.app.lib.theme.bottomBackground
|
|||||||
import io.legado.app.lib.theme.getPrimaryTextColor
|
import io.legado.app.lib.theme.getPrimaryTextColor
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.ui.book.searchContent.SearchResult
|
import io.legado.app.ui.book.searchContent.SearchResult
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.ColorUtils
|
||||||
|
import io.legado.app.utils.activity
|
||||||
|
import io.legado.app.utils.invisible
|
||||||
|
import io.legado.app.utils.loadAnimation
|
||||||
|
import io.legado.app.utils.navigationBarGravity
|
||||||
|
import io.legado.app.utils.navigationBarHeight
|
||||||
|
import io.legado.app.utils.visible
|
||||||
import splitties.views.bottomPadding
|
import splitties.views.bottomPadding
|
||||||
import splitties.views.leftPadding
|
import splitties.views.leftPadding
|
||||||
import splitties.views.padding
|
import splitties.views.padding
|
||||||
@ -235,6 +241,8 @@ class SearchMenu @JvmOverloads constructor(
|
|||||||
fun exitSearchMenu()
|
fun exitSearchMenu()
|
||||||
fun showMenuBar()
|
fun showMenuBar()
|
||||||
fun navigateToSearch(searchResult: SearchResult, index: Int)
|
fun navigateToSearch(searchResult: SearchResult, index: Int)
|
||||||
|
fun onMenuShow()
|
||||||
|
fun onMenuHide()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
146
app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt
Normal file
146
app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package io.legado.app.ui.book.read.page
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Picture
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.SystemClock
|
||||||
|
import androidx.core.graphics.withClip
|
||||||
|
import io.legado.app.help.config.AppConfig
|
||||||
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
|
import io.legado.app.lib.theme.ThemeStore
|
||||||
|
import io.legado.app.ui.book.read.page.entities.PageDirection
|
||||||
|
import io.legado.app.utils.screenshot
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动翻页
|
||||||
|
*/
|
||||||
|
class AutoPager(private val readView: ReadView) {
|
||||||
|
private var progress = 0
|
||||||
|
var isRunning = false
|
||||||
|
private var isPausing = false
|
||||||
|
private var scrollOffsetRemain = 0.0
|
||||||
|
private var scrollOffset = 0
|
||||||
|
private var lastTimeMillis = 0L
|
||||||
|
private var bitmap: Bitmap? = null
|
||||||
|
private var picture: Picture? = null
|
||||||
|
private var pictureIsDirty = true
|
||||||
|
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
|
private val rect = Rect()
|
||||||
|
private val paint by lazy { Paint() }
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
isRunning = true
|
||||||
|
paint.color = ThemeStore.accentColor
|
||||||
|
lastTimeMillis = SystemClock.uptimeMillis()
|
||||||
|
readView.curPage.upSelectAble(false)
|
||||||
|
readView.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (!isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isRunning = false
|
||||||
|
isPausing = false
|
||||||
|
readView.curPage.upSelectAble(AppConfig.textSelectAble)
|
||||||
|
readView.invalidate()
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
if (!isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isPausing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
if (!isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isPausing = false
|
||||||
|
lastTimeMillis = SystemClock.uptimeMillis()
|
||||||
|
readView.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
progress = 0
|
||||||
|
scrollOffsetRemain = 0.0
|
||||||
|
scrollOffset = 0
|
||||||
|
bitmap?.recycle()
|
||||||
|
bitmap = null
|
||||||
|
pictureIsDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDraw(canvas: Canvas) {
|
||||||
|
if (!isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readView.isScroll) {
|
||||||
|
computeOffset()
|
||||||
|
if (!isPausing) readView.curPage.scroll(-scrollOffset)
|
||||||
|
} else {
|
||||||
|
val bottom = progress
|
||||||
|
val width = readView.width
|
||||||
|
if (atLeastApi23) {
|
||||||
|
if (picture == null) {
|
||||||
|
picture = Picture()
|
||||||
|
}
|
||||||
|
if (pictureIsDirty) {
|
||||||
|
pictureIsDirty = false
|
||||||
|
readView.nextPage.screenshot(picture!!)
|
||||||
|
}
|
||||||
|
canvas.withClip(0, 0, width, bottom) {
|
||||||
|
drawPicture(picture!!)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bitmap == null) {
|
||||||
|
bitmap = readView.nextPage.screenshot()
|
||||||
|
}
|
||||||
|
rect.set(0, 0, width, bottom)
|
||||||
|
canvas.drawBitmap(bitmap!!, rect, rect, null)
|
||||||
|
}
|
||||||
|
canvas.drawRect(
|
||||||
|
0f,
|
||||||
|
bottom.toFloat() - 1,
|
||||||
|
width.toFloat(),
|
||||||
|
bottom.toFloat(),
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
if (!isPausing) readView.invalidate()
|
||||||
|
computeOffset()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeOffset() {
|
||||||
|
|
||||||
|
val currentTime = SystemClock.uptimeMillis()
|
||||||
|
val elapsedTime = currentTime - lastTimeMillis
|
||||||
|
lastTimeMillis = currentTime
|
||||||
|
|
||||||
|
val readTime = ReadBookConfig.autoReadSpeed * 1000.0
|
||||||
|
val height = readView.height
|
||||||
|
scrollOffsetRemain += height / readTime * elapsedTime
|
||||||
|
if (scrollOffsetRemain < 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scrollOffset = scrollOffsetRemain.toInt()
|
||||||
|
this.scrollOffsetRemain -= scrollOffset
|
||||||
|
if (!readView.isScroll) {
|
||||||
|
progress += scrollOffset
|
||||||
|
if (progress >= height) {
|
||||||
|
if (!readView.fillPage(PageDirection.NEXT)) {
|
||||||
|
stop()
|
||||||
|
} else {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,7 +9,6 @@ import android.view.View
|
|||||||
import androidx.core.graphics.withTranslation
|
import androidx.core.graphics.withTranslation
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.constant.PageAnim
|
import io.legado.app.constant.PageAnim
|
||||||
import io.legado.app.constant.PreferKey
|
|
||||||
import io.legado.app.data.entities.Bookmark
|
import io.legado.app.data.entities.Bookmark
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
@ -28,16 +27,16 @@ import io.legado.app.ui.widget.dialog.PhotoDialog
|
|||||||
import io.legado.app.utils.PictureMirror
|
import io.legado.app.utils.PictureMirror
|
||||||
import io.legado.app.utils.activity
|
import io.legado.app.utils.activity
|
||||||
import io.legado.app.utils.getCompatColor
|
import io.legado.app.utils.getCompatColor
|
||||||
import io.legado.app.utils.getPrefBoolean
|
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阅读内容视图
|
* 阅读内容视图
|
||||||
*/
|
*/
|
||||||
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
||||||
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true)
|
var selectAble = AppConfig.textSelectAble
|
||||||
val selectedPaint by lazy {
|
val selectedPaint by lazy {
|
||||||
Paint().apply {
|
Paint().apply {
|
||||||
color = context.getCompatColor(R.color.btn_bg_press_2)
|
color = context.getCompatColor(R.color.btn_bg_press_2)
|
||||||
@ -61,6 +60,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
private var pageOffset = 0
|
private var pageOffset = 0
|
||||||
private val pictureMirror = PictureMirror()
|
private val pictureMirror = PictureMirror()
|
||||||
private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim
|
private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim
|
||||||
|
private var autoPager: AutoPager? = null
|
||||||
|
private val renderThread by lazy { Executors.newSingleThreadExecutor() }
|
||||||
|
private val renderRunnable by lazy { Runnable { preRenderPage() } }
|
||||||
|
|
||||||
//绘制图片的paint
|
//绘制图片的paint
|
||||||
val imagePaint by lazy {
|
val imagePaint by lazy {
|
||||||
@ -91,6 +93,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
|
autoPager?.onDraw(canvas)
|
||||||
if (longScreenshot) {
|
if (longScreenshot) {
|
||||||
canvas.translate(0f, scrollY.toFloat())
|
canvas.translate(0f, scrollY.toFloat())
|
||||||
}
|
}
|
||||||
@ -179,6 +182,20 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
pictureMirror.invalidate()
|
pictureMirror.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun submitPreRenderTask() {
|
||||||
|
renderThread.submit(renderRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun preRenderPage() {
|
||||||
|
val view = this
|
||||||
|
pageFactory.run {
|
||||||
|
prevPage.preRender(view)
|
||||||
|
prevPage.preRender(view)
|
||||||
|
nextPage.preRender(view)
|
||||||
|
nextPlusPage.preRender(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置滚动位置
|
* 重置滚动位置
|
||||||
*/
|
*/
|
||||||
@ -669,6 +686,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setAutoPager(autoPager: AutoPager?) {
|
||||||
|
this.autoPager = autoPager
|
||||||
|
}
|
||||||
|
|
||||||
override fun canScrollVertically(direction: Int): Boolean {
|
override fun canScrollVertically(direction: Int): Boolean {
|
||||||
return callBack.isScroll && pageFactory.hasNext()
|
return callBack.isScroll && pageFactory.hasNext()
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
private var tvBookName: BatteryView? = null
|
private var tvBookName: BatteryView? = null
|
||||||
private var tvTimeBattery: BatteryView? = null
|
private var tvTimeBattery: BatteryView? = null
|
||||||
private var tvTimeBatteryP: BatteryView? = null
|
private var tvTimeBatteryP: BatteryView? = null
|
||||||
|
private var isMainView = false
|
||||||
|
var isScroll = false
|
||||||
|
|
||||||
val headerHeight: Int
|
val headerHeight: Int
|
||||||
get() {
|
get() {
|
||||||
@ -272,7 +274,13 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
* 设置内容
|
* 设置内容
|
||||||
*/
|
*/
|
||||||
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
|
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
|
||||||
|
if (isMainView && !isScroll) {
|
||||||
setProgress(textPage)
|
setProgress(textPage)
|
||||||
|
} else {
|
||||||
|
post {
|
||||||
|
setProgress(textPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (resetPageOffset) {
|
if (resetPageOffset) {
|
||||||
resetPageOffset()
|
resetPageOffset()
|
||||||
}
|
}
|
||||||
@ -324,6 +332,14 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
|
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setAutoPager(autoPager: AutoPager?) {
|
||||||
|
binding.contentTextView.setAutoPager(autoPager)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitPreRenderTask() {
|
||||||
|
binding.contentTextView.submitPreRenderTask()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 滚动事件
|
* 滚动事件
|
||||||
*/
|
*/
|
||||||
@ -375,6 +391,7 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun markAsMainView() {
|
fun markAsMainView() {
|
||||||
|
isMainView = true
|
||||||
binding.contentTextView.isMainView = true
|
binding.contentTextView.isMainView = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
|||||||
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
||||||
import io.legado.app.utils.activity
|
import io.legado.app.utils.activity
|
||||||
import io.legado.app.utils.invisible
|
import io.legado.app.utils.invisible
|
||||||
import io.legado.app.utils.screenshot
|
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.visible
|
import io.legado.app.utils.visible
|
||||||
import java.text.BreakIterator
|
import java.text.BreakIterator
|
||||||
@ -108,6 +107,8 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
private val autoPagePint by lazy { Paint().apply { color = context.accentColor } }
|
private val autoPagePint by lazy { Paint().apply { color = context.accentColor } }
|
||||||
private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) }
|
private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) }
|
||||||
private var nextPageBitmap: Bitmap? = null
|
private var nextPageBitmap: Bitmap? = null
|
||||||
|
val autoPager = AutoPager(this)
|
||||||
|
val isAutoPage get() = autoPager.isRunning
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addView(nextPage)
|
addView(nextPage)
|
||||||
@ -149,22 +150,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
override fun dispatchDraw(canvas: Canvas) {
|
override fun dispatchDraw(canvas: Canvas) {
|
||||||
super.dispatchDraw(canvas)
|
super.dispatchDraw(canvas)
|
||||||
pageDelegate?.onDraw(canvas)
|
pageDelegate?.onDraw(canvas)
|
||||||
if (!isInEditMode && callBack.isAutoPage && !isScroll) {
|
autoPager.onDraw(canvas)
|
||||||
// 自动翻页
|
|
||||||
val bitmap = nextPageBitmap ?: nextPage.screenshot()?.also { nextPageBitmap = it }
|
|
||||||
bitmap?.let {
|
|
||||||
val bottom = callBack.autoPageProgress
|
|
||||||
autoPageRect.set(0, 0, width, bottom)
|
|
||||||
canvas.drawBitmap(it, autoPageRect, autoPageRect, null)
|
|
||||||
canvas.drawRect(
|
|
||||||
0f,
|
|
||||||
bottom.toFloat() - 1,
|
|
||||||
width.toFloat(),
|
|
||||||
bottom.toFloat(),
|
|
||||||
autoPagePint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun computeScroll() {
|
override fun computeScroll() {
|
||||||
@ -262,6 +248,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
pageDelegate?.onTouch(event)
|
pageDelegate?.onTouch(event)
|
||||||
}
|
}
|
||||||
pressOnTextSelected = false
|
pressOnTextSelected = false
|
||||||
|
autoPager.resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -530,6 +517,12 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
} else {
|
} else {
|
||||||
nextPage.visible()
|
nextPage.visible()
|
||||||
}
|
}
|
||||||
|
if (isScroll) {
|
||||||
|
curPage.setAutoPager(autoPager)
|
||||||
|
} else {
|
||||||
|
curPage.setAutoPager(null)
|
||||||
|
}
|
||||||
|
curPage.isScroll = isScroll
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -541,12 +534,13 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
post {
|
post {
|
||||||
curPage.setContentDescription(pageFactory.curPage.text)
|
curPage.setContentDescription(pageFactory.curPage.text)
|
||||||
}
|
}
|
||||||
if (isScroll && !callBack.isAutoPage) {
|
if (isScroll && !isAutoPage) {
|
||||||
curPage.setContent(pageFactory.curPage, resetPageOffset)
|
curPage.setContent(pageFactory.curPage, resetPageOffset)
|
||||||
} else {
|
} else {
|
||||||
if (callBack.isAutoPage && relativePosition >= 0) {
|
// if (isAutoPage && relativePosition >= 0) {
|
||||||
clearNextPageBitmap()
|
//// clearNextPageBitmap()
|
||||||
}
|
// autoPager.clear()
|
||||||
|
// }
|
||||||
when (relativePosition) {
|
when (relativePosition) {
|
||||||
-1 -> prevPage.setContent(pageFactory.prevPage)
|
-1 -> prevPage.setContent(pageFactory.prevPage)
|
||||||
1 -> nextPage.setContent(pageFactory.nextPage)
|
1 -> nextPage.setContent(pageFactory.nextPage)
|
||||||
@ -663,6 +657,24 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onScrollAnimStart() {
|
||||||
|
autoPager.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onScrollAnimStop() {
|
||||||
|
autoPager.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageChange() {
|
||||||
|
autoPager.reset()
|
||||||
|
curPage.submitPreRenderTask()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onContentLoadFinish() {
|
||||||
|
autoPager.reset()
|
||||||
|
curPage.submitPreRenderTask()
|
||||||
|
}
|
||||||
|
|
||||||
override val currentChapter: TextChapter?
|
override val currentChapter: TextChapter?
|
||||||
get() {
|
get() {
|
||||||
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
||||||
@ -688,8 +700,6 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
|
|
||||||
interface CallBack {
|
interface CallBack {
|
||||||
val isInitFinish: Boolean
|
val isInitFinish: Boolean
|
||||||
val isAutoPage: Boolean
|
|
||||||
val autoPageProgress: Int
|
|
||||||
fun showActionMenu()
|
fun showActionMenu()
|
||||||
fun screenOffTimerStart()
|
fun screenOffTimerStart()
|
||||||
fun showTextActionMenu()
|
fun showTextActionMenu()
|
||||||
|
@ -20,4 +20,6 @@ interface DataSource {
|
|||||||
fun hasPrevChapter(): Boolean
|
fun hasPrevChapter(): Boolean
|
||||||
|
|
||||||
fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)
|
fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)
|
||||||
|
|
||||||
|
fun onPageChange()
|
||||||
}
|
}
|
@ -21,6 +21,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {
|
|||||||
var noAnim: Boolean = false
|
var noAnim: Boolean = false
|
||||||
|
|
||||||
override fun onAnimStart(animationSpeed: Int) {
|
override fun onAnimStart(animationSpeed: Int) {
|
||||||
|
readView.onScrollAnimStart()
|
||||||
//惯性滚动
|
//惯性滚动
|
||||||
fling(
|
fling(
|
||||||
0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(),
|
0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(),
|
||||||
@ -29,7 +30,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onAnimStop() {
|
override fun onAnimStop() {
|
||||||
// nothing
|
readView.onScrollAnimStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouch(event: MotionEvent) {
|
override fun onTouch(event: MotionEvent) {
|
||||||
|
@ -121,7 +121,7 @@ data class TextLine(
|
|||||||
return visible
|
return visible
|
||||||
}
|
}
|
||||||
|
|
||||||
fun draw(view: ContentTextView, canvas: Canvas) {
|
fun draw(view: ContentTextView, canvas: Canvas?) {
|
||||||
pictureMirror.draw(canvas, view.width, height.toInt()) {
|
pictureMirror.draw(canvas, view.width, height.toInt()) {
|
||||||
drawTextLine(view, this)
|
drawTextLine(view, this)
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,7 @@ data class TextPage(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun draw(view: ContentTextView, canvas: Canvas) {
|
fun draw(view: ContentTextView, canvas: Canvas?) {
|
||||||
pictureMirror.draw(canvas, view.width, height.toInt()) {
|
pictureMirror.draw(canvas, view.width, height.toInt()) {
|
||||||
drawPage(view, this)
|
drawPage(view, this)
|
||||||
}
|
}
|
||||||
@ -278,6 +278,15 @@ data class TextPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun preRender(view: ContentTextView) {
|
||||||
|
if (!pictureMirror.isDirty) return
|
||||||
|
draw(view, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDirty(): Boolean {
|
||||||
|
return pictureMirror.isDirty
|
||||||
|
}
|
||||||
|
|
||||||
fun invalidate() {
|
fun invalidate() {
|
||||||
pictureMirror.invalidate()
|
pictureMirror.invalidate()
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
ReadBook.setPageIndex(pageIndex.plus(1))
|
ReadBook.setPageIndex(pageIndex.plus(1))
|
||||||
}
|
}
|
||||||
if (upContent) upContent(resetPageOffset = false)
|
if (upContent) upContent(resetPageOffset = false)
|
||||||
|
dataSource.onPageChange()
|
||||||
true
|
true
|
||||||
} else
|
} else
|
||||||
false
|
false
|
||||||
@ -63,6 +64,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
|||||||
ReadBook.setPageIndex(pageIndex.minus(1))
|
ReadBook.setPageIndex(pageIndex.minus(1))
|
||||||
}
|
}
|
||||||
if (upContent) upContent(resetPageOffset = false)
|
if (upContent) upContent(resetPageOffset = false)
|
||||||
|
dataSource.onPageChange()
|
||||||
true
|
true
|
||||||
} else
|
} else
|
||||||
false
|
false
|
||||||
|
@ -4,23 +4,31 @@ import android.graphics.Canvas
|
|||||||
import android.graphics.Picture
|
import android.graphics.Picture
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.graphics.record
|
import androidx.core.graphics.record
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
class PictureMirror {
|
class PictureMirror {
|
||||||
|
|
||||||
var picture: Picture? = null
|
var picture: Picture? = null
|
||||||
|
@Volatile
|
||||||
var isDirty = true
|
var isDirty = true
|
||||||
|
val lock = ReentrantLock()
|
||||||
|
|
||||||
inline fun draw(canvas: Canvas, width: Int, height: Int, block: Canvas.() -> Unit) {
|
inline fun draw(canvas: Canvas?, width: Int, height: Int, block: Canvas.() -> Unit) {
|
||||||
if (atLeastApi23) {
|
if (atLeastApi23) {
|
||||||
if (picture == null) picture = Picture()
|
if (picture == null) picture = Picture()
|
||||||
val picture = picture!!
|
val picture = picture!!
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
isDirty = false
|
if (!lock.tryLock()) return
|
||||||
|
try {
|
||||||
picture.record(width, height, block)
|
picture.record(width, height, block)
|
||||||
|
isDirty = false
|
||||||
|
} finally {
|
||||||
|
lock.unlock()
|
||||||
}
|
}
|
||||||
canvas.drawPicture(picture)
|
}
|
||||||
|
canvas?.drawPicture(picture)
|
||||||
} else {
|
} else {
|
||||||
canvas.block()
|
canvas?.block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user