diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonFrameLayout.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonFrameLayout.kt index 0544f741b..719b4a790 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonFrameLayout.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonFrameLayout.kt @@ -12,9 +12,11 @@ class WebtoonFrameLayout @JvmOverloads constructor( @AttrRes defStyleAttr: Int = 0, ) : FrameLayout(context, attrs, defStyleAttr) { - val target: WebtoonImageView by lazy(LazyThreadSafetyMode.NONE) { - findViewById(R.id.ssiv) - } + private var _target: WebtoonImageView? = null + val target: WebtoonImageView + get() = _target ?: findViewById(R.id.ssiv).also { + _target = it + } fun dispatchVerticalScroll(dy: Int): Int { if (dy == 0) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonImageView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonImageView.kt index fee639014..b4a2de35c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonImageView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonImageView.kt @@ -1,14 +1,16 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint import android.graphics.PointF import android.util.AttributeSet import androidx.core.view.ancestors import androidx.recyclerview.widget.RecyclerView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView -import org.koitharu.kotatsu.parsers.util.toIntUp - -private const val SCROLL_UNKNOWN = -1 +import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.core.util.ext.resolveDp +import kotlin.math.roundToInt class WebtoonImageView @JvmOverloads constructor( context: Context, @@ -18,7 +20,14 @@ class WebtoonImageView @JvmOverloads constructor( private val ct = PointF() private var scrollPos = 0 - private var scrollRange = SCROLL_UNKNOWN + private var debugPaint: Paint? = null + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (BuildConfig.DEBUG) { + drawDebug(canvas) + } + } fun scrollBy(delta: Int) { val maxScroll = getScrollRange() @@ -41,14 +50,14 @@ class WebtoonImageView @JvmOverloads constructor( fun getScroll() = scrollPos fun getScrollRange(): Int { - if (scrollRange == SCROLL_UNKNOWN) { - computeScrollRange() + if (!isReady) { + return 0 } - return scrollRange.coerceAtLeast(0) + val totalHeight = (sHeight * width / sWidth.toFloat()).roundToInt() + return (totalHeight - height).coerceAtLeast(0) } override fun recycle() { - scrollRange = SCROLL_UNKNOWN scrollPos = 0 super.recycle() } @@ -91,8 +100,6 @@ class WebtoonImageView @JvmOverloads constructor( override fun onDownsamplingChanged() { super.onDownsamplingChanged() adjustScale() - computeScrollRange() - ancestors.firstNotNullOfOrNull { it as? WebtoonRecyclerView }?.updateChildrenScroll() } override fun onReady() { @@ -102,13 +109,10 @@ class WebtoonImageView @JvmOverloads constructor( override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) - if (oldh == h || oldw == 0 || oldh == 0 || scrollRange == SCROLL_UNKNOWN) return - - computeScrollRange() - val container = ancestors.firstNotNullOfOrNull { it as? WebtoonFrameLayout } ?: return - val parentHeight = parentHeight() - if (scrollPos != 0 && container.bottom < parentHeight) { - scrollTo(scrollRange) + if (oldh != h && oldw != 0 && oldh != 0 && isReady) { + ancestors.firstNotNullOfOrNull { it as? WebtoonRecyclerView }?.updateChildrenScroll() + } else { + return } } @@ -120,14 +124,6 @@ class WebtoonImageView @JvmOverloads constructor( setScaleAndCenter(minScale, ct) } - private fun computeScrollRange() { - if (!isReady) { - return - } - val totalHeight = (sHeight * minScale).toIntUp() - scrollRange = (totalHeight - height).coerceAtLeast(0) - } - private fun adjustScale() { minScale = width / sWidth.toFloat() maxScale = minScale @@ -137,4 +133,18 @@ class WebtoonImageView @JvmOverloads constructor( private fun parentHeight(): Int { return ancestors.firstNotNullOfOrNull { it as? RecyclerView }?.height ?: 0 } + + private fun drawDebug(canvas: Canvas) { + val paint = debugPaint ?: Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = android.graphics.Color.RED + strokeWidth = context.resources.resolveDp(2f) + textAlign = android.graphics.Paint.Align.LEFT + textSize = context.resources.resolveDp(14f) + debugPaint = this + } + paint.style = Paint.Style.STROKE + canvas.drawRect(1f, 1f, width.toFloat() - 1f, height.toFloat() - 1f, paint) + paint.style = Paint.Style.FILL + canvas.drawText("${getScroll()} / ${getScrollRange()}", 100f, 100f, paint) + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt index 77ffa2810..788a11107 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt @@ -3,11 +3,10 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.Toast import androidx.core.view.ViewCompat.TYPE_TOUCH import androidx.core.view.forEach +import androidx.core.view.iterator import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition import java.util.LinkedList import java.util.WeakHashMap @@ -18,6 +17,7 @@ class WebtoonRecyclerView @JvmOverloads constructor( private var onPageScrollListeners: MutableList? = null private val detachedViews = WeakHashMap() + private var isFixingScroll: Boolean = false override fun onChildDetachedFromWindow(child: View) { super.onChildDetachedFromWindow(child) @@ -56,6 +56,13 @@ class WebtoonRecyclerView @JvmOverloads constructor( return consumedY != 0 || dy == 0 } + override fun onScrollStateChanged(state: Int) { + super.onScrollStateChanged(state) + if (state == SCROLL_STATE_IDLE) { + updateChildrenScroll() + } + } + private fun consumeVerticalScroll(dy: Int): Int { if (childCount == 0) { return 0 @@ -106,16 +113,12 @@ class WebtoonRecyclerView @JvmOverloads constructor( } private fun notifyScrollChanged(dy: Int) { - updateChildrenScroll() val listeners = onPageScrollListeners if (listeners.isNullOrEmpty()) { return } val centerPosition = findCenterViewPosition() listeners.forEach { it.dispatchScroll(this, dy, centerPosition) } - if (BuildConfig.DEBUG) { - validateLayout() - } } fun relayoutChildren() { @@ -128,29 +131,35 @@ class WebtoonRecyclerView @JvmOverloads constructor( } fun updateChildrenScroll() { - forEach { child -> + if (isFixingScroll) { + return + } + isFixingScroll = true + for (child in this) { val ssiv = (child as WebtoonFrameLayout).target - when { - child.top < 0 -> ssiv.scrollTo(ssiv.getScrollRange()) - child.top > 0 -> ssiv.scrollTo(0) - else -> ssiv.scrollBy(0) + if (adjustScroll(child, ssiv)) { + break } } + isFixingScroll = false } - private fun validateLayout() { - forEach { child -> - val ssiv = (child as WebtoonFrameLayout).target - val scroll = ssiv.getScroll() - val assertion = when { - child.top < 0 -> scroll == ssiv.getScrollRange() - child.top > 0 -> scroll == 0 - else -> true - } - if (!assertion) { - Toast.makeText(context, "Scroll = $scroll for view with top: ${child.top}", Toast.LENGTH_SHORT).show() - } + private fun adjustScroll(child: View, ssiv: WebtoonImageView): Boolean = when { + child.bottom < height && ssiv.getScroll() < ssiv.getScrollRange() -> { + val distance = minOf(height - child.bottom, ssiv.getScrollRange() - ssiv.getScroll()) + scrollBy(0, -distance) + ssiv.scrollBy(distance) + true } + + child.top > 0 && ssiv.getScroll() > 0 -> { + val distance = minOf(child.top, ssiv.getScroll()) + scrollBy(0, distance) + ssiv.scrollBy(-distance) + true + } + + else -> false } abstract class OnPageScrollListener {