Fix webtoon scroll dispatching

This commit is contained in:
Koitharu
2024-01-24 11:26:29 +02:00
parent 5d9a59d577
commit a0a72b1192
5 changed files with 50 additions and 41 deletions

View File

@@ -263,7 +263,7 @@ class ReaderViewModel @Inject constructor(
}
@MainThread
fun onCurrentPageChanged(position: Int) {
fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {
val prevJob = stateChangeJob
val pages = content.value.pages // capture immediately
stateChangeJob = launchJob(Dispatchers.Default) {
@@ -272,7 +272,8 @@ class ReaderViewModel @Inject constructor(
if (pages.size != content.value.pages.size) {
return@launchJob // TODO
}
pages.getOrNull(position)?.let { page ->
val centerPos = (lowerPos + upperPos) / 2
pages.getOrNull(centerPos)?.let { page ->
currentState.update { cs ->
cs?.copy(chapterId = page.chapterId, page = page.index)
}
@@ -282,14 +283,14 @@ class ReaderViewModel @Inject constructor(
return@launchJob
}
ensureActive()
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.last().chapterId, isNext = true)
}
if (position <= BOUNDS_PAGE_OFFSET) {
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.first().chapterId, isNext = false)
}
if (pageLoader.isPrefetchApplicable()) {
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))
}
}
}

View File

@@ -179,7 +179,7 @@ abstract class BasePagerReaderFragment : BaseReaderFragment<FragmentReaderPagerB
}
protected open fun notifyPageChanged(page: Int) {
viewModel.onCurrentPageChanged(page)
viewModel.onCurrentPageChanged(page, page)
}
companion object {

View File

@@ -32,7 +32,8 @@ class ReversedReaderFragment : BasePagerReaderFragment() {
}
override fun notifyPageChanged(page: Int) {
viewModel.onCurrentPageChanged(reversed(page))
val pos = reversed(page)
viewModel.onCurrentPageChanged(pos, pos)
}
private fun reversed(position: Int): Int {

View File

@@ -24,7 +24,8 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import javax.inject.Inject
@AndroidEntryPoint
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>() {
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),
WebtoonRecyclerView.OnWebtoonScrollListener {
@Inject
lateinit var networkState: NetworkState
@@ -46,7 +47,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
with(binding.recyclerView) {
setHasFixedSize(true)
adapter = readerAdapter
addOnPageScrollListener(PageScrollListener())
addOnPageScrollListener(this@WebtoonReaderFragment)
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
addOnScrollListener(it)
}
@@ -70,6 +71,15 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
exceptionResolver = exceptionResolver,
)
override fun onScrollChanged(
recyclerView: WebtoonRecyclerView,
dy: Int,
firstVisiblePosition: Int,
lastVisiblePosition: Int,
) {
viewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition)
}
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val setItems = launch {
requireAdapter().setItems(pages)
@@ -91,7 +101,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
?.restoreScroll(pendingState.scroll)
}
}
notifyPageChanged(position)
viewModel.onCurrentPageChanged(position, position)
} else {
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
@@ -121,10 +131,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
viewBinding?.frame?.onZoomOut()
}
private fun notifyPageChanged(page: Int) {
viewModel.onCurrentPageChanged(page)
}
override fun switchPageBy(delta: Int) {
with(requireViewBinding().recyclerView) {
if (isAnimationEnabled()) {
@@ -147,12 +153,4 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
}
return true
}
private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() {
override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) {
super.onPageChanged(recyclerView, index)
notifyPageChanged(index)
}
}
}

View File

@@ -6,8 +6,8 @@ import android.view.View
import androidx.core.view.ViewCompat.TYPE_TOUCH
import androidx.core.view.forEach
import androidx.core.view.iterator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
import java.util.LinkedList
import java.util.WeakHashMap
@@ -15,7 +15,8 @@ class WebtoonRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
private var onPageScrollListeners = LinkedList<OnWebtoonScrollListener>()
private val scrollDispatcher = WebtoonScrollDispatcher()
private val detachedViews = WeakHashMap<View, Unit>()
private var isFixingScroll: Boolean = false
@@ -103,22 +104,20 @@ class WebtoonRecyclerView @JvmOverloads constructor(
return 0
}
fun addOnPageScrollListener(listener: OnPageScrollListener) {
val list = onPageScrollListeners ?: LinkedList<OnPageScrollListener>().also { onPageScrollListeners = it }
list.add(listener)
fun addOnPageScrollListener(listener: OnWebtoonScrollListener) {
onPageScrollListeners.add(listener)
}
fun removeOnPageScrollListener(listener: OnPageScrollListener) {
onPageScrollListeners?.remove(listener)
fun removeOnPageScrollListener(listener: OnWebtoonScrollListener) {
onPageScrollListeners.remove(listener)
}
private fun notifyScrollChanged(dy: Int) {
val listeners = onPageScrollListeners
if (listeners.isNullOrEmpty()) {
if (listeners.isEmpty()) {
return
}
val centerPosition = findCenterViewPosition()
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
scrollDispatcher.dispatchScroll(this, dy)
}
fun relayoutChildren() {
@@ -162,20 +161,30 @@ class WebtoonRecyclerView @JvmOverloads constructor(
else -> false
}
abstract class OnPageScrollListener {
private class WebtoonScrollDispatcher {
private var lastPosition = NO_POSITION
private var firstPos = NO_POSITION
private var lastPos = NO_POSITION
fun dispatchScroll(recyclerView: WebtoonRecyclerView, dy: Int, centerPosition: Int) {
onScroll(recyclerView, dy)
if (centerPosition != NO_POSITION && centerPosition != lastPosition) {
lastPosition = centerPosition
onPageChanged(recyclerView, centerPosition)
fun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) {
val lm = rv.layoutManager as? LinearLayoutManager
if (lm == null) {
firstPos = NO_POSITION
lastPos = NO_POSITION
return
}
val newFirstPos = lm.findFirstVisibleItemPosition()
val newLastPos = lm.findLastVisibleItemPosition()
if (newFirstPos != firstPos || newLastPos != lastPos) {
firstPos = newFirstPos
lastPos = newLastPos
rv.onPageScrollListeners.forEach { it.onScrollChanged(rv, dy, newFirstPos, newLastPos) }
}
}
}
open fun onScroll(recyclerView: WebtoonRecyclerView, dy: Int) = Unit
interface OnWebtoonScrollListener {
open fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) = Unit
fun onScrollChanged(recyclerView: WebtoonRecyclerView, dy: Int, firstVisiblePosition: Int, lastVisiblePosition: Int)
}
}