Compare commits
1 Commits
v6.8.2
...
feature/do
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5df55d1fe9 |
@@ -13,11 +13,11 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -103,7 +103,7 @@ class ReaderActivity :
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityReaderBinding.inflate(layoutInflater))
|
||||
readerManager = ReaderManager(supportFragmentManager, R.id.container)
|
||||
readerManager = ReaderManager(supportFragmentManager, viewBinding.container)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
touchHelper = GridTouchHelper(this, this)
|
||||
scrollTimer = scrollTimerFactory.create(this, this)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.koitharu.kotatsu.reader.ui
|
||||
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.commit
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.doublepage.DoublePageReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment
|
||||
@@ -12,19 +14,24 @@ import java.util.EnumMap
|
||||
|
||||
class ReaderManager(
|
||||
private val fragmentManager: FragmentManager,
|
||||
@IdRes private val containerResId: Int,
|
||||
private val container: FragmentContainerView,
|
||||
) {
|
||||
|
||||
private val modeMap = EnumMap<ReaderMode, Class<out BaseReaderFragment<*>>>(ReaderMode::class.java)
|
||||
|
||||
init {
|
||||
modeMap[ReaderMode.STANDARD] = PagerReaderFragment::class.java
|
||||
val isTablet = container.resources.getBoolean(R.bool.is_tablet)
|
||||
modeMap[ReaderMode.STANDARD] = if (isTablet) {
|
||||
DoublePageReaderFragment::class.java
|
||||
} else {
|
||||
PagerReaderFragment::class.java
|
||||
}
|
||||
modeMap[ReaderMode.REVERSED] = ReversedReaderFragment::class.java
|
||||
modeMap[ReaderMode.WEBTOON] = WebtoonReaderFragment::class.java
|
||||
}
|
||||
|
||||
val currentReader: BaseReaderFragment<*>?
|
||||
get() = fragmentManager.findFragmentById(containerResId) as? BaseReaderFragment<*>
|
||||
get() = fragmentManager.findFragmentById(container.id) as? BaseReaderFragment<*>
|
||||
|
||||
val currentMode: ReaderMode?
|
||||
get() {
|
||||
@@ -36,14 +43,14 @@ class ReaderManager(
|
||||
val readerClass = requireNotNull(modeMap[newMode])
|
||||
fragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(containerResId, readerClass, null, null)
|
||||
replace(container.id, readerClass, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun replace(reader: BaseReaderFragment<*>) {
|
||||
/*fun replace(reader: BaseReaderFragment<*>) {
|
||||
fragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(containerResId, reader)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.doublepage
|
||||
|
||||
import android.graphics.PointF
|
||||
import android.view.Gravity
|
||||
import android.widget.FrameLayout
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.databinding.ItemPageBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder
|
||||
|
||||
class DoublePageHolder(
|
||||
owner: LifecycleOwner,
|
||||
binding: ItemPageBinding,
|
||||
loader: PageLoader,
|
||||
settings: ReaderSettings,
|
||||
networkState: NetworkState,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : PageHolder(owner, binding, loader, settings, networkState, exceptionResolver) {
|
||||
|
||||
private val isEven: Boolean
|
||||
get() = bindingAdapterPosition and 1 == 0
|
||||
|
||||
override fun onBind(data: ReaderPage) {
|
||||
super.onBind(data)
|
||||
(binding.textViewNumber.layoutParams as FrameLayout.LayoutParams)
|
||||
.gravity = (if (isEven) Gravity.START else Gravity.END) or Gravity.BOTTOM
|
||||
}
|
||||
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
with(binding.ssiv) {
|
||||
maxScale = 2f * maxOf(
|
||||
width / sWidth.toFloat(),
|
||||
height / sHeight.toFloat(),
|
||||
)
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||
setScaleAndCenter(
|
||||
minScale,
|
||||
PointF(if (isEven) sWidth.toFloat() else 0f, 0f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.doublepage
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class DoublePageLayoutManager(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int,
|
||||
defStyleRes: Int,
|
||||
) : LinearLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
override fun checkLayoutParams(lp: RecyclerView.LayoutParams?): Boolean {
|
||||
lp?.width = width / 2
|
||||
return super.checkLayoutParams(lp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.doublepage
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.yield
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderDoubleBinding
|
||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DoublePageReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var networkState: NetworkState
|
||||
|
||||
@Inject
|
||||
lateinit var pageLoader: PageLoader
|
||||
|
||||
override fun onCreateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
) = FragmentReaderDoubleBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onViewBindingCreated(
|
||||
binding: FragmentReaderDoubleBinding,
|
||||
savedInstanceState: Bundle?,
|
||||
) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
with(binding.recyclerView) {
|
||||
adapter = readerAdapter
|
||||
addOnScrollListener(PageScrollListener())
|
||||
DoublePageSnapHelper().attachToRecyclerView(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
requireViewBinding().recyclerView.adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) =
|
||||
coroutineScope {
|
||||
val items = async {
|
||||
requireAdapter().setItems(pages)
|
||||
yield()
|
||||
}
|
||||
if (pendingState != null) {
|
||||
val position = pages.indexOfFirst {
|
||||
it.chapterId == pendingState.chapterId && it.index == pendingState.page
|
||||
}
|
||||
items.await()
|
||||
if (position != -1) {
|
||||
requireViewBinding().recyclerView.firstVisibleItemPosition = position or 1
|
||||
notifyPageChanged(position)
|
||||
} else {
|
||||
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
items.await()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateAdapter() = DoublePagesAdapter(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
loader = pageLoader,
|
||||
settings = viewModel.readerSettings,
|
||||
networkState = networkState,
|
||||
exceptionResolver = exceptionResolver,
|
||||
)
|
||||
|
||||
override fun switchPageBy(delta: Int) {
|
||||
switchPageTo((requireViewBinding().recyclerView.currentItem() + delta) or 1, delta.absoluteValue > 1)
|
||||
}
|
||||
|
||||
override fun switchPageTo(position: Int, smooth: Boolean) {
|
||||
requireViewBinding().recyclerView.firstVisibleItemPosition = position or 1
|
||||
}
|
||||
|
||||
override fun getCurrentState(): ReaderState? = viewBinding?.run {
|
||||
val adapter = recyclerView.adapter as? BaseReaderAdapter<*>
|
||||
val page = adapter?.getItemOrNull(recyclerView.currentItem()) ?: return@run null
|
||||
ReaderState(
|
||||
chapterId = page.chapterId,
|
||||
page = page.index,
|
||||
scroll = 0,
|
||||
)
|
||||
}
|
||||
|
||||
private fun notifyPageChanged(page: Int) {
|
||||
viewModel.onCurrentPageChanged(page)
|
||||
}
|
||||
|
||||
private fun RecyclerView.currentItem(): Int {
|
||||
val lm = layoutManager as LinearLayoutManager
|
||||
return ((lm.findFirstVisibleItemPosition() + lm.findLastVisibleItemPosition()) / 2f).toIntUp()
|
||||
}
|
||||
|
||||
private inner class PageScrollListener : RecyclerView.OnScrollListener() {
|
||||
|
||||
private var lastPage = RecyclerView.NO_POSITION
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
val page = recyclerView.currentItem()
|
||||
if (page != lastPage) {
|
||||
lastPage = page
|
||||
notifyPageChanged(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.doublepage
|
||||
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.animation.Interpolator
|
||||
import android.widget.Scroller
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.OrientationHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider
|
||||
import androidx.recyclerview.widget.SnapHelper
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class DoublePageSnapHelper : SnapHelper() {
|
||||
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
|
||||
// Total number of items in a block of view in the RecyclerView
|
||||
private var blockSize = 2
|
||||
|
||||
// Maximum number of positions to move on a fling.
|
||||
private var maxPositionsToMove = 0
|
||||
|
||||
// Width of a RecyclerView item if orientation is horizontal; height of the item if vertical
|
||||
private var itemDimension = 0
|
||||
|
||||
// Maxim blocks to move during most vigorous fling.
|
||||
private val maxFlingBlocks = 2
|
||||
|
||||
// When snapping, used to determine direction of snap.
|
||||
private var priorFirstPosition = RecyclerView.NO_POSITION
|
||||
|
||||
// Our private scroller
|
||||
private var scroller: Scroller? = null
|
||||
|
||||
// Horizontal/vertical layout helper
|
||||
private lateinit var orientationHelper: OrientationHelper
|
||||
|
||||
// LTR/RTL helper
|
||||
private lateinit var layoutDirectionHelper: LayoutDirectionHelper
|
||||
|
||||
private val snapInterpolator = Interpolator { input ->
|
||||
var t = input
|
||||
t -= 1.0f
|
||||
t * t * t + 1.0f
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
override fun attachToRecyclerView(target: RecyclerView?) {
|
||||
if (target != null) {
|
||||
recyclerView = target
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
check(layoutManager.canScrollHorizontally()) { "RecyclerView must be scrollable" }
|
||||
orientationHelper = OrientationHelper.createHorizontalHelper(layoutManager)
|
||||
layoutDirectionHelper = LayoutDirectionHelper(ViewCompat.getLayoutDirection(recyclerView))
|
||||
scroller = Scroller(target.context, snapInterpolator)
|
||||
initItemDimensionIfNeeded(layoutManager)
|
||||
}
|
||||
super.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
override fun calculateDistanceToFinalSnap(
|
||||
layoutManager: RecyclerView.LayoutManager,
|
||||
targetView: View
|
||||
): IntArray {
|
||||
val out = IntArray(2)
|
||||
if (layoutManager.canScrollHorizontally()) {
|
||||
out[0] = layoutDirectionHelper.getScrollToAlignView(targetView)
|
||||
}
|
||||
if (layoutManager.canScrollVertically()) {
|
||||
out[1] = layoutDirectionHelper.getScrollToAlignView(targetView)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// We are flinging and need to know where we are heading.
|
||||
override fun findTargetSnapPosition(
|
||||
layoutManager: RecyclerView.LayoutManager,
|
||||
velocityX: Int, velocityY: Int
|
||||
): Int {
|
||||
val lm = layoutManager as LinearLayoutManager
|
||||
initItemDimensionIfNeeded(layoutManager)
|
||||
scroller!!.fling(0, 0, velocityX, velocityY, Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE)
|
||||
if (velocityX != 0) {
|
||||
return layoutDirectionHelper
|
||||
.getPositionsToMove(lm, scroller!!.finalX, itemDimension)
|
||||
}
|
||||
return if (velocityY != 0) {
|
||||
layoutDirectionHelper
|
||||
.getPositionsToMove(lm, scroller!!.finalY, itemDimension)
|
||||
} else RecyclerView.NO_POSITION
|
||||
}
|
||||
|
||||
// We have scrolled to the neighborhood where we will snap. Determine the snap position.
|
||||
override fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {
|
||||
// Snap to a view that is either 1) toward the bottom of the data and therefore on screen,
|
||||
// or, 2) toward the top of the data and may be off-screen.
|
||||
val snapPos: Int = calcTargetPosition(layoutManager as LinearLayoutManager)
|
||||
return if (snapPos == RecyclerView.NO_POSITION) null else layoutManager.findViewByPosition(snapPos)
|
||||
}
|
||||
|
||||
// Does the heavy lifting for findSnapView.
|
||||
private fun calcTargetPosition(layoutManager: LinearLayoutManager): Int {
|
||||
val snapPos: Int
|
||||
val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
|
||||
if (firstVisiblePos == RecyclerView.NO_POSITION) {
|
||||
return RecyclerView.NO_POSITION
|
||||
}
|
||||
initItemDimensionIfNeeded(layoutManager)
|
||||
if (firstVisiblePos >= priorFirstPosition) {
|
||||
// Scrolling toward bottom of data
|
||||
val firstCompletePosition = layoutManager.findFirstCompletelyVisibleItemPosition()
|
||||
snapPos = if (firstCompletePosition != RecyclerView.NO_POSITION
|
||||
&& firstCompletePosition % blockSize == 0
|
||||
) {
|
||||
firstCompletePosition
|
||||
} else {
|
||||
roundDownToBlockSize(firstVisiblePos + blockSize)
|
||||
}
|
||||
} else {
|
||||
// Scrolling toward top of data
|
||||
snapPos = roundDownToBlockSize(firstVisiblePos)
|
||||
// Check to see if target view exists. If it doesn't, force a smooth scroll.
|
||||
// SnapHelper only snaps to existing views and will not scroll to a non-existent one.
|
||||
// If limiting fling to single block, then the following is not needed since the
|
||||
// views are likely to be in the RecyclerView pool.
|
||||
if (layoutManager.findViewByPosition(snapPos) == null) {
|
||||
val toScroll: IntArray = layoutDirectionHelper.calculateDistanceToScroll(layoutManager, snapPos)
|
||||
recyclerView.smoothScrollBy(toScroll[0], toScroll[1], snapInterpolator)
|
||||
}
|
||||
}
|
||||
priorFirstPosition = firstVisiblePos
|
||||
return snapPos
|
||||
}
|
||||
|
||||
private fun initItemDimensionIfNeeded(layoutManager: RecyclerView.LayoutManager) {
|
||||
if (itemDimension != 0) {
|
||||
return
|
||||
}
|
||||
val child: View = layoutManager.getChildAt(0) ?: return
|
||||
if (layoutManager.canScrollHorizontally()) {
|
||||
itemDimension = child.width
|
||||
blockSize = getSpanCount(layoutManager) * (recyclerView.width / itemDimension)
|
||||
} else if (layoutManager.canScrollVertically()) {
|
||||
itemDimension = child.height
|
||||
blockSize = getSpanCount(layoutManager) * (recyclerView.height / itemDimension)
|
||||
}
|
||||
maxPositionsToMove = blockSize * maxFlingBlocks
|
||||
}
|
||||
|
||||
private fun getSpanCount(layoutManager: RecyclerView.LayoutManager): Int {
|
||||
return if (layoutManager is GridLayoutManager) layoutManager.spanCount else 1
|
||||
}
|
||||
|
||||
private fun roundDownToBlockSize(trialPosition: Int): Int {
|
||||
return trialPosition - trialPosition % blockSize
|
||||
}
|
||||
|
||||
private fun roundUpToBlockSize(trialPosition: Int): Int {
|
||||
return roundDownToBlockSize(trialPosition + blockSize - 1)
|
||||
}
|
||||
|
||||
override fun createScroller(layoutManager: RecyclerView.LayoutManager): RecyclerView.SmoothScroller? {
|
||||
return if (layoutManager !is ScrollVectorProvider) {
|
||||
null
|
||||
} else object : LinearSmoothScroller(recyclerView.context) {
|
||||
override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
|
||||
val snapDistances = calculateDistanceToFinalSnap(
|
||||
recyclerView.layoutManager!!,
|
||||
targetView,
|
||||
)
|
||||
val dx = snapDistances[0]
|
||||
val dy = snapDistances[1]
|
||||
val time = calculateTimeForDeceleration(
|
||||
max(abs(dx.toDouble()), abs(dy.toDouble()))
|
||||
.toInt(),
|
||||
)
|
||||
if (time > 0) {
|
||||
action.update(dx, dy, time, snapInterpolator)
|
||||
}
|
||||
}
|
||||
|
||||
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
|
||||
return 40f / displayMetrics.densityDpi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Helper class that handles calculations for LTR and RTL layouts.
|
||||
*/
|
||||
private inner class LayoutDirectionHelper(direction: Int) {
|
||||
|
||||
// Is the layout an RTL one?
|
||||
private val mIsRTL: Boolean
|
||||
|
||||
init {
|
||||
mIsRTL = direction == View.LAYOUT_DIRECTION_RTL
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the amount of scroll needed to align the target view with the layout edge.
|
||||
*/
|
||||
fun getScrollToAlignView(targetView: View?): Int {
|
||||
return if (mIsRTL) orientationHelper.getDecoratedEnd(targetView) - recyclerView.width else orientationHelper.getDecoratedStart(
|
||||
targetView,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the distance to final snap position when the view corresponding to the snap
|
||||
* position is not currently available.
|
||||
*
|
||||
* @param layoutManager LinearLayoutManager or descendant class
|
||||
* @param targetPos - Adapter position to snap to
|
||||
* @return int[2] {x-distance in pixels, y-distance in pixels}
|
||||
*/
|
||||
fun calculateDistanceToScroll(layoutManager: LinearLayoutManager, targetPos: Int): IntArray {
|
||||
val out = IntArray(2)
|
||||
val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
|
||||
if (layoutManager.canScrollHorizontally()) {
|
||||
if (targetPos <= firstVisiblePos) { // scrolling toward top of data
|
||||
if (mIsRTL) {
|
||||
val lastView = layoutManager.findViewByPosition(layoutManager.findLastVisibleItemPosition())
|
||||
out[0] = (orientationHelper.getDecoratedEnd(lastView)
|
||||
+ (firstVisiblePos - targetPos) * itemDimension)
|
||||
} else {
|
||||
val firstView = layoutManager.findViewByPosition(firstVisiblePos)
|
||||
out[0] = (orientationHelper.getDecoratedStart(firstView)
|
||||
- (firstVisiblePos - targetPos) * itemDimension)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layoutManager.canScrollVertically()) {
|
||||
if (targetPos <= firstVisiblePos) { // scrolling toward top of data
|
||||
val firstView = layoutManager.findViewByPosition(firstVisiblePos)
|
||||
out[1] = firstView!!.top - (firstVisiblePos - targetPos) * itemDimension
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the number of positions to move in the RecyclerView given a scroll amount
|
||||
and the size of the items to be scrolled. Return integral multiple of mBlockSize not
|
||||
equal to zero.
|
||||
*/
|
||||
fun getPositionsToMove(llm: LinearLayoutManager, scroll: Int, itemSize: Int): Int {
|
||||
var positionsToMove: Int
|
||||
positionsToMove = roundUpToBlockSize(abs((scroll.toDouble()) / itemSize).roundToInt())
|
||||
if (positionsToMove < blockSize) {
|
||||
// Must move at least one block
|
||||
positionsToMove = blockSize
|
||||
} else if (positionsToMove > maxPositionsToMove) {
|
||||
// Clamp number of positions to move, so we don't get wild flinging.
|
||||
positionsToMove = maxPositionsToMove
|
||||
}
|
||||
if (scroll < 0) {
|
||||
positionsToMove *= -1
|
||||
}
|
||||
if (mIsRTL) {
|
||||
positionsToMove *= -1
|
||||
}
|
||||
return if (layoutDirectionHelper.isDirectionToBottom(scroll < 0)) {
|
||||
// Scrolling toward the bottom of data.
|
||||
roundDownToBlockSize(llm.findFirstVisibleItemPosition()) + positionsToMove
|
||||
} else roundDownToBlockSize(llm.findLastVisibleItemPosition()) + positionsToMove
|
||||
// Scrolling toward the top of the data.
|
||||
}
|
||||
|
||||
fun isDirectionToBottom(velocityNegative: Boolean): Boolean {
|
||||
return if (mIsRTL) velocityNegative else !velocityNegative
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.doublepage
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.databinding.ItemPageBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
||||
|
||||
class DoublePagesAdapter(
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
loader: PageLoader,
|
||||
settings: ReaderSettings,
|
||||
networkState: NetworkState,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BaseReaderAdapter<DoublePageHolder>(loader, settings, networkState, exceptionResolver) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
loader: PageLoader,
|
||||
settings: ReaderSettings,
|
||||
networkState: NetworkState,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) = DoublePageHolder(
|
||||
owner = lifecycleOwner,
|
||||
binding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
loader = loader,
|
||||
settings = settings,
|
||||
networkState = networkState,
|
||||
exceptionResolver = exceptionResolver,
|
||||
)
|
||||
}
|
||||
10
app/src/main/res/layout/fragment_reader_double.xml
Normal file
10
app/src/main/res/layout/fragment_reader_double.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="org.koitharu.kotatsu.reader.ui.pager.doublepage.DoublePageLayoutManager" />
|
||||
Reference in New Issue
Block a user