This commit is contained in:
Horis 2024-02-13 22:19:53 +08:00
parent d115910e9b
commit 7fbc23ab13
17 changed files with 343 additions and 96 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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