Webtoon reader scroll with pan
This commit is contained in:
@@ -1,16 +1,29 @@
|
||||
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import org.koitharu.kotatsu.core.model.MangaPage
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.ui.reader.PageLoader
|
||||
|
||||
class WebtoonAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage, Unit>() {
|
||||
class WebtoonAdapter(private val loader: PageLoader) : BaseRecyclerAdapter<MangaPage, Int>() {
|
||||
|
||||
private var lastBound = -1
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) =
|
||||
WebtoonHolder(parent, loader)
|
||||
|
||||
override fun onGetItemId(item: MangaPage) = item.id
|
||||
|
||||
override fun getExtra(item: MangaPage, position: Int) = Unit
|
||||
override fun onBindViewHolder(holder: BaseViewHolder<MangaPage, Int>, position: Int) {
|
||||
super.onBindViewHolder(holder, position)
|
||||
lastBound = position
|
||||
}
|
||||
|
||||
override fun getExtra(item: MangaPage, position: Int) = if (position >= lastBound) {
|
||||
Gravity.TOP
|
||||
} else {
|
||||
Gravity.BOTTOM
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.RectF
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import kotlinx.android.synthetic.main.item_page_webtoon.view.*
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class WebtoonFrameLayout @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val pan = RectF()
|
||||
|
||||
private val target by lazy {
|
||||
findViewById<SubsamplingScaleImageView>(R.id.ssiv)
|
||||
}
|
||||
|
||||
fun dispatchVerticalScroll(dy: Int): Int {
|
||||
target.getPanRemaining(pan)
|
||||
val c = target.center ?: return 0
|
||||
val s = target.scale
|
||||
return when {
|
||||
dy > 0 -> {
|
||||
val delta = minOf(pan.bottom.toInt(), dy)
|
||||
c.offset(0f, delta.toFloat() / s)
|
||||
target.setScaleAndCenter(s, c)
|
||||
delta
|
||||
}
|
||||
dy < 0 -> {
|
||||
val delta = minOf(pan.top.toInt(), -dy)
|
||||
c.offset(0f, -delta.toFloat() / s)
|
||||
target.setScaleAndCenter(s, c)
|
||||
-delta
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
|
||||
import android.graphics.PointF
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.isVisible
|
||||
@@ -16,10 +17,11 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
|
||||
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page_webtoon),
|
||||
BaseViewHolder<MangaPage, Int>(parent, R.layout.item_page_webtoon),
|
||||
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader {
|
||||
|
||||
private var job: Job? = null
|
||||
private var yFactor = 0f
|
||||
|
||||
init {
|
||||
ssiv.setOnImageEventListener(this)
|
||||
@@ -28,7 +30,12 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(data: MangaPage, extra: Unit) {
|
||||
override fun onBind(data: MangaPage, extra: Int) {
|
||||
yFactor = when(extra) {
|
||||
Gravity.TOP -> 0f
|
||||
Gravity.BOTTOM -> 1f
|
||||
else -> 0.5f
|
||||
}
|
||||
doLoad(data, force = false)
|
||||
}
|
||||
|
||||
@@ -56,7 +63,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
|
||||
ssiv.setScaleAndCenter(
|
||||
ssiv.minScale,
|
||||
PointF(ssiv.sWidth / 2f, 0f)
|
||||
PointF(ssiv.sWidth / 2f, ssiv.sHeight * yFactor)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
|
||||
class WebtoonImageView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null) :
|
||||
SubsamplingScaleImageView(context, attr) {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent) = false
|
||||
|
||||
}
|
||||
@@ -3,13 +3,46 @@ package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||
|
||||
override fun onNestedPreScroll(target: View?, dx: Int, dy: Int, consumed: IntArray?) {
|
||||
super.onNestedPreScroll(target, dx, dy, consumed)
|
||||
override fun dispatchNestedPreScroll(
|
||||
dx: Int,
|
||||
dy: Int,
|
||||
consumed: IntArray?,
|
||||
offsetInWindow: IntArray?,
|
||||
type: Int
|
||||
): Boolean {
|
||||
val consumedY = consumeVerticalScroll(dy)
|
||||
val superRes = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
|
||||
consumed?.set(1, consumed[1] + consumedY)
|
||||
return superRes || consumedY != 0
|
||||
}
|
||||
|
||||
override fun dispatchNestedPreScroll(
|
||||
dx: Int,
|
||||
dy: Int,
|
||||
consumed: IntArray?,
|
||||
offsetInWindow: IntArray?
|
||||
): Boolean {
|
||||
val consumedY = consumeVerticalScroll(dy)
|
||||
val superRes = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)
|
||||
consumed?.set(1, consumed[1] + consumedY)
|
||||
return superRes || consumedY != 0
|
||||
}
|
||||
|
||||
private fun consumeVerticalScroll(dy: Int): Int {
|
||||
val child = when {
|
||||
dy > 0 -> children.firstOrNull { it is WebtoonFrameLayout }
|
||||
dy < 0 -> children.lastOrNull { it is WebtoonFrameLayout }
|
||||
else -> null
|
||||
} ?: return 0
|
||||
var scrollY = dy
|
||||
scrollY -= (child as WebtoonFrameLayout) .dispatchVerticalScroll(scrollY)
|
||||
return dy - scrollY
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
<org.koitharu.kotatsu.ui.reader.wetoon.WebtoonFrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.koitharu.kotatsu.ui.reader.wetoon.WebtoonImageView
|
||||
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
android:id="@+id/ssiv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
app:panEnabled="false" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
@@ -48,4 +50,4 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</org.koitharu.kotatsu.ui.reader.wetoon.WebtoonFrameLayout>
|
||||
Reference in New Issue
Block a user