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
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)

View File

@ -1,5 +1,6 @@
package io.legado.app.help.coroutine
import android.os.Looper
import io.legado.app.utils.printOnDebug
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletionHandler
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext
/**
@ -33,6 +35,9 @@ class Coroutine<T>(
companion object {
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(
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 success: Callback<T>? = null
@ -67,7 +72,13 @@ class Coroutine<T>(
get() = job.isCompleted
init {
this.job = executeInternal(context, block)
if (context == Dispatchers.Main.immediate && isMainThread) {
job
} else {
launchExecutor.execute {
job
}
}
}
fun timeout(timeMillis: () -> Long): Coroutine<T> {

View File

@ -275,7 +275,7 @@ object ReadBook : CoroutineScope by MainScope() {
textChapter.getPage(index - 2)?.recyclePictures()
}
if (index < pageIndex) {
textChapter.getPage(index + 2)?.recyclePictures()
textChapter.getPage(index + 3)?.recyclePictures()
}
}
durChapterPos = curTextChapter?.getReadLength(index) ?: index

View File

@ -58,6 +58,12 @@ abstract class BaseReadBookActivity :
override val binding by viewBinding(ActivityBookReadBinding::inflate)
override val viewModel by viewModels<ReadBookViewModel>()
var bottomDialog = 0
set(value) {
if (field != value) {
field = value
onBottomDialogChange()
}
}
private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) {
it.uri?.let { uri ->
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() {
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.LogUtils
import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.SyncedRenderer
import io.legado.app.utils.applyOpenTint
import io.legado.app.utils.buildMainHandler
import io.legado.app.utils.getPrefBoolean
@ -201,8 +200,7 @@ class ReadBookActivity : BaseReadBookActivity(),
}
override val isInitFinish: Boolean get() = viewModel.isInitFinish
override val isScroll: Boolean get() = binding.readView.isScroll
override var autoPageProgress = 0
override var isAutoPage = false
private val isAutoPage get() = binding.readView.isAutoPage
override var isShowingSearchResult = false
override var isSelectingSearchResult = false
set(value) {
@ -220,8 +218,6 @@ class ReadBookActivity : BaseReadBookActivity(),
private var bookChanged = false
private var pageChanged = 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 screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
@ -934,6 +930,7 @@ class ReadBookActivity : BaseReadBookActivity(),
ReadAloud.upTtsProgress(this)
}
loadStates = true
binding.readView.onContentLoadFinish()
}
/**
@ -945,9 +942,6 @@ class ReadBookActivity : BaseReadBookActivity(),
success: (() -> Unit)?
) {
lifecycleScope.launch {
if (relativePosition == 0) {
autoPageProgress = 0
}
binding.readView.upContent(relativePosition, resetPageOffset)
upSeekBarProgress()
loadStates = false
@ -970,8 +964,7 @@ class ReadBookActivity : BaseReadBookActivity(),
*/
override fun pageChanged() {
pageChanged = true
lifecycleScope.launch {
autoPageProgress = 0
handler.post {
upSeekBarProgress()
startBackupJob()
}
@ -1053,8 +1046,7 @@ class ReadBookActivity : BaseReadBookActivity(),
if (isAutoPage) {
autoPageStop()
} else {
isAutoPage = true
autoPagePlus()
binding.readView.autoPager.start()
binding.readMenu.setAutoPage(true)
screenTimeOut = -1L
screenOffTimerStart()
@ -1063,53 +1055,12 @@ class ReadBookActivity : BaseReadBookActivity(),
override fun autoPageStop() {
if (isAutoPage) {
isAutoPage = false
autoPageRenderer.stop()
binding.readView.invalidate()
binding.readView.clearNextPageBitmap()
binding.readView.autoPager.stop()
binding.readMenu.setAutoPage(false)
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() {
ReadBook.bookSource?.let {
sourceEditActivity.launch {
@ -1414,6 +1365,14 @@ class ReadBookActivity : BaseReadBookActivity(),
skipToSearch(searchResult)
}
override fun onMenuShow() {
binding.readView.autoPager.pause()
}
override fun onMenuHide() {
binding.readView.autoPager.resume()
}
/* 全文搜索跳转 */
private fun skipToSearch(searchResult: SearchResult) {
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.ThemeConfig
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.ui.book.info.BookInfoActivity
import io.legado.app.ui.browser.WebViewActivity
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
import io.legado.app.utils.*
import splitties.views.*
import io.legado.app.utils.ColorUtils
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) {
callBack.onMenuShow()
this.visible()
binding.titleBar.visible()
binding.bottomMenu.visible()
@ -286,6 +312,7 @@ class ReadMenu @JvmOverloads constructor(
}
fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode, onMenuOutEnd: (() -> Unit)? = null) {
callBack.onMenuHide()
this.onMenuOutEnd = onMenuOutEnd
if (this.isVisible) {
if (anim) {
@ -558,6 +585,8 @@ class ReadMenu @JvmOverloads constructor(
fun payAction()
fun disableSource()
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.model.ReadBook
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.leftPadding
import splitties.views.padding
@ -235,6 +241,8 @@ class SearchMenu @JvmOverloads constructor(
fun exitSearchMenu()
fun showMenuBar()
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 io.legado.app.R
import io.legado.app.constant.PageAnim
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Bookmark
import io.legado.app.help.config.AppConfig
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.activity
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.toastOnUi
import java.util.concurrent.Executors
import kotlin.math.min
/**
* 阅读内容视图
*/
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true)
var selectAble = AppConfig.textSelectAble
val selectedPaint by lazy {
Paint().apply {
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 val pictureMirror = PictureMirror()
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
val imagePaint by lazy {
@ -91,6 +93,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
autoPager?.onDraw(canvas)
if (longScreenshot) {
canvas.translate(0f, scrollY.toFloat())
}
@ -179,6 +182,20 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
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 {
return callBack.isScroll && pageFactory.hasNext()
}

View File

@ -45,6 +45,8 @@ class PageView(context: Context) : FrameLayout(context) {
private var tvBookName: BatteryView? = null
private var tvTimeBattery: BatteryView? = null
private var tvTimeBatteryP: BatteryView? = null
private var isMainView = false
var isScroll = false
val headerHeight: Int
get() {
@ -272,7 +274,13 @@ class PageView(context: Context) : FrameLayout(context) {
* 设置内容
*/
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
setProgress(textPage)
if (isMainView && !isScroll) {
setProgress(textPage)
} else {
post {
setProgress(textPage)
}
}
if (resetPageOffset) {
resetPageOffset()
}
@ -324,6 +332,14 @@ class PageView(context: Context) : FrameLayout(context) {
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() {
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.utils.activity
import io.legado.app.utils.invisible
import io.legado.app.utils.screenshot
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.visible
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 boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) }
private var nextPageBitmap: Bitmap? = null
val autoPager = AutoPager(this)
val isAutoPage get() = autoPager.isRunning
init {
addView(nextPage)
@ -149,22 +150,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
pageDelegate?.onDraw(canvas)
if (!isInEditMode && callBack.isAutoPage && !isScroll) {
// 自动翻页
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
)
}
}
autoPager.onDraw(canvas)
}
override fun computeScroll() {
@ -262,6 +248,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
pageDelegate?.onTouch(event)
}
pressOnTextSelected = false
autoPager.resume()
}
}
return true
@ -530,6 +517,12 @@ class ReadView(context: Context, attrs: AttributeSet) :
} else {
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 {
curPage.setContentDescription(pageFactory.curPage.text)
}
if (isScroll && !callBack.isAutoPage) {
if (isScroll && !isAutoPage) {
curPage.setContent(pageFactory.curPage, resetPageOffset)
} else {
if (callBack.isAutoPage && relativePosition >= 0) {
clearNextPageBitmap()
}
// if (isAutoPage && relativePosition >= 0) {
//// clearNextPageBitmap()
// autoPager.clear()
// }
when (relativePosition) {
-1 -> prevPage.setContent(pageFactory.prevPage)
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?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
@ -688,8 +700,6 @@ class ReadView(context: Context, attrs: AttributeSet) :
interface CallBack {
val isInitFinish: Boolean
val isAutoPage: Boolean
val autoPageProgress: Int
fun showActionMenu()
fun screenOffTimerStart()
fun showTextActionMenu()

View File

@ -20,4 +20,6 @@ interface DataSource {
fun hasPrevChapter(): Boolean
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
override fun onAnimStart(animationSpeed: Int) {
readView.onScrollAnimStart()
//惯性滚动
fling(
0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(),
@ -29,7 +30,7 @@ class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {
}
override fun onAnimStop() {
// nothing
readView.onScrollAnimStop()
}
override fun onTouch(event: MotionEvent) {

View File

@ -121,7 +121,7 @@ data class TextLine(
return visible
}
fun draw(view: ContentTextView, canvas: Canvas) {
fun draw(view: ContentTextView, canvas: Canvas?) {
pictureMirror.draw(canvas, view.width, height.toInt()) {
drawTextLine(view, this)
}

View File

@ -263,7 +263,7 @@ data class TextPage(
return null
}
fun draw(view: ContentTextView, canvas: Canvas) {
fun draw(view: ContentTextView, canvas: Canvas?) {
pictureMirror.draw(canvas, view.width, height.toInt()) {
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() {
pictureMirror.invalidate()
}

View File

@ -44,6 +44,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
ReadBook.setPageIndex(pageIndex.plus(1))
}
if (upContent) upContent(resetPageOffset = false)
dataSource.onPageChange()
true
} else
false
@ -63,6 +64,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
ReadBook.setPageIndex(pageIndex.minus(1))
}
if (upContent) upContent(resetPageOffset = false)
dataSource.onPageChange()
true
} else
false

View File

@ -4,23 +4,31 @@ 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 {
var picture: Picture? = null
@Volatile
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 (picture == null) picture = Picture()
val picture = picture!!
if (isDirty) {
isDirty = false
picture.record(width, height, block)
if (!lock.tryLock()) return
try {
picture.record(width, height, block)
isDirty = false
} finally {
lock.unlock()
}
}
canvas.drawPicture(picture)
canvas?.drawPicture(picture)
} else {
canvas.block()
canvas?.block()
}
}