|
|
@ -16,7 +16,7 @@ import io.legado.app.help.config.AppConfig |
|
|
|
import io.legado.app.help.config.ReadBookConfig |
|
|
|
import io.legado.app.help.config.ReadBookConfig |
|
|
|
import io.legado.app.lib.theme.accentColor |
|
|
|
import io.legado.app.lib.theme.accentColor |
|
|
|
import io.legado.app.model.ReadBook |
|
|
|
import io.legado.app.model.ReadBook |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextChar |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextColumn |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextLine |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextLine |
|
|
|
import io.legado.app.ui.book.read.page.entities.TextPage |
|
|
|
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.entities.TextPos |
|
|
@ -30,7 +30,7 @@ import io.legado.app.utils.toastOnUi |
|
|
|
import kotlin.math.min |
|
|
|
import kotlin.math.min |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 阅读内容界面 |
|
|
|
* 阅读内容视图 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { |
|
|
|
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { |
|
|
|
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true) |
|
|
|
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true) |
|
|
@ -51,7 +51,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
private var cacheIncreased = false |
|
|
|
private var cacheIncreased = false |
|
|
|
private val increaseSize = 8 * 1024 * 1024 |
|
|
|
private val increaseSize = 8 * 1024 * 1024 |
|
|
|
private val maxCacheSize = 256 * 1024 * 1024 |
|
|
|
private val maxCacheSize = 256 * 1024 * 1024 |
|
|
|
private val reviewButtonArea: ArrayList<FloatArray> = arrayListOf() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//滚动参数 |
|
|
|
//滚动参数 |
|
|
|
private val pageFactory: TextPageFactory get() = callBack.pageFactory |
|
|
|
private val pageFactory: TextPageFactory get() = callBack.pageFactory |
|
|
@ -68,12 +67,18 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
callBack = activity as CallBack |
|
|
|
callBack = activity as CallBack |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 设置内容 |
|
|
|
|
|
|
|
*/ |
|
|
|
fun setContent(textPage: TextPage) { |
|
|
|
fun setContent(textPage: TextPage) { |
|
|
|
this.textPage = textPage |
|
|
|
this.textPage = textPage |
|
|
|
imagePaint.isAntiAlias = AppConfig.useAntiAlias |
|
|
|
imagePaint.isAntiAlias = AppConfig.useAntiAlias |
|
|
|
invalidate() |
|
|
|
invalidate() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 更新绘制区域 |
|
|
|
|
|
|
|
*/ |
|
|
|
fun upVisibleRect() { |
|
|
|
fun upVisibleRect() { |
|
|
|
visibleRect.set( |
|
|
|
visibleRect.set( |
|
|
|
ChapterProvider.paddingLeft.toFloat(), |
|
|
|
ChapterProvider.paddingLeft.toFloat(), |
|
|
@ -102,7 +107,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
* 绘制页面 |
|
|
|
* 绘制页面 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private fun drawPage(canvas: Canvas) { |
|
|
|
private fun drawPage(canvas: Canvas) { |
|
|
|
reviewButtonArea.clear() |
|
|
|
|
|
|
|
var relativeOffset = relativeOffset(0) |
|
|
|
var relativeOffset = relativeOffset(0) |
|
|
|
textPage.textLines.forEach { textLine -> |
|
|
|
textPage.textLines.forEach { textLine -> |
|
|
|
draw(canvas, textPage, textLine, relativeOffset) |
|
|
|
draw(canvas, textPage, textLine, relativeOffset) |
|
|
@ -125,6 +129,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 绘制页面 |
|
|
|
|
|
|
|
*/ |
|
|
|
private fun draw( |
|
|
|
private fun draw( |
|
|
|
canvas: Canvas, |
|
|
|
canvas: Canvas, |
|
|
|
textPage: TextPage, |
|
|
|
textPage: TextPage, |
|
|
@ -161,23 +168,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
reviewCountPaint.textSize = textPaint.textSize * 0.6F |
|
|
|
reviewCountPaint.textSize = textPaint.textSize * 0.6F |
|
|
|
reviewCountPaint.color = textColor |
|
|
|
reviewCountPaint.color = textColor |
|
|
|
textLine.textChars.forEach { |
|
|
|
textLine.textChars.forEach { |
|
|
|
if (it.isImage) { |
|
|
|
when (it.style) { |
|
|
|
drawImage(canvas, textPage, textLine, it, lineTop, lineBottom) |
|
|
|
0 -> { |
|
|
|
} else { |
|
|
|
|
|
|
|
textPaint.color = textColor |
|
|
|
textPaint.color = textColor |
|
|
|
if (it.isSearchResult) { |
|
|
|
if (it.isSearchResult) { |
|
|
|
textPaint.color = context.accentColor |
|
|
|
textPaint.color = context.accentColor |
|
|
|
} |
|
|
|
} |
|
|
|
if (it.charData == "\uD83D\uDCAC" && it.isLineEnd) { |
|
|
|
canvas.drawText(it.charData, it.start, lineBase, textPaint) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
1 -> drawImage(canvas, textPage, textLine, it, lineTop, lineBottom) |
|
|
|
|
|
|
|
2 -> { |
|
|
|
if (textLine.reviewCount <= 0) return@forEach |
|
|
|
if (textLine.reviewCount <= 0) return@forEach |
|
|
|
reviewButtonArea.add( |
|
|
|
|
|
|
|
floatArrayOf( |
|
|
|
|
|
|
|
it.start - textPaint.textSize * 0.4F, |
|
|
|
|
|
|
|
lineBase + textPaint.textSize * 0.2F, |
|
|
|
|
|
|
|
it.start + textPaint.textSize * 2F, |
|
|
|
|
|
|
|
lineBase - textPaint.textSize * 1F, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
canvas.drawLine( |
|
|
|
canvas.drawLine( |
|
|
|
it.start, |
|
|
|
it.start, |
|
|
|
lineBase - textPaint.textSize * 2 / 5, |
|
|
|
lineBase - textPaint.textSize * 2 / 5, |
|
|
@ -243,7 +244,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
lineBase - textPaint.textSize / 6, |
|
|
|
lineBase - textPaint.textSize / 6, |
|
|
|
reviewCountPaint |
|
|
|
reviewCountPaint |
|
|
|
) |
|
|
|
) |
|
|
|
} else canvas.drawText(it.charData, it.start, lineBase, textPaint) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (it.selected) { |
|
|
|
if (it.selected) { |
|
|
|
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) |
|
|
|
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) |
|
|
@ -259,7 +260,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
canvas: Canvas, |
|
|
|
canvas: Canvas, |
|
|
|
textPage: TextPage, |
|
|
|
textPage: TextPage, |
|
|
|
textLine: TextLine, |
|
|
|
textLine: TextLine, |
|
|
|
textChar: TextChar, |
|
|
|
textChar: TextColumn, |
|
|
|
lineTop: Float, |
|
|
|
lineTop: Float, |
|
|
|
lineBottom: Float |
|
|
|
lineBottom: Float |
|
|
|
) { |
|
|
|
) { |
|
|
@ -344,20 +345,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
invalidate() |
|
|
|
invalidate() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun resetPageOffset() { |
|
|
|
|
|
|
|
pageOffset = 0 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 点击评论按钮 |
|
|
|
* 重置滚动位置 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
fun pressReviewButton(x: Float, y: Float): Boolean { |
|
|
|
fun resetPageOffset() { |
|
|
|
reviewButtonArea.forEach { |
|
|
|
pageOffset = 0 |
|
|
|
if (x in it[0]..it[2] && y in it[3]..it[1]) { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -366,19 +358,34 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
fun longPress( |
|
|
|
fun longPress( |
|
|
|
x: Float, |
|
|
|
x: Float, |
|
|
|
y: Float, |
|
|
|
y: Float, |
|
|
|
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit, |
|
|
|
select: (textPos: TextPos) -> Unit, |
|
|
|
) { |
|
|
|
) { |
|
|
|
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar -> |
|
|
|
touch(x, y) { _, textPos, _, _, textColumn -> |
|
|
|
if (textChar.isImage) { |
|
|
|
if (textColumn.style == 2) return@touch |
|
|
|
callBack.onImageLongPress(x, y, textChar.charData) |
|
|
|
if (textColumn.style == 1) { |
|
|
|
|
|
|
|
callBack.onImageLongPress(x, y, textColumn.charData) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
if (!selectAble) return@touch |
|
|
|
if (!selectAble) return@touch |
|
|
|
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) return@touch |
|
|
|
textColumn.selected = true |
|
|
|
textChar.selected = true |
|
|
|
|
|
|
|
invalidate() |
|
|
|
invalidate() |
|
|
|
select(relativePos, lineIndex, charIndex) |
|
|
|
select(textPos) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 单击 |
|
|
|
|
|
|
|
* @return true:已处理, false:未处理 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
fun click(x: Float, y: Float): Boolean { |
|
|
|
|
|
|
|
var handled = false |
|
|
|
|
|
|
|
touch(x, y) { _, textPos, textPage, textLine, textColumn -> |
|
|
|
|
|
|
|
if (textColumn.style == 2) { |
|
|
|
|
|
|
|
context.toastOnUi("Button Pressed!") |
|
|
|
|
|
|
|
handled = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return handled |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -387,13 +394,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
fun selectText( |
|
|
|
fun selectText( |
|
|
|
x: Float, |
|
|
|
x: Float, |
|
|
|
y: Float, |
|
|
|
y: Float, |
|
|
|
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit, |
|
|
|
select: (textPos: TextPos) -> Unit, |
|
|
|
) { |
|
|
|
) { |
|
|
|
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar -> |
|
|
|
touch(x, y) { _, textPos, _, _, textColumn -> |
|
|
|
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) return@touch |
|
|
|
if (textColumn.style == 2) return@touch |
|
|
|
textChar.selected = true |
|
|
|
textColumn.selected = true |
|
|
|
invalidate() |
|
|
|
invalidate() |
|
|
|
select(relativePos, lineIndex, charIndex) |
|
|
|
select(textPos) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -401,11 +408,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
* 开始选择符移动 |
|
|
|
* 开始选择符移动 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
fun selectStartMove(x: Float, y: Float) { |
|
|
|
fun selectStartMove(x: Float, y: Float) { |
|
|
|
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar -> |
|
|
|
touch(x, y) { relativeOffset, textPos, _, textLine, textChar -> |
|
|
|
val pos = TextPos(relativePos, lineIndex, charIndex) |
|
|
|
if (selectStart.compare(textPos) != 0) { |
|
|
|
if (selectStart.compare(pos) != 0) { |
|
|
|
if (textPos.compare(selectEnd) <= 0) { |
|
|
|
if (pos.compare(selectEnd) <= 0) { |
|
|
|
selectStart.upData(pos = textPos) |
|
|
|
selectStart.upData(pos = pos) |
|
|
|
|
|
|
|
upSelectedStart( |
|
|
|
upSelectedStart( |
|
|
|
textChar.start, |
|
|
|
textChar.start, |
|
|
|
textLine.lineBottom + relativeOffset, |
|
|
|
textLine.lineBottom + relativeOffset, |
|
|
@ -421,11 +427,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
* 结束选择符移动 |
|
|
|
* 结束选择符移动 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
fun selectEndMove(x: Float, y: Float) { |
|
|
|
fun selectEndMove(x: Float, y: Float) { |
|
|
|
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar -> |
|
|
|
touch(x, y) { relativeOffset, textPos, _, textLine, textChar -> |
|
|
|
val pos = TextPos(relativePos, lineIndex, charIndex) |
|
|
|
if (textPos.compare(selectEnd) != 0) { |
|
|
|
if (pos.compare(selectEnd) != 0) { |
|
|
|
if (textPos.compare(selectStart) >= 0) { |
|
|
|
if (pos.compare(selectStart) >= 0) { |
|
|
|
selectEnd.upData(textPos) |
|
|
|
selectEnd.upData(pos) |
|
|
|
|
|
|
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
|
|
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
|
|
|
upSelectChars() |
|
|
|
upSelectChars() |
|
|
|
} |
|
|
|
} |
|
|
@ -433,17 +438,19 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 触碰位置信息 |
|
|
|
|
|
|
|
* @param touched 回调 |
|
|
|
|
|
|
|
*/ |
|
|
|
private fun touch( |
|
|
|
private fun touch( |
|
|
|
x: Float, |
|
|
|
x: Float, |
|
|
|
y: Float, |
|
|
|
y: Float, |
|
|
|
touched: ( |
|
|
|
touched: ( |
|
|
|
relativeOffset: Float, |
|
|
|
relativeOffset: Float, |
|
|
|
relativePos: Int, |
|
|
|
textPos: TextPos, |
|
|
|
textPage: TextPage, |
|
|
|
textPage: TextPage, |
|
|
|
lineIndex: Int, |
|
|
|
|
|
|
|
textLine: TextLine, |
|
|
|
textLine: TextLine, |
|
|
|
charIndex: Int, |
|
|
|
textColumn: TextColumn |
|
|
|
textChar: TextChar |
|
|
|
|
|
|
|
) -> Unit |
|
|
|
) -> Unit |
|
|
|
) { |
|
|
|
) { |
|
|
|
if (!visibleRect.contains(x, y)) return |
|
|
|
if (!visibleRect.contains(x, y)) return |
|
|
@ -462,9 +469,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
if (textChar.isTouch(x)) { |
|
|
|
if (textChar.isTouch(x)) { |
|
|
|
touched.invoke( |
|
|
|
touched.invoke( |
|
|
|
relativeOffset, |
|
|
|
relativeOffset, |
|
|
|
relativePos, textPage, |
|
|
|
TextPos(relativePos, lineIndex, charIndex), |
|
|
|
lineIndex, textLine, |
|
|
|
textPage, textLine, textChar |
|
|
|
charIndex, textChar |
|
|
|
|
|
|
|
) |
|
|
|
) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
@ -514,7 +520,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at |
|
|
|
textPos.lineIndex = lineIndex |
|
|
|
textPos.lineIndex = lineIndex |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
|
|
|
textPos.charIndex = charIndex |
|
|
|
textPos.charIndex = charIndex |
|
|
|
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) continue |
|
|
|
if (textChar.style == 2) continue |
|
|
|
textChar.selected = |
|
|
|
textChar.selected = |
|
|
|
textPos.compare(selectStart) >= 0 && textPos.compare(selectEnd) <= 0 |
|
|
|
textPos.compare(selectStart) >= 0 && textPos.compare(selectEnd) <= 0 |
|
|
|
textChar.isSearchResult = textChar.selected && callBack.isSelectingSearchResult |
|
|
|
textChar.isSearchResult = textChar.selected && callBack.isSelectingSearchResult |
|
|
|