pull/517/head
gedoor 4 years ago
parent d20ec0a97c
commit f14ba7c859
  1. 88
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  2. 1
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  3. 279
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  4. 611
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  5. 460
      app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
  6. 2
      app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt
  7. 2
      app/src/main/java/io/legado/app/ui/book/read/page/api/PageFactory.kt
  8. 6
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt
  9. 22
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  10. 6
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt
  11. 46
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  12. 23
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  13. 12
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt
  14. 6
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt
  15. 4
      app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt
  16. 4
      app/src/main/res/layout/activity_book_read.xml

@ -37,9 +37,9 @@ import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR
import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR
import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_COLOR
import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.TextPageFactory
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.book.searchContent.SearchContentActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.ChapterListActivity
@ -60,7 +60,7 @@ import org.jetbrains.anko.toast
class ReadBookActivity : ReadBookBaseActivity(),
View.OnTouchListener,
PageView.CallBack,
ReadView.CallBack,
TextActionMenu.CallBack,
ContentTextView.CallBack,
ReadMenu.CallBack,
@ -79,7 +79,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
override val scope: CoroutineScope get() = this
override val isInitFinish: Boolean get() = viewModel.isInitFinish
override val isScroll: Boolean get() = binding.pageView.isScroll
override val isScroll: Boolean get() = binding.readView.isScroll
private val mHandler = Handler(Looper.getMainLooper())
private val keepScreenRunnable: Runnable =
Runnable { keepScreenOn(window, false) }
@ -89,8 +89,8 @@ class ReadBookActivity : ReadBookBaseActivity(),
private var screenTimeOut: Long = 0
private var timeBatteryReceiver: TimeBatteryReceiver? = null
private var loadStates: Boolean = false
override val pageFactory: TextPageFactory get() = binding.pageView.pageFactory
override val headerHeight: Int get() = binding.pageView.curPage.headerHeight
override val pageFactory: TextPageFactory get() = binding.readView.pageFactory
override val headerHeight: Int get() = binding.readView.curPage.headerHeight
@SuppressLint("ClickableViewAccessibility")
override fun onActivityCreated(savedInstanceState: Bundle?) {
@ -116,7 +116,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
binding.pageView.upStatusBar()
binding.readView.upStatusBar()
ReadBook.loadContent(resetPageOffset = false)
}
@ -125,7 +125,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
ReadBook.readStartTime = System.currentTimeMillis()
upSystemUiVisibility()
timeBatteryReceiver = TimeBatteryReceiver.register(this)
binding.pageView.upTime()
binding.readView.upTime()
}
override fun onPause() {
@ -190,7 +190,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
R.id.menu_refresh -> {
ReadBook.book?.let {
ReadBook.curTextChapter = null
binding.pageView.upContent()
binding.readView.upContent()
viewModel.refreshContent(it)
}
}
@ -212,7 +212,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
ReadBook.loadContent(false)
}
R.id.menu_page_anim -> showPageAnimConfig {
binding.pageView.upPageAnim()
binding.readView.upPageAnim()
}
R.id.menu_book_info -> ReadBook.book?.let {
startActivity<BookInfoActivity>(
@ -264,13 +264,13 @@ class ReadBookActivity : ReadBookBaseActivity(),
when {
isPrevKey(keyCode) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
binding.pageView.pageDelegate?.keyTurnPage(PageDirection.PREV)
binding.readView.pageDelegate?.keyTurnPage(PageDirection.PREV)
return true
}
}
isNextKey(keyCode) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
binding.pageView.pageDelegate?.keyTurnPage(PageDirection.NEXT)
binding.readView.pageDelegate?.keyTurnPage(PageDirection.NEXT)
return true
}
}
@ -285,7 +285,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
}
keyCode == KeyEvent.KEYCODE_SPACE -> {
binding.pageView.pageDelegate?.keyTurnPage(PageDirection.NEXT)
binding.readView.pageDelegate?.keyTurnPage(PageDirection.NEXT)
return true
}
}
@ -346,11 +346,11 @@ class ReadBookActivity : ReadBookBaseActivity(),
MotionEvent.ACTION_DOWN -> textActionMenu?.dismiss()
MotionEvent.ACTION_MOVE -> {
when (v.id) {
R.id.cursor_left -> binding.pageView.curPage.selectStartMove(
R.id.cursor_left -> binding.readView.curPage.selectStartMove(
event.rawX + binding.cursorLeft.width,
event.rawY - binding.cursorLeft.height
)
R.id.cursor_right -> binding.pageView.curPage.selectEndMove(
R.id.cursor_right -> binding.readView.curPage.selectEndMove(
event.rawX - binding.cursorRight.width,
event.rawY - binding.cursorRight.height
)
@ -419,7 +419,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/**
* 当前选择的文本
*/
override val selectedText: String get() = binding.pageView.curPage.selectedText
override val selectedText: String get() = binding.readView.curPage.selectedText
/**
* 文本选择菜单操作
@ -455,8 +455,8 @@ class ReadBookActivity : ReadBookBaseActivity(),
*/
override fun onMenuActionFinally() {
textActionMenu?.dismiss()
binding.pageView.curPage.cancelSelect()
binding.pageView.isTextSelected = false
binding.readView.curPage.cancelSelect()
binding.readView.isTextSelected = false
}
/**
@ -468,8 +468,8 @@ class ReadBookActivity : ReadBookBaseActivity(),
if (getPrefBoolean("volumeKeyPageOnPlay")
|| BaseReadAloudService.pause
) {
binding.pageView.pageDelegate?.isCancel = false
binding.pageView.pageDelegate?.keyTurnPage(direction)
binding.readView.pageDelegate?.isCancel = false
binding.readView.pageDelegate?.keyTurnPage(direction)
return true
}
}
@ -499,7 +499,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {
autoPageProgress = 0
launch {
binding.pageView.upContent(relativePosition, resetPageOffset)
binding.readView.upContent(relativePosition, resetPageOffset)
binding.readMenu.setSeekPage(ReadBook.durPageIndex())
}
loadStates = false
@ -516,7 +516,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
override fun upPageAnim() {
launch {
binding.pageView.upPageAnim()
binding.readView.upPageAnim()
}
}
@ -579,8 +579,8 @@ class ReadBookActivity : ReadBookBaseActivity(),
autoPageStop()
} else {
isAutoPage = true
binding.pageView.upContent()
binding.pageView.upContent(1)
binding.readView.upContent()
binding.readView.upContent(1)
autoPagePlus()
}
binding.readMenu.setAutoPage(isAutoPage)
@ -589,20 +589,20 @@ class ReadBookActivity : ReadBookBaseActivity(),
override fun autoPageStop() {
isAutoPage = false
mHandler.removeCallbacks(autoPageRunnable)
binding.pageView.upContent()
binding.readView.upContent()
}
private fun autoPagePlus() {
mHandler.removeCallbacks(autoPageRunnable)
if (binding.pageView.isScroll) {
binding.pageView.curPage.scroll(-binding.pageView.height / ReadBookConfig.autoReadSpeed / 50)
if (binding.readView.isScroll) {
binding.readView.curPage.scroll(-binding.readView.height / ReadBookConfig.autoReadSpeed / 50)
} else {
autoPageProgress++
if (autoPageProgress >= ReadBookConfig.autoReadSpeed * 50) {
autoPageProgress = 0
binding.pageView.fillPage(PageDirection.NEXT)
binding.readView.fillPage(PageDirection.NEXT)
} else {
binding.pageView.invalidate()
binding.readView.invalidate()
}
}
mHandler.postDelayed(autoPageRunnable, 20)
@ -751,28 +751,28 @@ class ReadBookActivity : ReadBookBaseActivity(),
ReadBook.skipToPage(positions[0])
}
withContext(Main) {
binding.pageView.curPage.selectStartMoveIndex(
binding.readView.curPage.selectStartMoveIndex(
0,
positions[1],
positions[2]
)
delay(20L)
when (positions[3]) {
0 -> binding.pageView.curPage.selectEndMoveIndex(
0 -> binding.readView.curPage.selectEndMoveIndex(
0,
positions[1],
positions[2] + viewModel.searchContentQuery.length - 1
)
1 -> binding.pageView.curPage.selectEndMoveIndex(
1 -> binding.readView.curPage.selectEndMoveIndex(
0,
positions[1] + 1,
positions[4]
)
//consider change page, jump to scroll position
-1 -> binding.pageView.curPage
-1 -> binding.readView.curPage
.selectEndMoveIndex(1, 0, positions[4])
}
binding.pageView.isTextSelected = true
binding.readView.isTextSelected = true
delay(100L)
}
}
@ -803,7 +803,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
super.onDestroy()
mHandler.removeCallbacks(keepScreenRunnable)
textActionMenu?.dismiss()
binding.pageView.onDestroy()
binding.readView.onDestroy()
ReadBook.msg = null
if (!BuildConfig.DEBUG) {
SyncBookProgress.uploadBookProgress()
@ -813,11 +813,11 @@ class ReadBookActivity : ReadBookBaseActivity(),
override fun observeLiveBus() {
super.observeLiveBus()
observeEvent<String>(EventBus.TIME_CHANGED) { binding.pageView.upTime() }
observeEvent<Int>(EventBus.BATTERY_CHANGED) { binding.pageView.upBattery(it) }
observeEvent<String>(EventBus.TIME_CHANGED) { binding.readView.upTime() }
observeEvent<Int>(EventBus.BATTERY_CHANGED) { binding.readView.upBattery(it) }
observeEvent<BookChapter>(EventBus.OPEN_CHAPTER) {
viewModel.openChapter(it.index, ReadBook.durChapterPos)
binding.pageView.upContent()
binding.readView.upContent()
}
observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {
if (it) {
@ -828,13 +828,13 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
observeEvent<Boolean>(EventBus.UP_CONFIG) {
upSystemUiVisibility()
binding.pageView.upBg()
binding.pageView.upTipStyle()
binding.pageView.upStyle()
binding.readView.upBg()
binding.readView.upTipStyle()
binding.readView.upStyle()
if (it) {
ReadBook.loadContent(resetPageOffset = false)
} else {
binding.pageView.upContent(resetPageOffset = false)
binding.readView.upContent(resetPageOffset = false)
}
}
observeEvent<Int>(EventBus.ALOUD_STATE) {
@ -843,7 +843,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
val page = textChapter.getPageByReadPos(ReadBook.durChapterPos)
if (page != null) {
page.removePageAloudSpan()
binding.pageView.upContent(resetPageOffset = false)
binding.readView.upContent(resetPageOffset = false)
}
}
}
@ -864,7 +864,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
upScreenTimeOut()
}
observeEvent<Boolean>(PreferKey.textSelectAble) {
binding.pageView.curPage.upSelectAble(it)
binding.readView.curPage.upSelectAble(it)
}
observeEvent<String>(PreferKey.showBrightnessView) {
binding.readMenu.upBrightnessState()

@ -15,6 +15,7 @@ import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.entities.*
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.ImageProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.activity
import io.legado.app.utils.getCompatColor

@ -1,279 +0,0 @@
package io.legado.app.ui.book.read.page
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.constant.AppConst.timeFormat
import io.legado.app.databinding.ViewBookPageBinding
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.entities.PageData
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.widget.BatteryView
import io.legado.app.utils.*
import org.jetbrains.anko.topPadding
import java.util.*
/**
* 阅读界面
*/
class ContentView(context: Context) : FrameLayout(context) {
private val binding = ViewBookPageBinding.inflate(LayoutInflater.from(context), this, true)
private var battery = 100
private var tvTitle: BatteryView? = null
private var tvTime: BatteryView? = null
private var tvBattery: BatteryView? = null
private var tvPage: BatteryView? = null
private var tvTotalProgress: BatteryView? = null
private var tvPageAndTotal: BatteryView? = null
private var tvBookName: BatteryView? = null
private var tvTimeBattery: BatteryView? = null
val headerHeight: Int
get() {
val h1 = if (ReadBookConfig.hideStatusBar) 0 else context.statusBarHeight
val h2 = if (binding.llHeader.isGone) 0 else binding.llHeader.height
return h1 + h2
}
init {
if (!isInEditMode) {
//设置背景防止切换背景时文字重叠
setBackgroundColor(context.getCompatColor(R.color.background))
upTipStyle()
upStyle()
binding.contentTextView.upView = {
setProgress(it)
}
}
}
fun upStyle() = with(binding) {
ChapterProvider.let {
bvHeaderLeft.typeface = it.typeface
tvHeaderLeft.typeface = it.typeface
tvHeaderMiddle.typeface = it.typeface
tvHeaderRight.typeface = it.typeface
bvFooterLeft.typeface = it.typeface
tvFooterLeft.typeface = it.typeface
tvFooterMiddle.typeface = it.typeface
tvFooterRight.typeface = it.typeface
}
ReadBookConfig.let {
val tipColor = with(ReadTipConfig) {
if (tipColor == 0) it.textColor else tipColor
}
bvHeaderLeft.setColor(tipColor)
tvHeaderLeft.setColor(tipColor)
tvHeaderMiddle.setColor(tipColor)
tvHeaderRight.setColor(tipColor)
bvFooterLeft.setColor(tipColor)
tvFooterLeft.setColor(tipColor)
tvFooterMiddle.setColor(tipColor)
tvFooterRight.setColor(tipColor)
upStatusBar()
llHeader.setPadding(
it.headerPaddingLeft.dp,
it.headerPaddingTop.dp,
it.headerPaddingRight.dp,
it.headerPaddingBottom.dp
)
llFooter.setPadding(
it.footerPaddingLeft.dp,
it.footerPaddingTop.dp,
it.footerPaddingRight.dp,
it.footerPaddingBottom.dp
)
vwTopDivider.visible(it.showHeaderLine)
vwBottomDivider.visible(it.showFooterLine)
pageNvBar.layoutParams = pageNvBar.layoutParams.apply {
height = if (it.hideNavigationBar) 0 else App.navigationBarHeight
}
}
contentTextView.upVisibleRect()
upTime()
upBattery(battery)
}
/**
* 显示状态栏时隐藏header
*/
fun upStatusBar() {
binding.vwStatusBar.topPadding = context.statusBarHeight
binding.vwStatusBar.isGone =
ReadBookConfig.hideStatusBar || (activity as? BaseActivity<*>)?.isInMultiWindow == true
}
fun upTipStyle() = with(binding) {
ReadTipConfig.apply {
tvHeaderLeft.isInvisible = tipHeaderLeft != chapterTitle
bvHeaderLeft.isInvisible =
tipHeaderLeft == none || !tvHeaderLeft.isInvisible
tvHeaderRight.isGone = tipHeaderRight == none
tvHeaderMiddle.isGone = tipHeaderMiddle == none
tvFooterLeft.isInvisible = tipFooterLeft != chapterTitle
bvFooterLeft.isInvisible =
tipFooterLeft == none || !tvFooterLeft.isInvisible
tvFooterRight.isGone = tipFooterRight == none
tvFooterMiddle.isGone = tipFooterMiddle == none
llHeader.isGone = when (headerMode) {
1 -> false
2 -> true
else -> !ReadBookConfig.hideStatusBar
}
llFooter.isGone = when (footerMode) {
1 -> true
else -> false
}
}
tvTitle = getTipView(ReadTipConfig.chapterTitle)
tvTitle?.apply {
isBattery = false
textSize = 12f
}
tvTime = getTipView(ReadTipConfig.time)
tvTime?.apply {
isBattery = false
textSize = 12f
}
tvBattery = getTipView(ReadTipConfig.battery)
tvBattery?.apply {
isBattery = true
textSize = 10f
}
tvPage = getTipView(ReadTipConfig.page)
tvPage?.apply {
isBattery = false
textSize = 12f
}
tvTotalProgress = getTipView(ReadTipConfig.totalProgress)
tvTotalProgress?.apply {
isBattery = false
textSize = 12f
}
tvPageAndTotal = getTipView(ReadTipConfig.pageAndTotal)
tvPageAndTotal?.apply {
isBattery = false
textSize = 12f
}
tvBookName = getTipView(ReadTipConfig.bookName)
tvBookName?.apply {
isBattery = false
textSize = 12f
}
tvTimeBattery = getTipView(ReadTipConfig.timeBattery)
tvTimeBattery?.apply {
isBattery = false
textSize = 12f
}
}
private fun getTipView(tip: Int): BatteryView? = with(binding) {
return when (tip) {
ReadTipConfig.tipHeaderLeft ->
if (tip == ReadTipConfig.chapterTitle) tvHeaderLeft else bvHeaderLeft
ReadTipConfig.tipHeaderMiddle -> tvHeaderMiddle
ReadTipConfig.tipHeaderRight -> tvHeaderRight
ReadTipConfig.tipFooterLeft ->
if (tip == ReadTipConfig.chapterTitle) tvFooterLeft else bvFooterLeft
ReadTipConfig.tipFooterMiddle -> tvFooterMiddle
ReadTipConfig.tipFooterRight -> tvFooterRight
else -> null
}
}
fun setBg(bg: Drawable?) {
binding.pagePanel.background = bg
}
fun upTime() {
tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
upTimeBattery()
}
fun upBattery(battery: Int) {
this.battery = battery
tvBattery?.setBattery(battery)
upTimeBattery()
}
@SuppressLint("SetTextI18n")
private fun upTimeBattery() {
tvTimeBattery?.let {
val time = timeFormat.format(Date(System.currentTimeMillis()))
it.text = "$time $battery%"
}
}
fun setContent(pageData: PageData, resetPageOffset: Boolean = true) {
setProgress(pageData.textPage)
if (resetPageOffset) {
resetPageOffset()
}
binding.contentTextView.setContent(pageData)
}
fun setContentDescription(content: String) {
binding.contentTextView.contentDescription = content
}
fun resetPageOffset() {
binding.contentTextView.resetPageOffset()
}
@SuppressLint("SetTextI18n")
fun setProgress(textPage: TextPage) = textPage.apply {
tvBookName?.text = ReadBook.book?.name
tvTitle?.text = textPage.title
tvPage?.text = "${index.plus(1)}/$pageSize"
tvTotalProgress?.text = readProgress
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
}
fun scroll(offset: Int) {
binding.contentTextView.scroll(offset)
}
fun upSelectAble(selectAble: Boolean) {
binding.contentTextView.selectAble = selectAble
}
fun selectText(
x: Float, y: Float,
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
) {
return binding.contentTextView.selectText(x, y - headerHeight, select)
}
fun selectStartMove(x: Float, y: Float) {
binding.contentTextView.selectStartMove(x, y - headerHeight)
}
fun selectStartMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) {
binding.contentTextView.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
fun selectEndMove(x: Float, y: Float) {
binding.contentTextView.selectEndMove(x, y - headerHeight)
}
fun selectEndMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) {
binding.contentTextView.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
fun cancelSelect() {
binding.contentTextView.cancelSelect()
}
val selectedText: String get() = binding.contentTextView.selectedText
}

@ -2,457 +2,278 @@ package io.legado.app.ui.book.read.page
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.widget.FrameLayout
import io.legado.app.help.AppConfig
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.constant.AppConst.timeFormat
import io.legado.app.databinding.ViewBookPageBinding
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor
import io.legado.app.help.ReadTipConfig
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.delegate.*
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.PageData
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.utils.activity
import io.legado.app.utils.screenshot
import kotlin.math.abs
class PageView(context: Context, attrs: AttributeSet) :
FrameLayout(context, attrs),
DataSource {
val callBack: CallBack get() = activity as CallBack
var pageFactory: TextPageFactory = TextPageFactory(this)
var pageDelegate: PageDelegate? = null
private set(value) {
field?.onDestroy()
field = null
field = value
upContent()
}
var isScroll = false
var prevPage: ContentView = ContentView(context)
var curPage: ContentView = ContentView(context)
var nextPage: ContentView = ContentView(context)
val defaultAnimationSpeed = 300
private var pressDown = false
private var isMove = false
//起始点
var startX: Float = 0f
var startY: Float = 0f
//上一个触碰点
var lastX: Float = 0f
var lastY: Float = 0f
//触碰点
var touchX: Float = 0f
var touchY: Float = 0f
//是否停止动画动作
var isAbortAnim = false
//长按
private var longPressed = false
private val longPressTimeout = 600L
private val longPressRunnable = Runnable {
longPressed = true
onLongPress()
}
var isTextSelected = false
private var pressOnTextSelected = false
private var firstRelativePage = 0
private var firstLineIndex: Int = 0
private var firstCharIndex: Int = 0
val slopSquare by lazy { ViewConfiguration.get(context).scaledTouchSlop }
private val tlRect = RectF(10F, 10F, width * 0.33f, height * 0.33f)
private val tcRect = RectF(width * 0.33f, 10F, width * 0.66f, height * 0.33f)
private val trRect = RectF(width * 0.36f, 10F, width - 10f, height * 0.33f)
private val mlRect = RectF(10F, height * 0.33f, width * 0.33f, height * 0.66f)
private val mcRect = RectF(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
private val mrRect = RectF(width * 0.66f, height * 0.33f, width - 10f, height * 0.66f)
private val blRect = RectF(10F, height * 0.66f, width * 0.33f, height - 10f)
private val bcRect = RectF(width * 0.33f, height * 0.66f, width * 0.66f, height - 10f)
private val brRect = RectF(width * 0.66f, height * 0.66f, width - 10f, height - 10f)
private val autoPageRect by lazy {
Rect()
}
private val autoPagePint by lazy {
Paint().apply {
color = context.accentColor
import io.legado.app.ui.widget.BatteryView
import io.legado.app.utils.*
import org.jetbrains.anko.topPadding
import java.util.*
/**
* 阅读界面
*/
class PageView(context: Context) : FrameLayout(context) {
private val binding = ViewBookPageBinding.inflate(LayoutInflater.from(context), this, true)
private var battery = 100
private var tvTitle: BatteryView? = null
private var tvTime: BatteryView? = null
private var tvBattery: BatteryView? = null
private var tvPage: BatteryView? = null
private var tvTotalProgress: BatteryView? = null
private var tvPageAndTotal: BatteryView? = null
private var tvBookName: BatteryView? = null
private var tvTimeBattery: BatteryView? = null
val headerHeight: Int
get() {
val h1 = if (ReadBookConfig.hideStatusBar) 0 else context.statusBarHeight
val h2 = if (binding.llHeader.isGone) 0 else binding.llHeader.height
return h1 + h2
}
}
init {
addView(nextPage)
addView(curPage)
addView(prevPage)
if (!isInEditMode) {
upBg()
setWillNotDraw(false)
upPageAnim()
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
tlRect.set(10F, 10F, width * 0.33f, height * 0.33f)
tcRect.set(width * 0.33f, 10F, width * 0.66f, height * 0.33f)
trRect.set(width * 0.36f, 10F, width - 10f, height * 0.33f)
mlRect.set(10F, height * 0.33f, width * 0.33f, height * 0.66f)
mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
mrRect.set(width * 0.66f, height * 0.33f, width - 10f, height * 0.66f)
blRect.set(10F, height * 0.66f, width * 0.33f, height - 10f)
bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height - 10f)
brRect.set(width * 0.66f, height * 0.66f, width - 10f, height - 10f)
prevPage.x = -w.toFloat()
pageDelegate?.setViewSize(w, h)
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
pageDelegate?.onDraw(canvas)
if (!isInEditMode && callBack.isAutoPage && !isScroll) {
nextPage.screenshot()?.let {
val bottom =
height * callBack.autoPageProgress / (ReadBookConfig.autoReadSpeed * 50)
autoPageRect.set(0, 0, width, bottom)
canvas.drawBitmap(it, autoPageRect, autoPageRect, null)
canvas.drawRect(
0f,
bottom.toFloat() - 1,
width.toFloat(),
bottom.toFloat(),
autoPagePint
)
//设置背景防止切换背景时文字重叠
setBackgroundColor(context.getCompatColor(R.color.background))
upTipStyle()
upStyle()
binding.contentTextView.upView = {
setProgress(it)
}
}
}
override fun computeScroll() {
pageDelegate?.scroll()
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
/**
* 触摸事件
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
callBack.screenOffTimerStart()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (isTextSelected) {
curPage.cancelSelect()
isTextSelected = false
pressOnTextSelected = true
} else {
pressOnTextSelected = false
}
longPressed = false
postDelayed(longPressRunnable, longPressTimeout)
pressDown = true
isMove = false
pageDelegate?.onTouch(event)
pageDelegate?.onDown()
setStartPoint(event.x, event.y)
}
MotionEvent.ACTION_MOVE -> {
if (!isMove) {
isMove =
abs(startX - event.x) > slopSquare || abs(startY - event.y) > slopSquare
}
if (isMove) {
longPressed = false
removeCallbacks(longPressRunnable)
if (isTextSelected) {
selectText(event.x, event.y)
} else {
pageDelegate?.onTouch(event)
}
}
fun upStyle() = with(binding) {
ChapterProvider.let {
bvHeaderLeft.typeface = it.typeface
tvHeaderLeft.typeface = it.typeface
tvHeaderMiddle.typeface = it.typeface
tvHeaderRight.typeface = it.typeface
bvFooterLeft.typeface = it.typeface
tvFooterLeft.typeface = it.typeface
tvFooterMiddle.typeface = it.typeface
tvFooterRight.typeface = it.typeface
}
ReadBookConfig.let {
val tipColor = with(ReadTipConfig) {
if (tipColor == 0) it.textColor else tipColor
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
removeCallbacks(longPressRunnable)
if (!pressDown) return true
pressDown = false
if (!isMove) {
if (!longPressed && !pressOnTextSelected) {
onSingleTapUp()
return true
}
}
if (isTextSelected) {
callBack.showTextActionMenu()
} else if (isMove) {
pageDelegate?.onTouch(event)
}
pressOnTextSelected = false
bvHeaderLeft.setColor(tipColor)
tvHeaderLeft.setColor(tipColor)
tvHeaderMiddle.setColor(tipColor)
tvHeaderRight.setColor(tipColor)
bvFooterLeft.setColor(tipColor)
tvFooterLeft.setColor(tipColor)
tvFooterMiddle.setColor(tipColor)
tvFooterRight.setColor(tipColor)
upStatusBar()
llHeader.setPadding(
it.headerPaddingLeft.dp,
it.headerPaddingTop.dp,
it.headerPaddingRight.dp,
it.headerPaddingBottom.dp
)
llFooter.setPadding(
it.footerPaddingLeft.dp,
it.footerPaddingTop.dp,
it.footerPaddingRight.dp,
it.footerPaddingBottom.dp
)
vwTopDivider.visible(it.showHeaderLine)
vwBottomDivider.visible(it.showFooterLine)
pageNvBar.layoutParams = pageNvBar.layoutParams.apply {
height = if (it.hideNavigationBar) 0 else App.navigationBarHeight
}
}
return true
}
fun upStatusBar() {
curPage.upStatusBar()
prevPage.upStatusBar()
nextPage.upStatusBar()
}
/**
* 保存开始位置
*/
fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) {
startX = x
startY = y
lastX = x
lastY = y
touchX = x
touchY = y
if (invalidate) {
invalidate()
}
contentTextView.upVisibleRect()
upTime()
upBattery(battery)
}
/**
* 保存当前位置
* 显示状态栏时隐藏header
*/
fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) {
lastX = touchX
lastY = touchY
touchX = x
touchY = y
if (invalidate) {
invalidate()
}
pageDelegate?.onScroll()
}
/**
* 长按选择
*/
private fun onLongPress() {
curPage.selectText(startX, startY) { relativePage, lineIndex, charIndex ->
isTextSelected = true
firstRelativePage = relativePage
firstLineIndex = lineIndex
firstCharIndex = charIndex
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
}
fun upStatusBar() {
binding.vwStatusBar.topPadding = context.statusBarHeight
binding.vwStatusBar.isGone =
ReadBookConfig.hideStatusBar || (activity as? BaseActivity<*>)?.isInMultiWindow == true
}
/**
* 单击
*/
private fun onSingleTapUp() {
when {
isTextSelected -> isTextSelected = false
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
click(AppConfig.clickActionMC)
fun upTipStyle() = with(binding) {
ReadTipConfig.apply {
tvHeaderLeft.isInvisible = tipHeaderLeft != chapterTitle
bvHeaderLeft.isInvisible =
tipHeaderLeft == none || !tvHeaderLeft.isInvisible
tvHeaderRight.isGone = tipHeaderRight == none
tvHeaderMiddle.isGone = tipHeaderMiddle == none
tvFooterLeft.isInvisible = tipFooterLeft != chapterTitle
bvFooterLeft.isInvisible =
tipFooterLeft == none || !tvFooterLeft.isInvisible
tvFooterRight.isGone = tipFooterRight == none
tvFooterMiddle.isGone = tipFooterMiddle == none
llHeader.isGone = when (headerMode) {
1 -> false
2 -> true
else -> !ReadBookConfig.hideStatusBar
}
bcRect.contains(startX, startY) -> {
click(AppConfig.clickActionBC)
}
blRect.contains(startX, startY) -> {
click(AppConfig.clickActionBL)
}
brRect.contains(startX, startY) -> {
click(AppConfig.clickActionBR)
}
mlRect.contains(startX, startY) -> {
click(AppConfig.clickActionML)
}
mrRect.contains(startX, startY) -> {
click(AppConfig.clickActionMR)
}
tlRect.contains(startX, startY) -> {
click(AppConfig.clickActionTL)
}
tcRect.contains(startX, startY) -> {
click(AppConfig.clickActionTC)
}
trRect.contains(startX, startY) -> {
click(AppConfig.clickActionTR)
llFooter.isGone = when (footerMode) {
1 -> true
else -> false
}
}
tvTitle = getTipView(ReadTipConfig.chapterTitle)
tvTitle?.apply {
isBattery = false
textSize = 12f
}
tvTime = getTipView(ReadTipConfig.time)
tvTime?.apply {
isBattery = false
textSize = 12f
}
tvBattery = getTipView(ReadTipConfig.battery)
tvBattery?.apply {
isBattery = true
textSize = 10f
}
tvPage = getTipView(ReadTipConfig.page)
tvPage?.apply {
isBattery = false
textSize = 12f
}
tvTotalProgress = getTipView(ReadTipConfig.totalProgress)
tvTotalProgress?.apply {
isBattery = false
textSize = 12f
}
tvPageAndTotal = getTipView(ReadTipConfig.pageAndTotal)
tvPageAndTotal?.apply {
isBattery = false
textSize = 12f
}
tvBookName = getTipView(ReadTipConfig.bookName)
tvBookName?.apply {
isBattery = false
textSize = 12f
}
tvTimeBattery = getTipView(ReadTipConfig.timeBattery)
tvTimeBattery?.apply {
isBattery = false
textSize = 12f
}
}
private fun click(action: Int) {
when (action) {
0 -> callBack.showActionMenu()
1 -> pageDelegate?.nextPageByAnim(defaultAnimationSpeed)
2 -> pageDelegate?.prevPageByAnim(defaultAnimationSpeed)
3 -> ReadBook.moveToNextChapter(true)
4 -> ReadBook.moveToPrevChapter(upContent = true, toLast = false)
private fun getTipView(tip: Int): BatteryView? = with(binding) {
return when (tip) {
ReadTipConfig.tipHeaderLeft ->
if (tip == ReadTipConfig.chapterTitle) tvHeaderLeft else bvHeaderLeft
ReadTipConfig.tipHeaderMiddle -> tvHeaderMiddle
ReadTipConfig.tipHeaderRight -> tvHeaderRight
ReadTipConfig.tipFooterLeft ->
if (tip == ReadTipConfig.chapterTitle) tvFooterLeft else bvFooterLeft
ReadTipConfig.tipFooterMiddle -> tvFooterMiddle
ReadTipConfig.tipFooterRight -> tvFooterRight
else -> null
}
}
/**
* 选择文本
*/
private fun selectText(x: Float, y: Float) {
curPage.selectText(x, y) { relativePage, lineIndex, charIndex ->
when {
relativePage > firstRelativePage -> {
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
relativePage < firstRelativePage -> {
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
lineIndex > firstLineIndex -> {
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
lineIndex < firstLineIndex -> {
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
charIndex > firstCharIndex -> {
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
else -> {
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
}
}
fun setBg(bg: Drawable?) {
binding.pagePanel.background = bg
}
fun onDestroy() {
pageDelegate?.onDestroy()
curPage.cancelSelect()
fun upTime() {
tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
upTimeBattery()
}
fun fillPage(direction: PageDirection) {
when (direction) {
PageDirection.PREV -> {
pageFactory.moveToPrev(true)
}
PageDirection.NEXT -> {
pageFactory.moveToNext(true)
}
else -> Unit
}
fun upBattery(battery: Int) {
this.battery = battery
tvBattery?.setBattery(battery)
upTimeBattery()
}
fun upPageAnim() {
isScroll = ReadBook.pageAnim() == 3
when (ReadBook.pageAnim()) {
0 -> if (pageDelegate !is CoverPageDelegate) {
pageDelegate = CoverPageDelegate(this)
}
1 -> if (pageDelegate !is SlidePageDelegate) {
pageDelegate = SlidePageDelegate(this)
}
2 -> if (pageDelegate !is SimulationPageDelegate) {
pageDelegate = SimulationPageDelegate(this)
}
3 -> if (pageDelegate !is ScrollPageDelegate) {
pageDelegate = ScrollPageDelegate(this)
}
else -> if (pageDelegate !is NoAnimPageDelegate) {
pageDelegate = NoAnimPageDelegate(this)
}
@SuppressLint("SetTextI18n")
private fun upTimeBattery() {
tvTimeBattery?.let {
val time = timeFormat.format(Date(System.currentTimeMillis()))
it.text = "$time $battery%"
}
}
override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {
curPage.setContentDescription(pageFactory.curData.textPage.text)
if (isScroll && !callBack.isAutoPage) {
curPage.setContent(pageFactory.curData, resetPageOffset)
} else {
curPage.resetPageOffset()
when (relativePosition) {
-1 -> prevPage.setContent(pageFactory.prevData)
1 -> nextPage.setContent(pageFactory.nextData)
else -> {
curPage.setContent(pageFactory.curData)
nextPage.setContent(pageFactory.nextData)
prevPage.setContent(pageFactory.prevData)
}
}
fun setContent(pageData: PageData, resetPageOffset: Boolean = true) {
setProgress(pageData.textPage)
if (resetPageOffset) {
resetPageOffset()
}
callBack.screenOffTimerStart()
binding.contentTextView.setContent(pageData)
}
fun upTipStyle() {
curPage.upTipStyle()
prevPage.upTipStyle()
nextPage.upTipStyle()
fun setContentDescription(content: String) {
binding.contentTextView.contentDescription = content
}
fun upStyle() {
ChapterProvider.upStyle()
curPage.upStyle()
prevPage.upStyle()
nextPage.upStyle()
fun resetPageOffset() {
binding.contentTextView.resetPageOffset()
}
fun upBg() {
ReadBookConfig.bg ?: let {
ReadBookConfig.upBg()
}
curPage.setBg(ReadBookConfig.bg)
prevPage.setBg(ReadBookConfig.bg)
nextPage.setBg(ReadBookConfig.bg)
@SuppressLint("SetTextI18n")
fun setProgress(textPage: TextPage) = textPage.apply {
tvBookName?.text = ReadBook.book?.name
tvTitle?.text = textPage.title
tvPage?.text = "${index.plus(1)}/$pageSize"
tvTotalProgress?.text = readProgress
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
}
fun upTime() {
curPage.upTime()
prevPage.upTime()
nextPage.upTime()
fun scroll(offset: Int) {
binding.contentTextView.scroll(offset)
}
fun upBattery(battery: Int) {
curPage.upBattery(battery)
prevPage.upBattery(battery)
nextPage.upBattery(battery)
fun upSelectAble(selectAble: Boolean) {
binding.contentTextView.selectAble = selectAble
}
override val currentChapter: TextChapter?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
}
fun selectText(
x: Float, y: Float,
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
) {
return binding.contentTextView.selectText(x, y - headerHeight, select)
}
override val nextChapter: TextChapter?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(1) else null
}
fun selectStartMove(x: Float, y: Float) {
binding.contentTextView.selectStartMove(x, y - headerHeight)
}
override val prevChapter: TextChapter?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(-1) else null
}
fun selectStartMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) {
binding.contentTextView.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
override fun hasNextChapter(): Boolean {
return ReadBook.durChapterIndex < ReadBook.chapterSize - 1
fun selectEndMove(x: Float, y: Float) {
binding.contentTextView.selectEndMove(x, y - headerHeight)
}
override fun hasPrevChapter(): Boolean {
return ReadBook.durChapterIndex > 0
fun selectEndMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) {
binding.contentTextView.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
interface CallBack {
val isInitFinish: Boolean
val isAutoPage: Boolean
val autoPageProgress: Int
fun showActionMenu()
fun screenOffTimerStart()
fun showTextActionMenu()
fun cancelSelect() {
binding.contentTextView.cancelSelect()
}
}
val selectedText: String get() = binding.contentTextView.selectedText
}

@ -0,0 +1,460 @@
package io.legado.app.ui.book.read.page
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.widget.FrameLayout
import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor
import io.legado.app.service.help.ReadBook
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.entities.PageDirection
import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.utils.activity
import io.legado.app.utils.screenshot
import kotlin.math.abs
class ReadView(context: Context, attrs: AttributeSet) :
FrameLayout(context, attrs),
DataSource {
val callBack: CallBack get() = activity as CallBack
var pageFactory: TextPageFactory = TextPageFactory(this)
var pageDelegate: PageDelegate? = null
private set(value) {
field?.onDestroy()
field = null
field = value
upContent()
}
var isScroll = false
var prevPage: PageView = PageView(context)
var curPage: PageView = PageView(context)
var nextPage: PageView = PageView(context)
val defaultAnimationSpeed = 300
private var pressDown = false
private var isMove = false
//起始点
var startX: Float = 0f
var startY: Float = 0f
//上一个触碰点
var lastX: Float = 0f
var lastY: Float = 0f
//触碰点
var touchX: Float = 0f
var touchY: Float = 0f
//是否停止动画动作
var isAbortAnim = false
//长按
private var longPressed = false
private val longPressTimeout = 600L
private val longPressRunnable = Runnable {
longPressed = true
onLongPress()
}
var isTextSelected = false
private var pressOnTextSelected = false
private var firstRelativePage = 0
private var firstLineIndex: Int = 0
private var firstCharIndex: Int = 0
val slopSquare by lazy { ViewConfiguration.get(context).scaledTouchSlop }
private val tlRect = RectF(10F, 10F, width * 0.33f, height * 0.33f)
private val tcRect = RectF(width * 0.33f, 10F, width * 0.66f, height * 0.33f)
private val trRect = RectF(width * 0.36f, 10F, width - 10f, height * 0.33f)
private val mlRect = RectF(10F, height * 0.33f, width * 0.33f, height * 0.66f)
private val mcRect = RectF(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
private val mrRect = RectF(width * 0.66f, height * 0.33f, width - 10f, height * 0.66f)
private val blRect = RectF(10F, height * 0.66f, width * 0.33f, height - 10f)
private val bcRect = RectF(width * 0.33f, height * 0.66f, width * 0.66f, height - 10f)
private val brRect = RectF(width * 0.66f, height * 0.66f, width - 10f, height - 10f)
private val autoPageRect by lazy {
Rect()
}
private val autoPagePint by lazy {
Paint().apply {
color = context.accentColor
}
}
init {
addView(nextPage)
addView(curPage)
addView(prevPage)
if (!isInEditMode) {
upBg()
setWillNotDraw(false)
upPageAnim()
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
tlRect.set(10F, 10F, width * 0.33f, height * 0.33f)
tcRect.set(width * 0.33f, 10F, width * 0.66f, height * 0.33f)
trRect.set(width * 0.36f, 10F, width - 10f, height * 0.33f)
mlRect.set(10F, height * 0.33f, width * 0.33f, height * 0.66f)
mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
mrRect.set(width * 0.66f, height * 0.33f, width - 10f, height * 0.66f)
blRect.set(10F, height * 0.66f, width * 0.33f, height - 10f)
bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height - 10f)
brRect.set(width * 0.66f, height * 0.66f, width - 10f, height - 10f)
prevPage.x = -w.toFloat()
pageDelegate?.setViewSize(w, h)
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
pageDelegate?.onDraw(canvas)
if (!isInEditMode && callBack.isAutoPage && !isScroll) {
nextPage.screenshot()?.let {
val bottom =
height * callBack.autoPageProgress / (ReadBookConfig.autoReadSpeed * 50)
autoPageRect.set(0, 0, width, bottom)
canvas.drawBitmap(it, autoPageRect, autoPageRect, null)
canvas.drawRect(
0f,
bottom.toFloat() - 1,
width.toFloat(),
bottom.toFloat(),
autoPagePint
)
}
}
}
override fun computeScroll() {
pageDelegate?.scroll()
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
/**
* 触摸事件
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
callBack.screenOffTimerStart()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (isTextSelected) {
curPage.cancelSelect()
isTextSelected = false
pressOnTextSelected = true
} else {
pressOnTextSelected = false
}
longPressed = false
postDelayed(longPressRunnable, longPressTimeout)
pressDown = true
isMove = false
pageDelegate?.onTouch(event)
pageDelegate?.onDown()
setStartPoint(event.x, event.y)
}
MotionEvent.ACTION_MOVE -> {
if (!isMove) {
isMove =
abs(startX - event.x) > slopSquare || abs(startY - event.y) > slopSquare
}
if (isMove) {
longPressed = false
removeCallbacks(longPressRunnable)
if (isTextSelected) {
selectText(event.x, event.y)
} else {
pageDelegate?.onTouch(event)
}
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
removeCallbacks(longPressRunnable)
if (!pressDown) return true
pressDown = false
if (!isMove) {
if (!longPressed && !pressOnTextSelected) {
onSingleTapUp()
return true
}
}
if (isTextSelected) {
callBack.showTextActionMenu()
} else if (isMove) {
pageDelegate?.onTouch(event)
}
pressOnTextSelected = false
}
}
return true
}
fun upStatusBar() {
curPage.upStatusBar()
prevPage.upStatusBar()
nextPage.upStatusBar()
}
/**
* 保存开始位置
*/
fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) {
startX = x
startY = y
lastX = x
lastY = y
touchX = x
touchY = y
if (invalidate) {
invalidate()
}
}
/**
* 保存当前位置
*/
fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) {
lastX = touchX
lastY = touchY
touchX = x
touchY = y
if (invalidate) {
invalidate()
}
pageDelegate?.onScroll()
}
/**
* 长按选择
*/
private fun onLongPress() {
curPage.selectText(startX, startY) { relativePage, lineIndex, charIndex ->
isTextSelected = true
firstRelativePage = relativePage
firstLineIndex = lineIndex
firstCharIndex = charIndex
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
}
}
/**
* 单击
*/
private fun onSingleTapUp() {
when {
isTextSelected -> isTextSelected = false
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
click(AppConfig.clickActionMC)
}
bcRect.contains(startX, startY) -> {
click(AppConfig.clickActionBC)
}
blRect.contains(startX, startY) -> {
click(AppConfig.clickActionBL)
}
brRect.contains(startX, startY) -> {
click(AppConfig.clickActionBR)
}
mlRect.contains(startX, startY) -> {
click(AppConfig.clickActionML)
}
mrRect.contains(startX, startY) -> {
click(AppConfig.clickActionMR)
}
tlRect.contains(startX, startY) -> {
click(AppConfig.clickActionTL)
}
tcRect.contains(startX, startY) -> {
click(AppConfig.clickActionTC)
}
trRect.contains(startX, startY) -> {
click(AppConfig.clickActionTR)
}
}
}
private fun click(action: Int) {
when (action) {
0 -> callBack.showActionMenu()
1 -> pageDelegate?.nextPageByAnim(defaultAnimationSpeed)
2 -> pageDelegate?.prevPageByAnim(defaultAnimationSpeed)
3 -> ReadBook.moveToNextChapter(true)
4 -> ReadBook.moveToPrevChapter(upContent = true, toLast = false)
}
}
/**
* 选择文本
*/
private fun selectText(x: Float, y: Float) {
curPage.selectText(x, y) { relativePage, lineIndex, charIndex ->
when {
relativePage > firstRelativePage -> {
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
relativePage < firstRelativePage -> {
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
lineIndex > firstLineIndex -> {
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
lineIndex < firstLineIndex -> {
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
charIndex > firstCharIndex -> {
curPage.selectStartMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(relativePage, lineIndex, charIndex)
}
else -> {
curPage.selectEndMoveIndex(firstRelativePage, firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(relativePage, lineIndex, charIndex)
}
}
}
}
fun onDestroy() {
pageDelegate?.onDestroy()
curPage.cancelSelect()
}
fun fillPage(direction: PageDirection) {
when (direction) {
PageDirection.PREV -> {
pageFactory.moveToPrev(true)
}
PageDirection.NEXT -> {
pageFactory.moveToNext(true)
}
else -> Unit
}
}
fun upPageAnim() {
isScroll = ReadBook.pageAnim() == 3
when (ReadBook.pageAnim()) {
0 -> if (pageDelegate !is CoverPageDelegate) {
pageDelegate = CoverPageDelegate(this)
}
1 -> if (pageDelegate !is SlidePageDelegate) {
pageDelegate = SlidePageDelegate(this)
}
2 -> if (pageDelegate !is SimulationPageDelegate) {
pageDelegate = SimulationPageDelegate(this)
}
3 -> if (pageDelegate !is ScrollPageDelegate) {
pageDelegate = ScrollPageDelegate(this)
}
else -> if (pageDelegate !is NoAnimPageDelegate) {
pageDelegate = NoAnimPageDelegate(this)
}
}
}
override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {
curPage.setContentDescription(pageFactory.curData.textPage.text)
if (isScroll && !callBack.isAutoPage) {
curPage.setContent(pageFactory.curData, resetPageOffset)
} else {
curPage.resetPageOffset()
when (relativePosition) {
-1 -> prevPage.setContent(pageFactory.prevData)
1 -> nextPage.setContent(pageFactory.nextData)
else -> {
curPage.setContent(pageFactory.curData)
nextPage.setContent(pageFactory.nextData)
prevPage.setContent(pageFactory.prevData)
}
}
}
callBack.screenOffTimerStart()
}
fun upTipStyle() {
curPage.upTipStyle()
prevPage.upTipStyle()
nextPage.upTipStyle()
}
fun upStyle() {
ChapterProvider.upStyle()
curPage.upStyle()
prevPage.upStyle()
nextPage.upStyle()
}
fun upBg() {
ReadBookConfig.bg ?: let {
ReadBookConfig.upBg()
}
curPage.setBg(ReadBookConfig.bg)
prevPage.setBg(ReadBookConfig.bg)
nextPage.setBg(ReadBookConfig.bg)
}
fun upTime() {
curPage.upTime()
prevPage.upTime()
nextPage.upTime()
}
fun upBattery(battery: Int) {
curPage.upBattery(battery)
prevPage.upBattery(battery)
nextPage.upBattery(battery)
}
override val currentChapter: TextChapter?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
}
override val nextChapter: TextChapter?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(1) else null
}
override val prevChapter: TextChapter?
get() {
return if (callBack.isInitFinish) ReadBook.textChapter(-1) else null
}
override fun hasNextChapter(): Boolean {
return ReadBook.durChapterIndex < ReadBook.chapterSize - 1
}
override fun hasPrevChapter(): Boolean {
return ReadBook.durChapterIndex > 0
}
interface CallBack {
val isInitFinish: Boolean
val isAutoPage: Boolean
val autoPageProgress: Int
fun showActionMenu()
fun screenOffTimerStart()
fun showTextActionMenu()
}
}

@ -1,4 +1,4 @@
package io.legado.app.ui.book.read.page
package io.legado.app.ui.book.read.page.api
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChapter

@ -1,4 +1,4 @@
package io.legado.app.ui.book.read.page
package io.legado.app.ui.book.read.page.api
abstract class PageFactory<DATA>(protected val dataSource: DataSource) {

@ -3,10 +3,10 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection
class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
class CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
private val bitmapMatrix = Matrix()
private val shadowDrawableR: GradientDrawable
@ -54,7 +54,7 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
override fun onAnimStop() {
if (!isCancel) {
pageView.fillPage(mDirection)
readView.fillPage(mDirection)
}
}

@ -2,11 +2,11 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Bitmap
import android.view.MotionEvent
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.screenshot
abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) {
abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readView) {
protected var curBitmap: Bitmap? = null
protected var prevBitmap: Bitmap? = null
@ -44,7 +44,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
onScroll(event)
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
onAnimStart(pageView.defaultAnimationSpeed)
onAnimStart(readView.defaultAnimationSpeed)
}
}
}
@ -72,7 +72,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
val deltaX = (focusX - startX).toInt()
val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > pageView.slopSquare
isMoved = distance > readView.slopSquare
if (isMoved) {
if (sumX - startX > 0) {
//如果上一页不存在
@ -95,7 +95,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
isCancel = if (mDirection == PageDirection.NEXT) sumX > lastX else sumX < lastX
isRunning = true
//设置触摸点
pageView.setTouchPoint(sumX, sumY)
readView.setTouchPoint(sumX, sumY)
}
}
@ -104,14 +104,14 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
isMoved = false
isRunning = false
if (!scroller.isFinished) {
pageView.isAbortAnim = true
readView.isAbortAnim = true
scroller.abortAnimation()
if (!isCancel) {
pageView.fillPage(mDirection)
pageView.invalidate()
readView.fillPage(mDirection)
readView.invalidate()
}
} else {
pageView.isAbortAnim = false
readView.isAbortAnim = false
}
}
@ -119,7 +119,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
abortAnim()
if (!hasNext()) return
setDirection(PageDirection.NEXT)
pageView.setTouchPoint(viewWidth.toFloat(), 0f, false)
readView.setTouchPoint(viewWidth.toFloat(), 0f, false)
onAnimStart(animationSpeed)
}
@ -127,7 +127,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
abortAnim()
if (!hasPrev()) return
setDirection(PageDirection.PREV)
pageView.setTouchPoint(0f, 0f)
readView.setTouchPoint(0f, 0f)
onAnimStart(animationSpeed)
}

@ -1,13 +1,13 @@
package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
class NoAnimPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
class NoAnimPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
override fun onAnimStart(animationSpeed: Int) {
if (!isCancel) {
pageView.fillPage(mDirection)
readView.fillPage(mDirection)
}
stopScroll()
}

@ -8,40 +8,40 @@ import android.widget.Scroller
import androidx.annotation.CallSuper
import com.google.android.material.snackbar.Snackbar
import io.legado.app.R
import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection
import kotlin.math.abs
abstract class PageDelegate(protected val pageView: PageView) {
abstract class PageDelegate(protected val readView: ReadView) {
protected val context: Context = pageView.context
protected val context: Context = readView.context
//起始点
protected val startX: Float get() = pageView.startX
protected val startY: Float get() = pageView.startY
protected val startX: Float get() = readView.startX
protected val startY: Float get() = readView.startY
//上一个触碰点
protected val lastX: Float get() = pageView.lastX
protected val lastY: Float get() = pageView.lastY
protected val lastX: Float get() = readView.lastX
protected val lastY: Float get() = readView.lastY
//触碰点
protected val touchX: Float get() = pageView.touchX
protected val touchY: Float get() = pageView.touchY
protected val touchX: Float get() = readView.touchX
protected val touchY: Float get() = readView.touchY
protected val nextPage: ContentView get() = pageView.nextPage
protected val curPage: ContentView get() = pageView.curPage
protected val prevPage: ContentView get() = pageView.prevPage
protected val nextPage: PageView get() = readView.nextPage
protected val curPage: PageView get() = readView.curPage
protected val prevPage: PageView get() = readView.prevPage
protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height
protected var viewWidth: Int = readView.width
protected var viewHeight: Int = readView.height
protected val scroller: Scroller by lazy {
Scroller(pageView.context, DecelerateInterpolator())
Scroller(readView.context, DecelerateInterpolator())
}
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
Snackbar.make(readView, "", Snackbar.LENGTH_SHORT)
}
var isMoved = false
@ -66,7 +66,7 @@ abstract class PageDelegate(protected val pageView: PageView) {
scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY)
isRunning = true
isStarted = true
pageView.invalidate()
readView.invalidate()
}
protected fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, animationSpeed: Int) {
@ -78,15 +78,15 @@ abstract class PageDelegate(protected val pageView: PageView) {
scroller.startScroll(startX, startY, dx, dy, duration)
isRunning = true
isStarted = true
pageView.invalidate()
readView.invalidate()
}
protected fun stopScroll() {
isStarted = false
pageView.post {
readView.post {
isMoved = false
isRunning = false
pageView.invalidate()
readView.invalidate()
}
}
@ -97,7 +97,7 @@ abstract class PageDelegate(protected val pageView: PageView) {
fun scroll() {
if (scroller.computeScrollOffset()) {
pageView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat())
readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat())
} else if (isStarted) {
onAnimStop()
stopScroll()
@ -157,7 +157,7 @@ abstract class PageDelegate(protected val pageView: PageView) {
* 判断是否有上一页
*/
fun hasPrev(): Boolean {
val hasPrev = pageView.pageFactory.hasPrev()
val hasPrev = readView.pageFactory.hasPrev()
if (!hasPrev) {
if (!snackBar.isShown) {
snackBar.setText(R.string.no_prev_page)
@ -171,7 +171,7 @@ abstract class PageDelegate(protected val pageView: PageView) {
* 判断是否有下一页
*/
fun hasNext(): Boolean {
val hasNext = pageView.pageFactory.hasNext()
val hasNext = readView.pageFactory.hasNext()
if (!hasNext) {
if (!snackBar.isShown) {
snackBar.setText(R.string.no_next_page)

@ -3,13 +3,14 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import android.view.MotionEvent
import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.provider.ChapterProvider
class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
class ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {
// 滑动追踪的时间
private val velocityDuration = 1000
//速度追踪器
private val mVelocity: VelocityTracker = VelocityTracker.obtain()
@ -35,7 +36,7 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
onScroll(event)
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
onAnimStart(pageView.defaultAnimationSpeed)
onAnimStart(readView.defaultAnimationSpeed)
}
}
}
@ -67,12 +68,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
val div = if (pointerUp) count - 1 else count
val focusX = sumX / div
val focusY = sumY / div
pageView.setTouchPoint(sumX, sumY)
readView.setTouchPoint(sumX, sumY)
if (!isMoved) {
val deltaX = (focusX - startX).toInt()
val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > pageView.slopSquare
isMoved = distance > readView.slopSquare
}
if (isMoved) {
isRunning = true
@ -89,26 +90,26 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
isMoved = false
isRunning = false
if (!scroller.isFinished) {
pageView.isAbortAnim = true
readView.isAbortAnim = true
scroller.abortAnimation()
} else {
pageView.isAbortAnim = false
readView.isAbortAnim = false
}
}
override fun nextPageByAnim(animationSpeed: Int) {
if (pageView.isAbortAnim) {
if (readView.isAbortAnim) {
return
}
pageView.setStartPoint(0f, 0f, false)
readView.setStartPoint(0f, 0f, false)
startScroll(0, 0, 0, -ChapterProvider.visibleHeight, animationSpeed)
}
override fun prevPageByAnim(animationSpeed: Int) {
if (pageView.isAbortAnim) {
if (readView.isAbortAnim) {
return
}
pageView.setStartPoint(0f, 0f, false)
readView.setStartPoint(0f, 0f, false)
startScroll(0, 0, 0, ChapterProvider.visibleHeight, animationSpeed)
}
}

@ -5,20 +5,22 @@ import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.view.MotionEvent
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection
import kotlin.math.*
@Suppress("DEPRECATION")
class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
class SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
//不让x,y为0,否则在点计算时会有问题
private var mTouchX = 0.1f
private var mTouchY = 0.1f
// 拖拽点对应的页脚
private var mCornerX = 1
private var mCornerY = 1
private val mPath0: Path = Path()
private val mPath1: Path = Path()
// 贝塞尔曲线起始点
private val mBezierStart1 = PointF()
// 贝塞尔曲线控制点
@ -126,13 +128,13 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3)
|| mDirection == PageDirection.PREV
) {
pageView.touchY = viewHeight.toFloat()
readView.touchY = viewHeight.toFloat()
}
if (startY > viewHeight / 3 && startY < viewHeight / 2
&& mDirection == PageDirection.NEXT
) {
pageView.touchY = 1f
readView.touchY = 1f
}
}
}
@ -191,7 +193,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
override fun onAnimStop() {
if (!isCancel) {
pageView.fillPage(mDirection)
readView.fillPage(mDirection)
}
}

@ -2,10 +2,10 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import android.graphics.Matrix
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.entities.PageDirection
class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
class SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {
private val bitmapMatrix = Matrix()
@ -55,7 +55,7 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
override fun onAnimStop() {
if (!isCancel) {
pageView.fillPage(mDirection)
readView.fillPage(mDirection)
}
}
}

@ -1,6 +1,8 @@
package io.legado.app.ui.book.read.page
package io.legado.app.ui.book.read.page.provider
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.api.DataSource
import io.legado.app.ui.book.read.page.api.PageFactory
import io.legado.app.ui.book.read.page.entities.PageData
import io.legado.app.ui.book.read.page.entities.TextPage

@ -4,8 +4,8 @@
android:layout_height="match_parent"
android:orientation="vertical">
<io.legado.app.ui.book.read.page.PageView
android:id="@+id/page_view"
<io.legado.app.ui.book.read.page.ReadView
android:id="@+id/read_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

Loading…
Cancel
Save