Improve webtoon zoom fling

This commit is contained in:
vianh
2024-02-15 21:21:45 +07:00
committed by Koitharu
parent 813561fd3b
commit b4bd923ce8

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.Matrix import android.graphics.Matrix
import android.graphics.Point
import android.graphics.Rect import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.util.AttributeSet import android.util.AttributeSet
@@ -19,12 +20,16 @@ import android.widget.OverScroller
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewConfigurationCompat import androidx.core.view.ViewConfigurationCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import kotlin.math.roundToInt
private const val MAX_SCALE = 2.5f private const val MAX_SCALE = 2.5f
private const val MIN_SCALE = 0.5f private const val MIN_SCALE = 0.5f
private const val FLING_RANGE = 20_000
class WebtoonScalingFrame @JvmOverloads constructor( class WebtoonScalingFrame @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@@ -36,6 +41,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
private val scaleDetector = ScaleGestureDetector(context, this) private val scaleDetector = ScaleGestureDetector(context, this)
private val gestureDetector = GestureDetectorCompat(context, GestureListener()) private val gestureDetector = GestureDetectorCompat(context, GestureListener())
private val overScroller = OverScroller(context, AccelerateDecelerateInterpolator()) private val overScroller = OverScroller(context, AccelerateDecelerateInterpolator())
private val transformMatrix = Matrix() private val transformMatrix = Matrix()
private val matrixValues = FloatArray(9) private val matrixValues = FloatArray(9)
private val scale private val scale
@@ -49,6 +55,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
private val translateBounds = RectF() private val translateBounds = RectF()
private val targetHitRect = Rect() private val targetHitRect = Rect()
private var animator: ValueAnimator? = null private var animator: ValueAnimator? = null
private var pendingScroll = 0
var isZoomEnable = false var isZoomEnable = false
set(value) { set(value) {
@@ -80,7 +87,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
overScroller.forceFinished(true) overScroller.forceFinished(true)
} }
gestureDetector.onTouchEvent(ev) val consumed = gestureDetector.onTouchEvent(ev)
scaleDetector.onTouchEvent(ev) scaleDetector.onTouchEvent(ev)
// Offset event to inside the child view // Offset event to inside the child view
@@ -88,11 +95,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
ev.offsetLocation(halfWidth - ev.x + targetHitRect.width() / 3, 0f) ev.offsetLocation(halfWidth - ev.x + targetHitRect.width() / 3, 0f)
} }
// Send action cancel to avoid recycler jump when scale end return consumed || scaleDetector.isInProgress || super.dispatchTouchEvent(ev)
if (scaleDetector.isInProgress) {
ev.action = MotionEvent.ACTION_CANCEL
}
return super.dispatchTouchEvent(ev)
} }
override fun onGenericMotionEvent(event: MotionEvent): Boolean { override fun onGenericMotionEvent(event: MotionEvent): Boolean {
@@ -178,6 +181,10 @@ class WebtoonScalingFrame @JvmOverloads constructor(
scaleY = scale scaleY = scale
translationX = transX translationX = transX
translationY = transY translationY = transY
if (pendingScroll != 0) {
nestedScrollBy(0, pendingScroll)
pendingScroll = 0
}
} }
val newHeight = if (scale < 1f) (height / scale).toInt() else height val newHeight = if (scale < 1f) (height / scale).toInt() else height
@@ -210,6 +217,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
else -> 0f else -> 0f
} }
pendingScroll = if (scale > 1) (dy / scale).roundToInt() else 0
transformMatrix.postTranslate(dx, dy) transformMatrix.postTranslate(dx, dy)
syncMatrixValues() syncMatrixValues()
} }
@@ -277,6 +285,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
private fun findTargetChild() = getChildAt(0) as WebtoonRecyclerView private fun findTargetChild() = getChildAt(0) as WebtoonRecyclerView
private inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable { private inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable {
private val prevPos = Point()
override fun onScroll( override fun onScroll(
e1: MotionEvent?, e1: MotionEvent?,
@@ -294,7 +303,7 @@ class WebtoonScalingFrame @JvmOverloads constructor(
val newScale = if (scale != 1f) 1f else MAX_SCALE * 0.8f val newScale = if (scale != 1f) 1f else MAX_SCALE * 0.8f
ValueAnimator.ofFloat(scale, newScale).run { ValueAnimator.ofFloat(scale, newScale).run {
interpolator = AccelerateDecelerateInterpolator() interpolator = AccelerateDecelerateInterpolator()
duration = 300 duration = context.getAnimationDuration(R.integer.config_defaultAnimTime)
addUpdateListener { addUpdateListener {
scaleChild(it.animatedValue as Float, e.x, e.y) scaleChild(it.animatedValue as Float, e.x, e.y)
} }
@@ -311,15 +320,16 @@ class WebtoonScalingFrame @JvmOverloads constructor(
): Boolean { ): Boolean {
if (scale <= 1) return false if (scale <= 1) return false
prevPos.set(transX.toInt(), transY.toInt())
overScroller.fling( overScroller.fling(
transX.toInt(), prevPos.x,
transY.toInt(), prevPos.y,
velocityX.toInt(), velocityX.toInt(),
velocityY.toInt(), velocityY.toInt(),
translateBounds.left.toInt(), translateBounds.left.toInt(),
translateBounds.right.toInt(), translateBounds.right.toInt(),
translateBounds.top.toInt(), translateBounds.top.toInt() - FLING_RANGE,
translateBounds.bottom.toInt(), translateBounds.bottom.toInt() + FLING_RANGE,
) )
postOnAnimation(this) postOnAnimation(this)
return true return true
@@ -328,9 +338,10 @@ class WebtoonScalingFrame @JvmOverloads constructor(
override fun run() { override fun run() {
if (overScroller.computeScrollOffset()) { if (overScroller.computeScrollOffset()) {
transformMatrix.postTranslate( transformMatrix.postTranslate(
overScroller.currX - transX, overScroller.currX.toFloat() - prevPos.x,
overScroller.currY - transY, overScroller.currY.toFloat() - prevPos.y
) )
prevPos.set(overScroller.currX, overScroller.currY)
invalidateTarget() invalidateTarget()
postOnAnimation(this) postOnAnimation(this)
} }