Improve webtoon zoom fling
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user