|
|
@ -3,14 +3,13 @@ package io.legado.app.ui.book.read.page |
|
|
|
import android.content.Context |
|
|
|
import android.content.Context |
|
|
|
import android.graphics.Canvas |
|
|
|
import android.graphics.Canvas |
|
|
|
import android.graphics.Paint |
|
|
|
import android.graphics.Paint |
|
|
|
import android.text.Layout |
|
|
|
|
|
|
|
import android.text.StaticLayout |
|
|
|
|
|
|
|
import android.util.AttributeSet |
|
|
|
import android.util.AttributeSet |
|
|
|
import android.view.View |
|
|
|
import android.view.View |
|
|
|
import io.legado.app.R |
|
|
|
import io.legado.app.R |
|
|
|
import io.legado.app.constant.PreferKey |
|
|
|
import io.legado.app.constant.PreferKey |
|
|
|
import io.legado.app.help.ReadBookConfig |
|
|
|
import io.legado.app.help.ReadBookConfig |
|
|
|
import io.legado.app.lib.theme.accentColor |
|
|
|
import io.legado.app.lib.theme.accentColor |
|
|
|
|
|
|
|
import io.legado.app.ui.book.read.page.entities.TextChar |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextPage |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextPage |
|
|
|
import io.legado.app.utils.activity |
|
|
|
import io.legado.app.utils.activity |
|
|
|
import io.legado.app.utils.getCompatColor |
|
|
|
import io.legado.app.utils.getCompatColor |
|
|
@ -18,6 +17,8 @@ import io.legado.app.utils.getPrefBoolean |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { |
|
|
|
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { |
|
|
|
|
|
|
|
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble) |
|
|
|
|
|
|
|
var upView: ((TextPage) -> Unit)? = null |
|
|
|
private val selectedPaint by lazy { |
|
|
|
private 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) |
|
|
@ -25,7 +26,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
private var callBack: CallBack |
|
|
|
private var callBack: CallBack |
|
|
|
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble) |
|
|
|
|
|
|
|
private var selectLineStart = 0 |
|
|
|
private var selectLineStart = 0 |
|
|
|
private var selectCharStart = 0 |
|
|
|
private var selectCharStart = 0 |
|
|
|
private var selectLineEnd = 0 |
|
|
|
private var selectLineEnd = 0 |
|
|
@ -36,7 +36,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
private val maxScrollOffset = 100f |
|
|
|
private val maxScrollOffset = 100f |
|
|
|
private var pageOffset = 0f |
|
|
|
private var pageOffset = 0f |
|
|
|
private var linePos = 0 |
|
|
|
private var linePos = 0 |
|
|
|
private var isLastPage = false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init { |
|
|
|
init { |
|
|
|
callBack = activity as CallBack |
|
|
|
callBack = activity as CallBack |
|
|
@ -51,70 +50,105 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
|
|
|
|
|
|
|
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { |
|
|
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { |
|
|
|
super.onSizeChanged(w, h, oldw, oldh) |
|
|
|
super.onSizeChanged(w, h, oldw, oldh) |
|
|
|
ReadBookConfig.durConfig.let { |
|
|
|
ChapterProvider.viewWidth = w |
|
|
|
ChapterProvider.viewWidth = w |
|
|
|
ChapterProvider.viewHeight = h |
|
|
|
ChapterProvider.viewHeight = h |
|
|
|
ChapterProvider.upSize() |
|
|
|
ChapterProvider.upSize(ReadBookConfig.durConfig) |
|
|
|
textPage.format() |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun onDraw(canvas: Canvas) { |
|
|
|
override fun onDraw(canvas: Canvas) { |
|
|
|
super.onDraw(canvas) |
|
|
|
super.onDraw(canvas) |
|
|
|
if (textPage.textLines.isEmpty()) { |
|
|
|
if (ReadBookConfig.isScroll) { |
|
|
|
drawMsg(canvas, textPage.text) |
|
|
|
drawScrollPage(canvas) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
drawHorizontalPage(canvas) |
|
|
|
drawHorizontalPage(canvas) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Suppress("DEPRECATION") |
|
|
|
private fun drawHorizontalPage(canvas: Canvas) { |
|
|
|
private fun drawMsg(canvas: Canvas, msg: String) { |
|
|
|
textPage.textLines.forEach { textLine -> |
|
|
|
val layout = StaticLayout( |
|
|
|
drawChars( |
|
|
|
msg, ChapterProvider.contentPaint, width, |
|
|
|
canvas, |
|
|
|
Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false |
|
|
|
textLine.textChars, |
|
|
|
) |
|
|
|
textLine.lineTop, |
|
|
|
val y = (height - layout.height) / 2f |
|
|
|
textLine.lineBase, |
|
|
|
for (lineIndex in 0 until layout.lineCount) { |
|
|
|
textLine.lineBottom, |
|
|
|
val x = (width - layout.getLineMax(lineIndex)) / 2 |
|
|
|
textLine.isTitle, |
|
|
|
val words = |
|
|
|
textLine.isReadAloud |
|
|
|
msg.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) |
|
|
|
) |
|
|
|
canvas.drawText(words, x, y, ChapterProvider.contentPaint) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun drawHorizontalPage(canvas: Canvas) { |
|
|
|
private fun drawScrollPage(canvas: Canvas) { |
|
|
|
|
|
|
|
val mPageOffset = pageOffset |
|
|
|
textPage.textLines.forEach { textLine -> |
|
|
|
textPage.textLines.forEach { textLine -> |
|
|
|
val textPaint = if (textLine.isTitle) { |
|
|
|
val lineTop = textLine.lineTop + mPageOffset |
|
|
|
ChapterProvider.titlePaint |
|
|
|
val lineBase = textLine.lineBase + mPageOffset |
|
|
|
} else { |
|
|
|
val lineBottom = textLine.lineBottom + mPageOffset |
|
|
|
ChapterProvider.contentPaint |
|
|
|
drawChars( |
|
|
|
} |
|
|
|
canvas, |
|
|
|
textPaint.color = if (textLine.isReadAloud) { |
|
|
|
textLine.textChars, |
|
|
|
context.accentColor |
|
|
|
lineTop, |
|
|
|
} else { |
|
|
|
lineBase, |
|
|
|
ReadBookConfig.durConfig.textColor() |
|
|
|
lineBottom, |
|
|
|
} |
|
|
|
textLine.isTitle, |
|
|
|
textLine.textChars.forEach { |
|
|
|
textLine.isReadAloud |
|
|
|
canvas.drawText( |
|
|
|
) |
|
|
|
it.charData, |
|
|
|
} |
|
|
|
it.leftBottomPosition.x, |
|
|
|
pageFactory.nextPage?.textLines?.forEach { textLine -> |
|
|
|
it.leftBottomPosition.y, |
|
|
|
val yPy = mPageOffset + textPage.height - ChapterProvider.paddingTop |
|
|
|
textPaint |
|
|
|
val lineTop = textLine.lineTop + yPy |
|
|
|
) |
|
|
|
val lineBase = textLine.lineBase + yPy |
|
|
|
if (it.selected) { |
|
|
|
val lineBottom = textLine.lineBottom + yPy |
|
|
|
canvas.drawRect( |
|
|
|
drawChars( |
|
|
|
it.leftBottomPosition.x, |
|
|
|
canvas, |
|
|
|
textLine.lineTop, |
|
|
|
textLine.textChars, |
|
|
|
it.rightTopPosition.x, |
|
|
|
lineTop, |
|
|
|
textLine.lineBottom, |
|
|
|
lineBase, |
|
|
|
selectedPaint |
|
|
|
lineBottom, |
|
|
|
) |
|
|
|
textLine.isTitle, |
|
|
|
} |
|
|
|
textLine.isReadAloud |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
pageFactory.prevPage?.textLines?.forEach { textLine -> |
|
|
|
|
|
|
|
val yPy = mPageOffset + ChapterProvider.paddingTop |
|
|
|
|
|
|
|
val lineTop = -textLine.lineTop + yPy |
|
|
|
|
|
|
|
val lineBase = -textLine.lineBase + yPy |
|
|
|
|
|
|
|
val lineBottom = -textLine.lineBottom + yPy |
|
|
|
|
|
|
|
drawChars( |
|
|
|
|
|
|
|
canvas, |
|
|
|
|
|
|
|
textLine.textChars, |
|
|
|
|
|
|
|
lineTop, |
|
|
|
|
|
|
|
lineBase, |
|
|
|
|
|
|
|
lineBottom, |
|
|
|
|
|
|
|
textLine.isTitle, |
|
|
|
|
|
|
|
textLine.isReadAloud |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun drawChars( |
|
|
|
|
|
|
|
canvas: Canvas, |
|
|
|
|
|
|
|
textChars: List<TextChar>, |
|
|
|
|
|
|
|
lineTop: Float, |
|
|
|
|
|
|
|
lineBase: Float, |
|
|
|
|
|
|
|
lineBottom: Float, |
|
|
|
|
|
|
|
isTitle: Boolean, |
|
|
|
|
|
|
|
isReadAloud: Boolean |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
val textPaint = if (isTitle) ChapterProvider.titlePaint else ChapterProvider.contentPaint |
|
|
|
|
|
|
|
textPaint.color = |
|
|
|
|
|
|
|
if (isReadAloud) context.accentColor else ReadBookConfig.durConfig.textColor() |
|
|
|
|
|
|
|
textChars.forEach { |
|
|
|
|
|
|
|
canvas.drawText(it.charData, it.start, lineBase, textPaint) |
|
|
|
|
|
|
|
if (it.selected) { |
|
|
|
|
|
|
|
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun onScroll(mOffset: Float) { |
|
|
|
fun onScroll(mOffset: Float) { |
|
|
|
|
|
|
|
if (mOffset == 0f) return |
|
|
|
var offset = mOffset |
|
|
|
var offset = mOffset |
|
|
|
if (offset > maxScrollOffset) { |
|
|
|
if (offset > maxScrollOffset) { |
|
|
|
offset = maxScrollOffset |
|
|
|
offset = maxScrollOffset |
|
|
@ -122,78 +156,56 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
offset = -maxScrollOffset |
|
|
|
offset = -maxScrollOffset |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!isLastPage || offset < 0) { |
|
|
|
pageOffset += offset |
|
|
|
pageOffset += offset |
|
|
|
if (pageOffset > 0) { |
|
|
|
isLastPage = false |
|
|
|
pageFactory.moveToPrev() |
|
|
|
} |
|
|
|
textPage = pageFactory.currentPage ?: TextPage().format() |
|
|
|
// 首页 |
|
|
|
pageOffset -= textPage.height |
|
|
|
if (pageOffset < 0 && !pageFactory.hasPrev()) { |
|
|
|
upView?.invoke(textPage) |
|
|
|
pageOffset = 0f |
|
|
|
} else if (pageOffset < -textPage.height) { |
|
|
|
} |
|
|
|
pageOffset += textPage.height |
|
|
|
|
|
|
|
pageFactory.moveToNext() |
|
|
|
val cHeight = if (textPage.height > 0) textPage.height else height |
|
|
|
textPage = pageFactory.currentPage ?: TextPage().format() |
|
|
|
if (offset > 0 && pageOffset > cHeight) { |
|
|
|
upView?.invoke(textPage) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
invalidate() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun resetPageOffset() { |
|
|
|
fun resetPageOffset() { |
|
|
|
pageOffset = 0f |
|
|
|
pageOffset = 0f |
|
|
|
linePos = 0 |
|
|
|
linePos = 0 |
|
|
|
isLastPage = false |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun switchToPageOffset(offset: Int) { |
|
|
|
fun selectText(x: Float, y: Float, select: (lineIndex: Int, charIndex: Int) -> Unit) { |
|
|
|
when (offset) { |
|
|
|
|
|
|
|
1 -> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
-1 -> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun selectText(x: Float, y: Float): Boolean { |
|
|
|
|
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
if (y > textLine.lineTop && y < textLine.lineBottom) { |
|
|
|
if (y > textLine.lineTop && y < textLine.lineBottom) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { |
|
|
|
if (x > textChar.start && x < textChar.end) { |
|
|
|
textChar.selected = true |
|
|
|
textChar.selected = true |
|
|
|
invalidate() |
|
|
|
invalidate() |
|
|
|
selectLineStart = lineIndex |
|
|
|
selectLineStart = lineIndex |
|
|
|
selectCharStart = charIndex |
|
|
|
selectCharStart = charIndex |
|
|
|
selectLineEnd = lineIndex |
|
|
|
selectLineEnd = lineIndex |
|
|
|
selectCharEnd = charIndex |
|
|
|
selectCharEnd = charIndex |
|
|
|
upSelectedStart( |
|
|
|
upSelectedStart(textChar.start, textLine.lineBottom) |
|
|
|
textChar.leftBottomPosition.x, |
|
|
|
upSelectedEnd(textChar.end, textLine.lineBottom) |
|
|
|
textChar.leftBottomPosition.y |
|
|
|
select(lineIndex, charIndex) |
|
|
|
) |
|
|
|
|
|
|
|
upSelectedEnd( |
|
|
|
|
|
|
|
textChar.rightTopPosition.x, |
|
|
|
|
|
|
|
textChar.leftBottomPosition.y |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
break |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun selectStartMove(x: Float, y: Float) { |
|
|
|
fun selectStartMove(x: Float, y: Float) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
if (y > textLine.lineTop && y < textLine.lineBottom) { |
|
|
|
if (y > textLine.lineTop && y < textLine.lineBottom) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { |
|
|
|
if (x > textChar.start && x < textChar.end) { |
|
|
|
if (selectLineStart != lineIndex || selectCharStart != charIndex) { |
|
|
|
if (selectLineStart != lineIndex || selectCharStart != charIndex) { |
|
|
|
selectLineStart = lineIndex |
|
|
|
selectLineStart = lineIndex |
|
|
|
selectCharStart = charIndex |
|
|
|
selectCharStart = charIndex |
|
|
|
upSelectedStart( |
|
|
|
upSelectedStart(textChar.start, textLine.lineBottom) |
|
|
|
textChar.leftBottomPosition.x, |
|
|
|
|
|
|
|
textChar.leftBottomPosition.y |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
upSelectChars(textPage) |
|
|
|
upSelectChars(textPage) |
|
|
|
} |
|
|
|
} |
|
|
|
break |
|
|
|
break |
|
|
@ -204,18 +216,24 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun selectStartMoveIndex(lineIndex: Int, charIndex: Int) { |
|
|
|
|
|
|
|
selectLineStart = lineIndex |
|
|
|
|
|
|
|
selectCharStart = charIndex |
|
|
|
|
|
|
|
val textLine = textPage.textLines[lineIndex] |
|
|
|
|
|
|
|
val textChar = textLine.textChars[charIndex] |
|
|
|
|
|
|
|
upSelectedStart(textChar.start, textLine.lineBottom) |
|
|
|
|
|
|
|
upSelectChars(textPage) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun selectEndMove(x: Float, y: Float) { |
|
|
|
fun selectEndMove(x: Float, y: Float) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
if (y > textLine.lineTop && y < textLine.lineBottom) { |
|
|
|
if (y > textLine.lineTop && y < textLine.lineBottom) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { |
|
|
|
if (x > textChar.start && x < textChar.end) { |
|
|
|
if (selectLineEnd != lineIndex || selectCharEnd != charIndex) { |
|
|
|
if (selectLineEnd != lineIndex || selectCharEnd != charIndex) { |
|
|
|
selectLineEnd = lineIndex |
|
|
|
selectLineEnd = lineIndex |
|
|
|
selectCharEnd = charIndex |
|
|
|
selectCharEnd = charIndex |
|
|
|
upSelectedEnd( |
|
|
|
upSelectedEnd(textChar.end, textLine.lineBottom) |
|
|
|
textChar.rightTopPosition.x, |
|
|
|
|
|
|
|
textChar.leftBottomPosition.y |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
upSelectChars(textPage) |
|
|
|
upSelectChars(textPage) |
|
|
|
} |
|
|
|
} |
|
|
|
break |
|
|
|
break |
|
|
@ -226,6 +244,15 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun selectEndMoveIndex(lineIndex: Int, charIndex: Int) { |
|
|
|
|
|
|
|
selectLineEnd = lineIndex |
|
|
|
|
|
|
|
selectCharEnd = charIndex |
|
|
|
|
|
|
|
val textLine = textPage.textLines[lineIndex] |
|
|
|
|
|
|
|
val textChar = textLine.textChars[charIndex] |
|
|
|
|
|
|
|
upSelectedEnd(textChar.end, textLine.lineBottom) |
|
|
|
|
|
|
|
upSelectChars(textPage) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun upSelectChars(textPage: TextPage) { |
|
|
|
private fun upSelectChars(textPage: TextPage) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|