mirror of
https://github.com/gedoor/legado.git
synced 2024-07-06 23:47:49 +08:00
优化
This commit is contained in:
parent
b79f59acca
commit
5ac5f8d60b
@ -163,7 +163,22 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
|
||||
.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
init {
|
||||
prefs(appCtx).registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
var accentColor = accentColor()
|
||||
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences?,
|
||||
key: String?
|
||||
) {
|
||||
when (key) {
|
||||
ThemeStorePrefKeys.KEY_ACCENT_COLOR -> accentColor = accentColor()
|
||||
}
|
||||
}
|
||||
|
||||
fun editTheme(context: Context): ThemeStore {
|
||||
return ThemeStore(context)
|
||||
|
@ -212,4 +212,9 @@ object ImageProvider {
|
||||
}.getOrDefault(errorBitmap)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
bitmapLruCache.evictAll()
|
||||
BitmapCache.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
return hasPrevPage
|
||||
}
|
||||
|
||||
fun moveToNextChapter(upContent: Boolean): Boolean {
|
||||
fun moveToNextChapter(upContent: Boolean, upContentInPlace: Boolean = true): Boolean {
|
||||
if (durChapterIndex < chapterSize - 1) {
|
||||
durChapterPos = 0
|
||||
durChapterIndex++
|
||||
@ -213,11 +213,11 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
nextTextChapter = null
|
||||
if (curTextChapter == null) {
|
||||
AppLog.putDebug("moveToNextChapter-章节未加载,开始加载")
|
||||
callBack?.upContent()
|
||||
if (upContentInPlace) callBack?.upContent(resetPageOffset = false)
|
||||
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
||||
} else if (upContent) {
|
||||
} else if (upContent && upContentInPlace) {
|
||||
AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图")
|
||||
callBack?.upContent()
|
||||
callBack?.upContent(resetPageOffset = false)
|
||||
}
|
||||
loadContent(durChapterIndex.plus(1), upContent, false)
|
||||
saveRead()
|
||||
@ -233,7 +233,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
|
||||
fun moveToPrevChapter(
|
||||
upContent: Boolean,
|
||||
toLast: Boolean = true
|
||||
toLast: Boolean = true,
|
||||
upContentInPlace: Boolean = true
|
||||
): Boolean {
|
||||
if (durChapterIndex > 0) {
|
||||
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
||||
@ -242,10 +243,10 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
curTextChapter = prevTextChapter
|
||||
prevTextChapter = null
|
||||
if (curTextChapter == null) {
|
||||
callBack?.upContent()
|
||||
if (upContentInPlace) callBack?.upContent(resetPageOffset = false)
|
||||
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
||||
} else if (upContent) {
|
||||
callBack?.upContent()
|
||||
} else if (upContent && upContentInPlace) {
|
||||
callBack?.upContent(resetPageOffset = false)
|
||||
}
|
||||
loadContent(durChapterIndex.minus(1), upContent, false)
|
||||
saveRead()
|
||||
@ -267,6 +268,16 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
}
|
||||
|
||||
fun setPageIndex(index: Int) {
|
||||
val textChapter = curTextChapter
|
||||
if (textChapter != null) {
|
||||
val pageIndex = durPageIndex
|
||||
if (index > pageIndex) {
|
||||
textChapter.getPage(index - 2)?.recyclePictures()
|
||||
}
|
||||
if (index < pageIndex) {
|
||||
textChapter.getPage(index + 2)?.recyclePictures()
|
||||
}
|
||||
}
|
||||
durChapterPos = curTextChapter?.getReadLength(index) ?: index
|
||||
saveRead(true)
|
||||
curPageChanged(true)
|
||||
@ -595,6 +606,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
coroutineContext.cancelChildren()
|
||||
downloadedChapters.clear()
|
||||
downloadFailChapters.clear()
|
||||
ImageProvider.clear()
|
||||
}
|
||||
|
||||
interface CallBack {
|
||||
|
@ -97,6 +97,7 @@ 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
|
||||
import io.legado.app.utils.getPrefString
|
||||
import io.legado.app.utils.hexString
|
||||
@ -218,9 +219,10 @@ 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 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) } }
|
||||
|
||||
//恢复跳转前进度对话框的交互结果
|
||||
private var confirmRestoreProcess: Boolean? = null
|
||||
@ -265,22 +267,14 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
viewModel.initData(intent) {
|
||||
initDataSuccess()
|
||||
upMenu()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
upMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1532,8 +1526,6 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
if (it) { // 更新内容排版布局
|
||||
if (isInitFinish) {
|
||||
ReadBook.loadContent(resetPageOffset = false)
|
||||
} else {
|
||||
reloadContent = true
|
||||
}
|
||||
} else {
|
||||
readView.upContent(resetPageOffset = false)
|
||||
@ -1582,6 +1574,9 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
observeEvent<Boolean>(EventBus.UP_SEEK_BAR) {
|
||||
binding.readMenu.upSeekBar()
|
||||
}
|
||||
observeEvent<String>(EventBus.RECREATE) {
|
||||
binding.readView.invalidateTextPage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun upScreenTimeOut() {
|
||||
@ -1594,17 +1589,16 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
* 重置黑屏时间
|
||||
*/
|
||||
override fun screenOffTimerStart() {
|
||||
keepScreenJon?.cancel()
|
||||
keepScreenJon = lifecycleScope.launch {
|
||||
handler.post {
|
||||
if (screenTimeOut < 0) {
|
||||
keepScreenOn(true)
|
||||
return@launch
|
||||
return@post
|
||||
}
|
||||
val t = screenTimeOut - sysScreenOffTime
|
||||
if (t > 0) {
|
||||
keepScreenOn(true)
|
||||
delay(screenTimeOut)
|
||||
keepScreenOn(false)
|
||||
handler.removeCallbacks(screenOffRunnable)
|
||||
handler.postDelayed(screenOffRunnable, screenTimeOut)
|
||||
} else {
|
||||
keepScreenOn(false)
|
||||
}
|
||||
|
@ -3,22 +3,16 @@ package io.legado.app.ui.book.read.page
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Picture
|
||||
import android.graphics.RectF
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.core.graphics.record
|
||||
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.book.isImage
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
import io.legado.app.lib.theme.accentColor
|
||||
import io.legado.app.model.ImageProvider
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||
@ -31,8 +25,8 @@ 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.dpToPx
|
||||
import io.legado.app.utils.getCompatColor
|
||||
import io.legado.app.utils.getPrefBoolean
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
@ -44,8 +38,7 @@ import kotlin.math.min
|
||||
*/
|
||||
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
||||
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true)
|
||||
var upView: ((TextPage) -> Unit)? = null
|
||||
private val selectedPaint by lazy {
|
||||
val selectedPaint by lazy {
|
||||
Paint().apply {
|
||||
color = context.getCompatColor(R.color.btn_bg_press_2)
|
||||
style = Paint.Style.FILL
|
||||
@ -65,13 +58,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
//滚动参数
|
||||
private val pageFactory: TextPageFactory get() = callBack.pageFactory
|
||||
private var pageOffset = 0
|
||||
private lateinit var picture: Picture
|
||||
private var pictureIsDirty = true
|
||||
private val atLeastApi23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
private val pictureMirror = PictureMirror()
|
||||
private val isNoAnim get() = ReadBook.pageAnim() == PageAnim.noAnim
|
||||
|
||||
//绘制图片的paint
|
||||
private val imagePaint by lazy {
|
||||
val imagePaint by lazy {
|
||||
Paint().apply {
|
||||
isAntiAlias = AppConfig.useAntiAlias
|
||||
}
|
||||
@ -79,9 +70,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
|
||||
init {
|
||||
callBack = activity as CallBack
|
||||
if (atLeastApi23) {
|
||||
picture = Picture()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +96,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
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, oldw, oldh)
|
||||
upVisibleRect()
|
||||
textPage.format()
|
||||
}
|
||||
@ -119,14 +107,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
canvas.translate(0f, scrollY.toFloat())
|
||||
}
|
||||
canvas.clipRect(visibleRect)
|
||||
if (atLeastApi23 && !callBack.isScroll && !isNoAnim) {
|
||||
if (pictureIsDirty) {
|
||||
pictureIsDirty = false
|
||||
picture.record(width, height) {
|
||||
drawPage(this)
|
||||
}
|
||||
if (!callBack.isScroll && !isNoAnim) {
|
||||
pictureMirror.draw(canvas, width, height) {
|
||||
drawPage(this)
|
||||
}
|
||||
canvas.drawPicture(picture)
|
||||
} else {
|
||||
drawPage(canvas)
|
||||
}
|
||||
@ -137,147 +121,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
*/
|
||||
private fun drawPage(canvas: Canvas) {
|
||||
var relativeOffset = relativeOffset(0)
|
||||
var lines = textPage.lines
|
||||
for (i in lines.indices) {
|
||||
drawLine(canvas, textPage, lines[i], relativeOffset)
|
||||
val view = this
|
||||
canvas.withTranslation(0f, relativeOffset) {
|
||||
textPage.draw(view, this)
|
||||
}
|
||||
if (!callBack.isScroll) return
|
||||
//滚动翻页
|
||||
if (!pageFactory.hasNext()) return
|
||||
val textPage1 = relativePage(1)
|
||||
relativeOffset = relativeOffset(1)
|
||||
lines = textPage1.lines
|
||||
for (i in lines.indices) {
|
||||
drawLine(canvas, textPage1, lines[i], relativeOffset)
|
||||
canvas.withTranslation(0f, relativeOffset) {
|
||||
textPage1.draw(view, this)
|
||||
}
|
||||
if (!pageFactory.hasNextPlus()) return
|
||||
relativeOffset = relativeOffset(2)
|
||||
if (relativeOffset < ChapterProvider.visibleHeight) {
|
||||
val textPage2 = relativePage(2)
|
||||
lines = textPage2.lines
|
||||
for (i in lines.indices) {
|
||||
drawLine(canvas, textPage2, lines[i], relativeOffset)
|
||||
canvas.withTranslation(0f, relativeOffset) {
|
||||
textPage2.draw(view, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制页面
|
||||
*/
|
||||
private fun drawLine(
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
relativeOffset: Float,
|
||||
) {
|
||||
val lineTop = textLine.lineTop + relativeOffset
|
||||
val lineBase = textLine.lineBase + relativeOffset
|
||||
val lineBottom = textLine.lineBottom + relativeOffset
|
||||
drawChars(canvas, textPage, textLine, lineTop, lineBase, lineBottom)
|
||||
if (ReadBookConfig.underline && ReadBook.book?.isImage != true) {
|
||||
drawUnderline(canvas, textLine, relativeOffset)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制下划线
|
||||
*/
|
||||
private fun drawUnderline(canvas: Canvas, textLine: TextLine, relativeOffset: Float) {
|
||||
val lineY = relativeOffset + textLine.lineBottom - 1.dpToPx()
|
||||
canvas.drawLine(
|
||||
textLine.lineStart + textLine.indentWidth,
|
||||
lineY,
|
||||
textLine.lineEnd,
|
||||
lineY,
|
||||
ChapterProvider.contentPaint
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字
|
||||
*/
|
||||
private fun drawChars(
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
lineTop: Float,
|
||||
lineBase: Float,
|
||||
lineBottom: Float,
|
||||
) {
|
||||
val textPaint = if (textLine.isTitle) {
|
||||
ChapterProvider.titlePaint
|
||||
} else {
|
||||
ChapterProvider.contentPaint
|
||||
}
|
||||
val textColor = if (textLine.isReadAloud) context.accentColor else ReadBookConfig.textColor
|
||||
val columns = textLine.columns
|
||||
for (i in columns.indices) {
|
||||
when (val column = columns[i]) {
|
||||
is TextColumn -> {
|
||||
if (column.isSearchResult) {
|
||||
textPaint.color = context.accentColor
|
||||
} else if (textPaint.color != textColor) {
|
||||
textPaint.color = textColor
|
||||
}
|
||||
canvas.drawText(column.charData, column.start, lineBase, textPaint)
|
||||
if (column.selected) {
|
||||
canvas.drawRect(
|
||||
column.start,
|
||||
lineTop,
|
||||
column.end,
|
||||
lineBottom,
|
||||
selectedPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ImageColumn -> drawImage(canvas, textPage, textLine, column, lineTop, lineBottom)
|
||||
is ReviewColumn -> column.drawToCanvas(canvas, lineBase, textPaint.textSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图片
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun drawImage(
|
||||
canvas: Canvas,
|
||||
textPage: TextPage,
|
||||
textLine: TextLine,
|
||||
column: ImageColumn,
|
||||
lineTop: Float,
|
||||
lineBottom: Float
|
||||
) {
|
||||
|
||||
val book = ReadBook.book ?: return
|
||||
|
||||
val bitmap = ImageProvider.getImage(
|
||||
book,
|
||||
column.src,
|
||||
(column.end - column.start).toInt(),
|
||||
(lineBottom - lineTop).toInt()
|
||||
) {
|
||||
invalidate()
|
||||
} ?: return
|
||||
|
||||
val rectF = if (textLine.isImage) {
|
||||
RectF(column.start, lineTop, column.end, lineBottom)
|
||||
} else {
|
||||
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
|
||||
val h = (column.end - column.start) / bitmap.width * bitmap.height
|
||||
val div = (lineBottom - lineTop - h) / 2
|
||||
RectF(column.start, lineTop + div, column.end, lineBottom - div)
|
||||
}
|
||||
kotlin.runCatching {
|
||||
canvas.drawBitmap(bitmap, null, rectF, imagePaint)
|
||||
}.onFailure { e ->
|
||||
context.toastOnUi(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动事件
|
||||
* pageOffset 向上滚动 减小 向下滚动 增大
|
||||
* pageOffset 范围 0 ~ -textPage.height 大于0为上一页,小于-textPage.height为下一页
|
||||
* 以内容显示区域顶端为界,pageOffset的绝对值为textPage上方的高度
|
||||
* pageOffset + textPage.height 为 textPage 下方的高度
|
||||
*/
|
||||
fun scroll(mOffset: Int) {
|
||||
if (mOffset == 0) return
|
||||
@ -294,28 +165,25 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
val offset = (ChapterProvider.visibleHeight - textPage.height).toInt()
|
||||
pageOffset = min(0, offset)
|
||||
} else if (pageOffset > 0) {
|
||||
pageFactory.moveToPrev(true)
|
||||
textPage = pageFactory.curPage
|
||||
pageOffset -= textPage.height.toInt()
|
||||
upView?.invoke(textPage)
|
||||
contentDescription = textPage.text
|
||||
if (pageFactory.moveToPrev(true)) {
|
||||
pageOffset -= textPage.height.toInt()
|
||||
} else {
|
||||
pageOffset = 0
|
||||
}
|
||||
} else if (pageOffset < -textPage.height) {
|
||||
pageOffset += textPage.height.toInt()
|
||||
pageFactory.moveToNext(true)
|
||||
textPage = pageFactory.curPage
|
||||
upView?.invoke(textPage)
|
||||
contentDescription = textPage.text
|
||||
val height = textPage.height
|
||||
if (pageFactory.moveToNext(upContent = true)) {
|
||||
pageOffset += height.toInt()
|
||||
} else {
|
||||
pageOffset = -height.toInt()
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
super.invalidate()
|
||||
invalidatePicture()
|
||||
}
|
||||
|
||||
private fun invalidatePicture() {
|
||||
pictureIsDirty = true
|
||||
pictureMirror.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -517,9 +385,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
if (relativeOffset >= ChapterProvider.visibleHeight) return
|
||||
}
|
||||
val textPage = relativePage(relativePos)
|
||||
for ((lineIndex, textLine) in textPage.lines.withIndex()) {
|
||||
for (lineIndex in textPage.lines.indices) {
|
||||
val textLine = textPage.getLine(lineIndex)
|
||||
if (textLine.isTouchY(y, relativeOffset)) {
|
||||
for ((charIndex, textColumn) in textLine.columns.withIndex()) {
|
||||
if (textPage.doublePage) {
|
||||
val halfWidth = width / 2
|
||||
if (textLine.isLeftLine && x > halfWidth) {
|
||||
continue
|
||||
}
|
||||
if (!textLine.isLeftLine && x < halfWidth) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
val columns = textLine.columns
|
||||
for (charIndex in columns.indices) {
|
||||
val textColumn = columns[charIndex]
|
||||
if (textColumn.isTouch(x)) {
|
||||
touched.invoke(
|
||||
relativeOffset,
|
||||
@ -529,12 +409,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
return
|
||||
}
|
||||
}
|
||||
val isLast = textLine.columns.first().start < x
|
||||
val (charIndex, textColumn) = if (isLast) {
|
||||
textLine.columns.withIndex().last()
|
||||
} else {
|
||||
textLine.columns.withIndex().first()
|
||||
}
|
||||
val isLast = columns.first().start < x
|
||||
val charIndex = if (isLast) columns.lastIndex else 0
|
||||
val textColumn = if (isLast) columns.last() else columns.first()
|
||||
touched.invoke(
|
||||
relativeOffset,
|
||||
TextPos(relativePos, lineIndex, charIndex, false, isLast),
|
||||
@ -557,7 +434,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
if (relativeOffset >= ChapterProvider.visibleHeight) break
|
||||
}
|
||||
val textPage = relativePage(relativePos)
|
||||
for (textLine in textPage.lines) {
|
||||
val lines = textPage.lines
|
||||
for (i in lines.indices) {
|
||||
val textLine = lines[i]
|
||||
if (textLine.isVisible(relativeOffset)) {
|
||||
val visibleLine = textLine.copy().apply {
|
||||
lineTop += relativeOffset
|
||||
@ -580,7 +459,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
if (relativeOffset >= ChapterProvider.visibleHeight) break
|
||||
}
|
||||
val textPage = relativePage(relativePos)
|
||||
for (textLine in textPage.lines) {
|
||||
val lines = textPage.lines
|
||||
for (i in lines.indices) {
|
||||
val textLine = lines[i]
|
||||
if (textLine.isVisible(relativeOffset)) {
|
||||
val visibleLine = textLine.copy().apply {
|
||||
lineTop += relativeOffset
|
||||
|
@ -57,9 +57,6 @@ class PageView(context: Context) : FrameLayout(context) {
|
||||
if (!isInEditMode) {
|
||||
upStyle()
|
||||
}
|
||||
binding.contentTextView.upView = {
|
||||
setProgress(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
|
@ -21,16 +21,25 @@ import io.legado.app.model.ReadAloud
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.book.read.ContentEditDialog
|
||||
import io.legado.app.ui.book.read.page.api.DataSource
|
||||
import io.legado.app.ui.book.read.page.delegate.*
|
||||
import io.legado.app.ui.book.read.page.delegate.CoverPageDelegate
|
||||
import io.legado.app.ui.book.read.page.delegate.NoAnimPageDelegate
|
||||
import io.legado.app.ui.book.read.page.delegate.PageDelegate
|
||||
import io.legado.app.ui.book.read.page.delegate.ScrollPageDelegate
|
||||
import io.legado.app.ui.book.read.page.delegate.SimulationPageDelegate
|
||||
import io.legado.app.ui.book.read.page.delegate.SlidePageDelegate
|
||||
import io.legado.app.ui.book.read.page.entities.PageDirection
|
||||
import io.legado.app.ui.book.read.page.entities.TextChapter
|
||||
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||
import io.legado.app.ui.book.read.page.entities.TextPos
|
||||
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.*
|
||||
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
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
@ -49,7 +58,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
field = value
|
||||
upContent()
|
||||
}
|
||||
var isScroll = false
|
||||
override var isScroll = false
|
||||
val prevPage by lazy { PageView(context) }
|
||||
val curPage by lazy { PageView(context) }
|
||||
val nextPage by lazy { PageView(context) }
|
||||
@ -529,7 +538,9 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
* @param resetPageOffset 滚动阅读是是否重置位置
|
||||
*/
|
||||
override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {
|
||||
curPage.setContentDescription(pageFactory.curPage.text)
|
||||
post {
|
||||
curPage.setContentDescription(pageFactory.curPage.text)
|
||||
}
|
||||
if (isScroll && !callBack.isAutoPage) {
|
||||
curPage.setContent(pageFactory.curPage, resetPageOffset)
|
||||
} else {
|
||||
@ -643,6 +654,15 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
||||
nextPageBitmap = null
|
||||
}
|
||||
|
||||
fun invalidateTextPage() {
|
||||
pageFactory.run {
|
||||
prevPage.invalidateAll()
|
||||
curPage.invalidateAll()
|
||||
nextPage.invalidateAll()
|
||||
nextPlusPage.invalidateAll()
|
||||
}
|
||||
}
|
||||
|
||||
override val currentChapter: TextChapter?
|
||||
get() {
|
||||
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
||||
|
@ -13,6 +13,8 @@ interface DataSource {
|
||||
|
||||
val prevChapter: TextChapter?
|
||||
|
||||
val isScroll: Boolean
|
||||
|
||||
fun hasNextChapter(): Boolean
|
||||
|
||||
fun hasPrevChapter(): Boolean
|
||||
|
@ -186,17 +186,19 @@ data class TextChapter(
|
||||
*/
|
||||
fun getPageIndexByCharIndex(charIndex: Int): Int {
|
||||
var length = 0
|
||||
pages.forEach {
|
||||
length += it.charSize
|
||||
for (i in pages.indices) {
|
||||
val page = pages[i]
|
||||
length += page.charSize
|
||||
if (length > charIndex) {
|
||||
return it.index
|
||||
return page.index
|
||||
}
|
||||
}
|
||||
return pages.lastIndex
|
||||
}
|
||||
|
||||
fun clearSearchResult() {
|
||||
pages.forEach { page ->
|
||||
for (i in pages.indices) {
|
||||
val page = pages[i]
|
||||
page.searchResult.forEach {
|
||||
it.selected = false
|
||||
it.isSearchResult = false
|
||||
|
@ -1,9 +1,17 @@
|
||||
package io.legado.app.ui.book.read.page.entities
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint.FontMetrics
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.help.book.isImage
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
import io.legado.app.model.ReadBook
|
||||
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.dpToPx
|
||||
|
||||
/**
|
||||
* 行信息
|
||||
@ -22,8 +30,7 @@ data class TextLine(
|
||||
var pagePosition: Int = 0,
|
||||
val isTitle: Boolean = false,
|
||||
var isParagraphEnd: Boolean = false,
|
||||
var isReadAloud: Boolean = false,
|
||||
var isImage: Boolean = false
|
||||
var isImage: Boolean = false,
|
||||
) {
|
||||
|
||||
val columns: List<BaseColumn> get() = textColumns
|
||||
@ -31,8 +38,20 @@ data class TextLine(
|
||||
val lineStart: Float get() = textColumns.firstOrNull()?.start ?: 0f
|
||||
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()
|
||||
var isReadAloud: Boolean = false
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
invalidate()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
var textPage: TextPage = emptyTextPage
|
||||
var isLeftLine = true
|
||||
|
||||
fun addColumn(column: BaseColumn) {
|
||||
column.textLine = this
|
||||
textColumns.add(column)
|
||||
}
|
||||
|
||||
@ -102,4 +121,50 @@ data class TextLine(
|
||||
return visible
|
||||
}
|
||||
|
||||
fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
pictureMirror.draw(canvas, view.width, height.toInt()) {
|
||||
drawTextLine(view, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawTextLine(view: ContentTextView, canvas: Canvas) {
|
||||
for (i in columns.indices) {
|
||||
columns[i].draw(view, canvas)
|
||||
}
|
||||
if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) {
|
||||
drawUnderline(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制下划线
|
||||
*/
|
||||
private fun drawUnderline(canvas: Canvas) {
|
||||
val lineY = height - 1.dpToPx()
|
||||
canvas.drawLine(
|
||||
lineStart + indentWidth,
|
||||
lineY,
|
||||
lineEnd,
|
||||
lineY,
|
||||
ChapterProvider.contentPaint
|
||||
)
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
invalidateSelf()
|
||||
textPage.invalidate()
|
||||
}
|
||||
|
||||
fun invalidateSelf() {
|
||||
pictureMirror.invalidate()
|
||||
}
|
||||
|
||||
fun recyclePicture() {
|
||||
pictureMirror.recycle()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val emptyTextLine = TextLine()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
package io.legado.app.ui.book.read.page.entities
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.graphics.withTranslation
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
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 splitties.init.appCtx
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.min
|
||||
@ -31,6 +35,7 @@ data class TextPage(
|
||||
|
||||
companion object {
|
||||
val readProgressFormatter = DecimalFormat("0.0%")
|
||||
val emptyTextPage = TextPage()
|
||||
}
|
||||
|
||||
val lines: List<TextLine> get() = textLines
|
||||
@ -38,25 +43,29 @@ data class TextPage(
|
||||
val charSize: Int get() = text.length.coerceAtLeast(1)
|
||||
val searchResult = hashSetOf<TextColumn>()
|
||||
var isMsgPage: Boolean = false
|
||||
var pictureMirror: PictureMirror = PictureMirror()
|
||||
var doublePage = false
|
||||
|
||||
val paragraphs by lazy {
|
||||
paragraphsInternal
|
||||
}
|
||||
|
||||
val paragraphsInternal: ArrayList<TextParagraph> get() {
|
||||
val paragraphs = arrayListOf<TextParagraph>()
|
||||
val lines = textLines.filter { it.paragraphNum > 0 }
|
||||
val offset = lines.first().paragraphNum - 1
|
||||
lines.forEach { line ->
|
||||
if (paragraphs.lastIndex < line.paragraphNum - offset - 1) {
|
||||
paragraphs.add(TextParagraph(0))
|
||||
val paragraphsInternal: ArrayList<TextParagraph>
|
||||
get() {
|
||||
val paragraphs = arrayListOf<TextParagraph>()
|
||||
val lines = textLines.filter { it.paragraphNum > 0 }
|
||||
val offset = lines.first().paragraphNum - 1
|
||||
lines.forEach { line ->
|
||||
if (paragraphs.lastIndex < line.paragraphNum - offset - 1) {
|
||||
paragraphs.add(TextParagraph(0))
|
||||
}
|
||||
paragraphs[line.paragraphNum - offset - 1].textLines.add(line)
|
||||
}
|
||||
paragraphs[line.paragraphNum - offset - 1].textLines.add(line)
|
||||
return paragraphs
|
||||
}
|
||||
return paragraphs
|
||||
}
|
||||
|
||||
fun addLine(line: TextLine) {
|
||||
line.textPage = this
|
||||
textLines.add(line)
|
||||
}
|
||||
|
||||
@ -147,7 +156,7 @@ data class TextPage(
|
||||
)
|
||||
x = x1
|
||||
}
|
||||
textLines.add(textLine)
|
||||
addLine(textLine)
|
||||
}
|
||||
height = ChapterProvider.visibleHeight.toFloat()
|
||||
}
|
||||
@ -158,8 +167,8 @@ data class TextPage(
|
||||
* 移除朗读标志
|
||||
*/
|
||||
fun removePageAloudSpan(): TextPage {
|
||||
textLines.forEach { textLine ->
|
||||
textLine.isReadAloud = false
|
||||
for (i in textLines.indices) {
|
||||
textLines[i].isReadAloud = false
|
||||
}
|
||||
return this
|
||||
}
|
||||
@ -254,6 +263,39 @@ data class TextPage(
|
||||
return null
|
||||
}
|
||||
|
||||
fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
pictureMirror.draw(canvas, view.width, height.toInt()) {
|
||||
drawPage(view, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawPage(view: ContentTextView, canvas: Canvas) {
|
||||
for (i in lines.indices) {
|
||||
val line = lines[i]
|
||||
canvas.withTranslation(0f, line.lineTop) {
|
||||
line.draw(view, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
pictureMirror.invalidate()
|
||||
}
|
||||
|
||||
fun invalidateAll() {
|
||||
for (i in lines.indices) {
|
||||
lines[i].invalidateSelf()
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun recyclePictures() {
|
||||
pictureMirror.recycle()
|
||||
for (i in lines.indices) {
|
||||
lines[i].recyclePicture()
|
||||
}
|
||||
}
|
||||
|
||||
fun hasImageOrEmpty(): Boolean {
|
||||
return textLines.any { it.isImage } || textLines.isEmpty()
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
package io.legado.app.ui.book.read.page.entities.column
|
||||
|
||||
import android.graphics.Canvas
|
||||
import io.legado.app.ui.book.read.page.ContentTextView
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||
|
||||
/**
|
||||
* 列基类
|
||||
*/
|
||||
interface BaseColumn {
|
||||
var start: Float
|
||||
var end: Float
|
||||
var textLine: TextLine
|
||||
|
||||
fun draw(view: ContentTextView, canvas: Canvas)
|
||||
|
||||
fun isTouch(x: Float): Boolean {
|
||||
return x > start && x < end
|
||||
|
@ -1,6 +1,10 @@
|
||||
package io.legado.app.ui.book.read.page.entities.column
|
||||
|
||||
import android.graphics.Canvas
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.ui.book.read.page.ContentTextView
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine
|
||||
|
||||
|
||||
/**
|
||||
@ -9,5 +13,10 @@ import androidx.annotation.Keep
|
||||
@Keep
|
||||
data class ButtonColumn(
|
||||
override var start: Float,
|
||||
override var end: Float
|
||||
) : BaseColumn
|
||||
override var end: Float,
|
||||
) : BaseColumn {
|
||||
override var textLine: TextLine = emptyTextLine
|
||||
override fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
|
||||
}
|
||||
}
|
@ -1,6 +1,15 @@
|
||||
package io.legado.app.ui.book.read.page.entities.column
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.RectF
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.model.ImageProvider
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.book.read.page.ContentTextView
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import splitties.init.appCtx
|
||||
|
||||
/**
|
||||
* 图片列
|
||||
@ -10,4 +19,37 @@ data class ImageColumn(
|
||||
override var start: Float,
|
||||
override var end: Float,
|
||||
var src: String
|
||||
) : BaseColumn
|
||||
) : BaseColumn {
|
||||
|
||||
override var textLine: TextLine = emptyTextLine
|
||||
override fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
val book = ReadBook.book ?: return
|
||||
|
||||
val height = textLine.height
|
||||
|
||||
val bitmap = ImageProvider.getImage(
|
||||
book,
|
||||
src,
|
||||
(end - start).toInt(),
|
||||
height.toInt()
|
||||
) {
|
||||
textLine.invalidate()
|
||||
view.invalidate()
|
||||
} ?: return
|
||||
|
||||
val rectF = if (textLine.isImage) {
|
||||
RectF(start, 0f, end, height)
|
||||
} else {
|
||||
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
|
||||
val h = (end - start) / bitmap.width * bitmap.height
|
||||
val div = (height - h) / 2
|
||||
RectF(start, div, end, height - div)
|
||||
}
|
||||
kotlin.runCatching {
|
||||
canvas.drawBitmap(bitmap, null, rectF, view.imagePaint)
|
||||
}.onFailure { e ->
|
||||
appCtx.toastOnUi(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.ui.book.read.page.ContentTextView
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine
|
||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||
|
||||
/**
|
||||
@ -16,6 +19,16 @@ data class ReviewColumn(
|
||||
val count: Int = 0
|
||||
) : BaseColumn {
|
||||
|
||||
override var textLine: TextLine = emptyTextLine
|
||||
override fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
val textPaint = if (textLine.isTitle) {
|
||||
ChapterProvider.titlePaint
|
||||
} else {
|
||||
ChapterProvider.contentPaint
|
||||
}
|
||||
drawToCanvas(canvas, textLine.lineBase, textPaint.textSize)
|
||||
}
|
||||
|
||||
val countText by lazy {
|
||||
if (count > 999) {
|
||||
return@lazy "999"
|
||||
|
@ -1,6 +1,13 @@
|
||||
package io.legado.app.ui.book.read.page.entities.column
|
||||
|
||||
import android.graphics.Canvas
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.ui.book.read.page.ContentTextView
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
||||
import io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine
|
||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||
|
||||
/**
|
||||
* 文字列
|
||||
@ -10,6 +17,43 @@ data class TextColumn(
|
||||
override var start: Float,
|
||||
override var end: Float,
|
||||
val charData: String,
|
||||
var selected: Boolean = false,
|
||||
) : BaseColumn {
|
||||
|
||||
override var textLine: TextLine = emptyTextLine
|
||||
|
||||
var selected: Boolean = false
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
textLine.invalidate()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
var isSearchResult: Boolean = false
|
||||
) : BaseColumn
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
textLine.invalidate()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
override fun draw(view: ContentTextView, canvas: Canvas) {
|
||||
val textPaint = if (textLine.isTitle) {
|
||||
ChapterProvider.titlePaint
|
||||
} else {
|
||||
ChapterProvider.contentPaint
|
||||
}
|
||||
val textColor = if (textLine.isReadAloud || isSearchResult) {
|
||||
ThemeStore.accentColor
|
||||
} else {
|
||||
ReadBookConfig.textColor
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package io.legado.app.ui.book.read.page.provider
|
||||
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Paint.FontMetrics
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
@ -25,13 +24,13 @@ import io.legado.app.ui.book.read.page.entities.column.ReviewColumn
|
||||
import io.legado.app.ui.book.read.page.entities.column.TextColumn
|
||||
import io.legado.app.utils.RealPathUtil
|
||||
import io.legado.app.utils.dpToPx
|
||||
import io.legado.app.utils.fastSum
|
||||
import io.legado.app.utils.isContentScheme
|
||||
import io.legado.app.utils.isPad
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.spToPx
|
||||
import io.legado.app.utils.splitNotBlank
|
||||
import io.legado.app.utils.textHeight
|
||||
import io.legado.app.utils.toStringArray
|
||||
import splitties.init.appCtx
|
||||
import java.util.LinkedList
|
||||
import java.util.Locale
|
||||
@ -47,6 +46,8 @@ object ChapterProvider {
|
||||
//用于评论按钮的替换
|
||||
private const val reviewChar = "▨"
|
||||
|
||||
private const val indentChar = " "
|
||||
|
||||
@JvmStatic
|
||||
var viewWidth = 0
|
||||
private set
|
||||
@ -104,7 +105,7 @@ object ChapterProvider {
|
||||
private var indentCharWidth = 0f
|
||||
|
||||
@JvmStatic
|
||||
private var titlePaintTextHeight = 0f
|
||||
var titlePaintTextHeight = 0f
|
||||
|
||||
@JvmStatic
|
||||
var contentPaintTextHeight = 0f
|
||||
@ -132,6 +133,9 @@ object ChapterProvider {
|
||||
var doublePage = false
|
||||
private set
|
||||
|
||||
private val titleMeasureHelper = TextMeasure(titlePaint)
|
||||
private val contentMeasureHelper = TextMeasure(contentPaint)
|
||||
|
||||
init {
|
||||
upStyle()
|
||||
}
|
||||
@ -161,6 +165,7 @@ object ChapterProvider {
|
||||
textPages,
|
||||
stringBuilder,
|
||||
titlePaint,
|
||||
titleMeasureHelper,
|
||||
titlePaintTextHeight,
|
||||
titlePaintFontMetrics,
|
||||
isTitle = true,
|
||||
@ -196,6 +201,7 @@ object ChapterProvider {
|
||||
textPages,
|
||||
stringBuilder,
|
||||
contentPaint,
|
||||
contentMeasureHelper,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics,
|
||||
srcList = srcList
|
||||
@ -217,6 +223,7 @@ object ChapterProvider {
|
||||
textPages,
|
||||
stringBuilder,
|
||||
contentPaint,
|
||||
contentMeasureHelper,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics
|
||||
).let {
|
||||
@ -224,10 +231,19 @@ object ChapterProvider {
|
||||
durY = it.second
|
||||
}
|
||||
}
|
||||
durY = setTypeImage(
|
||||
book, matcher.group(1)!!,
|
||||
absStartX, durY, textPages, stringBuilder, book.getImageStyle()
|
||||
)
|
||||
setTypeImage(
|
||||
book,
|
||||
matcher.group(1)!!,
|
||||
absStartX,
|
||||
durY,
|
||||
textPages,
|
||||
contentPaintTextHeight,
|
||||
stringBuilder,
|
||||
book.getImageStyle()
|
||||
).let {
|
||||
absStartX = it.first
|
||||
durY = it.second
|
||||
}
|
||||
start = matcher.end()
|
||||
}
|
||||
if (start < content.length) {
|
||||
@ -239,6 +255,7 @@ object ChapterProvider {
|
||||
textPages,
|
||||
stringBuilder,
|
||||
contentPaint,
|
||||
contentMeasureHelper,
|
||||
contentPaintTextHeight,
|
||||
contentPaintFontMetrics
|
||||
).let {
|
||||
@ -249,14 +266,22 @@ object ChapterProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
textPages.last().height = durY + 20.dpToPx()
|
||||
textPages.last().text = stringBuilder.toString()
|
||||
val textPage = textPages.last()
|
||||
val endPadding = 20.dpToPx()
|
||||
val durYPadding = durY + endPadding
|
||||
if (textPage.height < durYPadding) {
|
||||
textPage.height = durYPadding
|
||||
} else {
|
||||
textPage.height += endPadding
|
||||
}
|
||||
textPage.text = stringBuilder.toString()
|
||||
textPages.forEachIndexed { index, item ->
|
||||
item.index = index
|
||||
item.pageSize = textPages.size
|
||||
item.chapterIndex = bookChapter.index
|
||||
item.chapterSize = chapterSize
|
||||
item.title = displayTitle
|
||||
item.doublePage = doublePage
|
||||
item.upLinesPosition()
|
||||
}
|
||||
|
||||
@ -280,15 +305,19 @@ object ChapterProvider {
|
||||
x: Int,
|
||||
y: Float,
|
||||
textPages: ArrayList<TextPage>,
|
||||
textHeight: Float,
|
||||
stringBuilder: StringBuilder,
|
||||
imageStyle: String?,
|
||||
): Float {
|
||||
): Pair<Int, Float> {
|
||||
var absStartX = x
|
||||
var durY = y
|
||||
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
if (durY > visibleHeight) {
|
||||
val textPage = textPages.last()
|
||||
textPage.height = durY
|
||||
if (textPage.height < durY) {
|
||||
textPage.height = durY
|
||||
}
|
||||
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||
stringBuilder.clear()
|
||||
textPages.add(TextPage())
|
||||
@ -313,10 +342,23 @@ object ChapterProvider {
|
||||
}
|
||||
if (durY + height > visibleHeight) {
|
||||
val textPage = textPages.last()
|
||||
textPage.height = durY
|
||||
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||
stringBuilder.clear()
|
||||
textPages.add(TextPage())
|
||||
if (doublePage && absStartX < viewWidth / 2) {
|
||||
//当前页面左列结束
|
||||
textPage.leftLineSize = textPage.lineSize
|
||||
absStartX = viewWidth / 2 + paddingLeft
|
||||
} else {
|
||||
//当前页面结束
|
||||
if (textPage.leftLineSize == 0) {
|
||||
textPage.leftLineSize = textPage.lineSize
|
||||
}
|
||||
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||
stringBuilder.clear()
|
||||
textPages.add(TextPage())
|
||||
}
|
||||
// 双页的 durY 不正确,可能会小于实际高度
|
||||
if (textPage.height < durY) {
|
||||
textPage.height = durY
|
||||
}
|
||||
durY = 0f
|
||||
}
|
||||
}
|
||||
@ -336,7 +378,7 @@ object ChapterProvider {
|
||||
)
|
||||
textPages.last().addLine(textLine)
|
||||
}
|
||||
return durY + paragraphSpacing / 10f
|
||||
return absStartX to durY + textHeight * paragraphSpacing / 10f
|
||||
}
|
||||
|
||||
/**
|
||||
@ -350,6 +392,7 @@ object ChapterProvider {
|
||||
textPages: ArrayList<TextPage>,
|
||||
stringBuilder: StringBuilder,
|
||||
textPaint: TextPaint,
|
||||
measureHelper: TextMeasure,
|
||||
textHeight: Float,
|
||||
fontMetrics: FontMetrics,
|
||||
isTitle: Boolean = false,
|
||||
@ -358,14 +401,11 @@ object ChapterProvider {
|
||||
srcList: LinkedList<String>? = null
|
||||
): Pair<Int, Float> {
|
||||
var absStartX = x
|
||||
val widthsArray = FloatArray(text.length)
|
||||
val layout = if (ReadBookConfig.useZhLayout) {
|
||||
ZhLayout(text, textPaint, visibleWidth, widthsArray)
|
||||
ZhLayout(text, textPaint, visibleWidth, measureHelper)
|
||||
} else {
|
||||
textPaint.getTextWidths(text, widthsArray)
|
||||
StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)
|
||||
}
|
||||
val widthsList = widthsArray.asList()
|
||||
var durY = when {
|
||||
//标题y轴居中
|
||||
emptyContent && textPages.size == 1 -> {
|
||||
@ -398,6 +438,7 @@ object ChapterProvider {
|
||||
if (durY + textHeight > visibleHeight) {
|
||||
val textPage = textPages.last()
|
||||
if (doublePage && absStartX < viewWidth / 2) {
|
||||
//当前页面左列结束
|
||||
textPage.leftLineSize = textPage.lineSize
|
||||
absStartX = viewWidth / 2 + paddingLeft
|
||||
} else {
|
||||
@ -406,32 +447,34 @@ object ChapterProvider {
|
||||
textPage.leftLineSize = textPage.lineSize
|
||||
}
|
||||
textPage.text = stringBuilder.toString()
|
||||
textPage.height = durY
|
||||
//新建页面
|
||||
textPages.add(TextPage())
|
||||
stringBuilder.clear()
|
||||
absStartX = paddingLeft
|
||||
}
|
||||
if (textPage.height < durY) {
|
||||
textPage.height = durY
|
||||
}
|
||||
durY = 0f
|
||||
}
|
||||
val lineStart = layout.getLineStart(lineIndex)
|
||||
val lineEnd = layout.getLineEnd(lineIndex)
|
||||
val words = text.substring(lineStart, lineEnd)
|
||||
val textWidths = widthsList.subList(lineStart, lineEnd)
|
||||
val desiredWidth = textWidths.sum()
|
||||
val lineText = text.substring(lineStart, lineEnd)
|
||||
val (words, widths) = measureHelper.measureTextSplit(lineText)
|
||||
val desiredWidth = widths.fastSum()
|
||||
when {
|
||||
lineIndex == 0 && layout.lineCount > 1 && !isTitle -> {
|
||||
//第一行 非标题
|
||||
textLine.text = words
|
||||
textLine.text = lineText
|
||||
addCharsToLineFirst(
|
||||
book, absStartX, textLine, words,
|
||||
textPaint, desiredWidth, textWidths, srcList
|
||||
desiredWidth, widths, srcList
|
||||
)
|
||||
}
|
||||
|
||||
lineIndex == layout.lineCount - 1 -> {
|
||||
//最后一行
|
||||
textLine.text = words
|
||||
textLine.text = lineText
|
||||
textLine.isParagraphEnd = true
|
||||
//标题x轴居中
|
||||
val startX = if (
|
||||
@ -443,8 +486,8 @@ object ChapterProvider {
|
||||
0f
|
||||
}
|
||||
addCharsToLineNatural(
|
||||
book, absStartX, textLine, words, textPaint,
|
||||
startX, !isTitle && lineIndex == 0, textWidths, srcList
|
||||
book, absStartX, textLine, words,
|
||||
startX, !isTitle && lineIndex == 0, widths, srcList
|
||||
)
|
||||
}
|
||||
|
||||
@ -457,20 +500,23 @@ object ChapterProvider {
|
||||
val startX = (visibleWidth - desiredWidth) / 2
|
||||
addCharsToLineNatural(
|
||||
book, absStartX, textLine, words,
|
||||
textPaint, startX, false, textWidths, srcList
|
||||
startX, false, widths, srcList
|
||||
)
|
||||
} else {
|
||||
//中间行
|
||||
textLine.text = words
|
||||
textLine.text = lineText
|
||||
addCharsToLineMiddle(
|
||||
book, absStartX, textLine, words,
|
||||
textPaint, desiredWidth, 0f, textWidths, srcList
|
||||
desiredWidth, 0f, widths, srcList
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doublePage) {
|
||||
textLine.isLeftLine = absStartX < viewWidth / 2
|
||||
}
|
||||
val sbLength = stringBuilder.length
|
||||
stringBuilder.append(words)
|
||||
stringBuilder.append(lineText)
|
||||
if (textLine.isParagraphEnd) {
|
||||
stringBuilder.append("\n")
|
||||
}
|
||||
@ -487,10 +533,13 @@ object ChapterProvider {
|
||||
chapterPosition + charSize + if (isParagraphEnd) 1 else 0
|
||||
} ?: 0) + sbLength
|
||||
textLine.pagePosition = sbLength
|
||||
textPages.last().addLine(textLine)
|
||||
textLine.upTopBottom(durY, textHeight, fontMetrics)
|
||||
val textPage = textPages.last()
|
||||
textPage.addLine(textLine)
|
||||
durY += textHeight * lineSpacingExtra
|
||||
textPages.last().height = durY
|
||||
if (textPage.height < durY) {
|
||||
textPage.height = durY
|
||||
}
|
||||
}
|
||||
durY += textHeight * paragraphSpacing / 10f
|
||||
return Pair(absStartX, durY)
|
||||
@ -503,8 +552,7 @@ object ChapterProvider {
|
||||
book: Book,
|
||||
absStartX: Int,
|
||||
textLine: TextLine,
|
||||
text: String,
|
||||
textPaint: TextPaint,
|
||||
words: List<String>,
|
||||
/**自然排版长度**/
|
||||
desiredWidth: Float,
|
||||
textWidths: List<Float>,
|
||||
@ -513,17 +561,17 @@ object ChapterProvider {
|
||||
var x = 0f
|
||||
if (!ReadBookConfig.textFullJustify) {
|
||||
addCharsToLineNatural(
|
||||
book, absStartX, textLine, text, textPaint,
|
||||
book, absStartX, textLine, words,
|
||||
x, true, textWidths, srcList
|
||||
)
|
||||
return
|
||||
}
|
||||
val bodyIndent = ReadBookConfig.paragraphIndent
|
||||
for (char in bodyIndent.toStringArray()) {
|
||||
for (i in bodyIndent.indices) {
|
||||
val x1 = x + indentCharWidth
|
||||
textLine.addColumn(
|
||||
TextColumn(
|
||||
charData = char,
|
||||
charData = indentChar,
|
||||
start = absStartX + x,
|
||||
end = absStartX + x1
|
||||
)
|
||||
@ -531,12 +579,12 @@ object ChapterProvider {
|
||||
x = x1
|
||||
textLine.indentWidth = x
|
||||
}
|
||||
if (text.length > bodyIndent.length) {
|
||||
val text1 = text.substring(bodyIndent.length, text.length)
|
||||
if (words.size > bodyIndent.length) {
|
||||
val text1 = words.subList(bodyIndent.length, words.size)
|
||||
val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size)
|
||||
addCharsToLineMiddle(
|
||||
book, absStartX, textLine, text1,
|
||||
textPaint, desiredWidth, x, textWidths1, srcList
|
||||
desiredWidth, x, textWidths1, srcList
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -548,8 +596,7 @@ object ChapterProvider {
|
||||
book: Book,
|
||||
absStartX: Int,
|
||||
textLine: TextLine,
|
||||
text: String,
|
||||
textPaint: TextPaint,
|
||||
words: List<String>,
|
||||
/**自然排版长度**/
|
||||
desiredWidth: Float,
|
||||
/**起始x坐标**/
|
||||
@ -559,19 +606,19 @@ object ChapterProvider {
|
||||
) {
|
||||
if (!ReadBookConfig.textFullJustify) {
|
||||
addCharsToLineNatural(
|
||||
book, absStartX, textLine, text, textPaint,
|
||||
book, absStartX, textLine, words,
|
||||
startX, false, textWidths, srcList
|
||||
)
|
||||
return
|
||||
}
|
||||
val residualWidth = visibleWidth - desiredWidth
|
||||
val spaceSize = text.count { it == ' ' }
|
||||
val (words, widths) = getStringArrayAndTextWidths(text, textWidths, textPaint)
|
||||
val spaceSize = words.count { it == " " }
|
||||
if (spaceSize > 1) {
|
||||
val d = residualWidth / spaceSize
|
||||
var x = startX
|
||||
words.forEachIndexed { index, char ->
|
||||
val cw = widths[index]
|
||||
for (index in words.indices) {
|
||||
val char = words[index]
|
||||
val cw = textWidths[index]
|
||||
val x1 = if (char == " ") {
|
||||
if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
||||
} else {
|
||||
@ -587,8 +634,9 @@ object ChapterProvider {
|
||||
val gapCount: Int = words.lastIndex
|
||||
val d = residualWidth / gapCount
|
||||
var x = startX
|
||||
words.forEachIndexed { index, char ->
|
||||
val cw = widths[index]
|
||||
for (index in words.indices) {
|
||||
val char = words[index]
|
||||
val cw = textWidths[index]
|
||||
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
||||
addCharToLine(
|
||||
book, absStartX, textLine, char,
|
||||
@ -607,8 +655,7 @@ object ChapterProvider {
|
||||
book: Book,
|
||||
absStartX: Int,
|
||||
textLine: TextLine,
|
||||
text: String,
|
||||
textPaint: TextPaint,
|
||||
words: List<String>,
|
||||
startX: Float,
|
||||
hasIndent: Boolean,
|
||||
textWidths: List<Float>,
|
||||
@ -616,9 +663,9 @@ object ChapterProvider {
|
||||
) {
|
||||
val indentLength = ReadBookConfig.paragraphIndent.length
|
||||
var x = startX
|
||||
val (words, widths) = getStringArrayAndTextWidths(text, textWidths, textPaint)
|
||||
words.forEachIndexed { index, char ->
|
||||
val cw = widths[index]
|
||||
for (index in words.indices) {
|
||||
val char = words[index]
|
||||
val cw = textWidths[index]
|
||||
val x1 = x + cw
|
||||
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
|
||||
x = x1
|
||||
@ -731,9 +778,11 @@ object ChapterProvider {
|
||||
getPaints(typeface).let {
|
||||
titlePaint = it.first
|
||||
contentPaint = it.second
|
||||
reviewPaint.color = contentPaint.color
|
||||
reviewPaint.textSize = contentPaint.textSize * 0.45f
|
||||
reviewPaint.textAlign = Paint.Align.CENTER
|
||||
titleMeasureHelper.setPaint(titlePaint)
|
||||
contentMeasureHelper.setPaint(contentPaint)
|
||||
// reviewPaint.color = contentPaint.color
|
||||
// reviewPaint.textSize = contentPaint.textSize * 0.45f
|
||||
// reviewPaint.textAlign = Paint.Align.CENTER
|
||||
}
|
||||
//间距
|
||||
lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f
|
||||
@ -820,12 +869,14 @@ object ChapterProvider {
|
||||
/**
|
||||
* 更新View尺寸
|
||||
*/
|
||||
fun upViewSize(width: Int, height: Int) {
|
||||
fun upViewSize(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
|
||||
if (width > 0 && height > 0 && (width != viewWidth || height != viewHeight)) {
|
||||
viewWidth = width
|
||||
viewHeight = height
|
||||
upLayout()
|
||||
postEvent(EventBus.UP_CONFIG, true)
|
||||
if (oldWidth > 0 && oldHeight > 0) {
|
||||
postEvent(EventBus.UP_CONFIG, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,146 @@
|
||||
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>()
|
||||
|
||||
private fun measureCodePoint(codePoint: Int): Float {
|
||||
if (codePoint < 128) {
|
||||
return asciiWidths[codePoint]
|
||||
}
|
||||
if (chineseCommonWidthBitSet[codePoint]) {
|
||||
return chineseCommonWidth
|
||||
}
|
||||
return codePointWidths.getOrDefault(codePoint, -1f)
|
||||
}
|
||||
|
||||
private fun measureCodePoints(codePoints: List<Int>) {
|
||||
val charArray = String(codePoints.toIntArray(), 0, codePoints.size).toCharArray()
|
||||
val widths = FloatArray(charArray.size)
|
||||
paint.getTextWidths(charArray, 0, charArray.size, widths)
|
||||
val widthsList = ArrayList<Float>(charArray.size)
|
||||
val buf = IntArray(1)
|
||||
for (i in charArray.indices) {
|
||||
if (charArray[i].isLowSurrogate()) continue
|
||||
val width = ceil(widths[i])
|
||||
widthsList.add(width)
|
||||
if (width == 0f && widthsList.size > 0) {
|
||||
val lastIndex = widthsList.lastIndex
|
||||
buf[0] = codePoints[lastIndex - 1]
|
||||
widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1))
|
||||
buf[0] = codePoints[lastIndex]
|
||||
widthsList[lastIndex] = paint.measureText(String(buf, 0, 1))
|
||||
}
|
||||
}
|
||||
for (i in codePoints.indices) {
|
||||
val codePoint = codePoints[i]
|
||||
val width = widthsList[i]
|
||||
if (codePoint < 128) {
|
||||
asciiWidths[codePoint] = width
|
||||
} else if (width == chineseCommonWidth) {
|
||||
chineseCommonWidthBitSet.set(codePoint)
|
||||
} else {
|
||||
codePointWidths[codePoint] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun measureTextSplit(text: String): Pair<ArrayList<String>, ArrayList<Float>> {
|
||||
var needMeasureCodePoints: HashSet<Int>? = null
|
||||
val codePoints = text.toCodePoints()
|
||||
val size = codePoints.size
|
||||
val widths = ArrayList<Float>(size)
|
||||
val stringList = ArrayList<String>(size)
|
||||
val buf = IntArray(1)
|
||||
for (i in codePoints.indices) {
|
||||
val codePoint = codePoints[i]
|
||||
val width = measureCodePoint(codePoint)
|
||||
widths.add(width)
|
||||
if (width == -1f) {
|
||||
if (needMeasureCodePoints == null) {
|
||||
needMeasureCodePoints = hashSetOf()
|
||||
}
|
||||
needMeasureCodePoints.add(codePoint)
|
||||
}
|
||||
buf[0] = codePoint
|
||||
stringList.add(String(buf, 0, 1))
|
||||
}
|
||||
if (!needMeasureCodePoints.isNullOrEmpty()) {
|
||||
measureCodePoints(needMeasureCodePoints.toList())
|
||||
for (i in codePoints.indices) {
|
||||
if (widths[i] == -1f) {
|
||||
widths[i] = measureCodePoint(codePoints[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return stringList to widths
|
||||
}
|
||||
|
||||
fun measureText(text: String): Float {
|
||||
var textWidth = 0f
|
||||
var needMeasureCodePoints: ArrayList<Int>? = null
|
||||
val codePoints = text.toCodePoints()
|
||||
for (i in codePoints.indices) {
|
||||
val codePoint = codePoints[i]
|
||||
val width = measureCodePoint(codePoint)
|
||||
if (width == -1f) {
|
||||
if (needMeasureCodePoints == null) {
|
||||
needMeasureCodePoints = ArrayList()
|
||||
}
|
||||
needMeasureCodePoints.add(codePoint)
|
||||
continue
|
||||
}
|
||||
textWidth += width
|
||||
}
|
||||
if (!needMeasureCodePoints.isNullOrEmpty()) {
|
||||
measureCodePoints(needMeasureCodePoints.toHashSet().toList())
|
||||
for (i in needMeasureCodePoints.indices) {
|
||||
textWidth += measureCodePoint(needMeasureCodePoints[i])
|
||||
}
|
||||
}
|
||||
return textWidth
|
||||
}
|
||||
|
||||
private fun String.toCodePoints(): List<Int> {
|
||||
val codePoints = ArrayList<Int>(length)
|
||||
val charArray = toCharArray()
|
||||
val size = length
|
||||
var i = 0
|
||||
while (i < size) {
|
||||
val c1 = charArray[i++]
|
||||
var cp = c1.code
|
||||
if (c1.isHighSurrogate() && i < size) {
|
||||
val c2 = charArray[i]
|
||||
if (c2.isLowSurrogate()) {
|
||||
i++
|
||||
cp = Character.toCodePoint(c1, c2)
|
||||
}
|
||||
}
|
||||
codePoints.add(cp)
|
||||
}
|
||||
return codePoints
|
||||
}
|
||||
|
||||
fun setPaint(paint: TextPaint) {
|
||||
this.paint = paint
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun invalidate() {
|
||||
chineseCommonWidth = paint.measureText("一")
|
||||
chineseCommonWidthBitSet.clear()
|
||||
codePointWidths.clear()
|
||||
asciiWidths.fill(-1f)
|
||||
}
|
||||
|
||||
}
|
@ -34,9 +34,12 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
||||
}
|
||||
|
||||
override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) {
|
||||
return if (hasNext() && currentChapter != null) {
|
||||
if (currentChapter?.isLastIndex(pageIndex) == true) {
|
||||
ReadBook.moveToNextChapter(upContent)
|
||||
return if (hasNext()) {
|
||||
if (currentChapter == null || currentChapter?.isLastIndex(pageIndex) == true) {
|
||||
if ((currentChapter == null || isScroll) && nextChapter == null) {
|
||||
return@with false
|
||||
}
|
||||
ReadBook.moveToNextChapter(upContent, false)
|
||||
} else {
|
||||
ReadBook.setPageIndex(pageIndex.plus(1))
|
||||
}
|
||||
@ -47,10 +50,16 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
|
||||
}
|
||||
|
||||
override fun moveToPrev(upContent: Boolean): Boolean = with(dataSource) {
|
||||
return if (hasPrev() && currentChapter != null) {
|
||||
return if (hasPrev()) {
|
||||
if (pageIndex <= 0) {
|
||||
ReadBook.moveToPrevChapter(upContent)
|
||||
if (currentChapter == null && prevChapter == null) {
|
||||
return@with false
|
||||
}
|
||||
ReadBook.moveToPrevChapter(upContent, upContentInPlace = false)
|
||||
} else {
|
||||
if (currentChapter == null) {
|
||||
return@with false
|
||||
}
|
||||
ReadBook.setPageIndex(pageIndex.minus(1))
|
||||
}
|
||||
if (upContent) upContent(resetPageOffset = false)
|
||||
|
@ -16,7 +16,7 @@ class ZhLayout(
|
||||
text: CharSequence,
|
||||
textPaint: TextPaint,
|
||||
width: Int,
|
||||
widthsArray: FloatArray
|
||||
measureHelper: TextMeasure,
|
||||
) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) {
|
||||
companion object {
|
||||
private val postPanc = hashSetOf(
|
||||
@ -50,12 +50,7 @@ class ZhLayout(
|
||||
|
||||
init {
|
||||
var line = 0
|
||||
curPaint.getTextWidths(text as String, widthsArray)
|
||||
val (words, widths) = ChapterProvider.getStringArrayAndTextWidths(
|
||||
text,
|
||||
widthsArray.asList(),
|
||||
curPaint
|
||||
)
|
||||
val (words, widths) = measureHelper.measureTextSplit(text as String)
|
||||
var lineW = 0f
|
||||
var cwPre = 0f
|
||||
var length = 0
|
||||
|
@ -11,6 +11,33 @@ object BitmapCache {
|
||||
|
||||
fun add(bitmap: Bitmap) {
|
||||
reusableBitmaps.add(SoftReference(bitmap))
|
||||
trimSize()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
if (reusableBitmaps.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val iterator = reusableBitmaps.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val item = iterator.next().get() ?: continue
|
||||
item.recycle()
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
|
||||
private fun trimSize() {
|
||||
var byteCount = 0
|
||||
val iterator = reusableBitmaps.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val item = iterator.next().get() ?: continue
|
||||
if (byteCount > 128 * 1024 * 1024) {
|
||||
item.recycle()
|
||||
iterator.remove()
|
||||
} else {
|
||||
byteCount += item.byteCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addInBitmapOptions(options: BitmapFactory.Options) {
|
||||
|
@ -0,0 +1,9 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
fun List<Float>.fastSum(): Float {
|
||||
var sum = 0f
|
||||
for (i in indices) {
|
||||
sum += this[i]
|
||||
}
|
||||
return sum
|
||||
}
|
40
app/src/main/java/io/legado/app/utils/PictureMirror.kt
Normal file
40
app/src/main/java/io/legado/app/utils/PictureMirror.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Picture
|
||||
import android.os.Build
|
||||
import androidx.core.graphics.record
|
||||
|
||||
class PictureMirror {
|
||||
|
||||
var picture: Picture? = null
|
||||
var isDirty = true
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user