diff --git a/app/src/main/java/com/otaliastudios/zoom/demo/ColorGridView.java b/app/src/main/java/com/otaliastudios/zoom/demo/ColorGridView.java index 7f5d91c..c1394a5 100644 --- a/app/src/main/java/com/otaliastudios/zoom/demo/ColorGridView.java +++ b/app/src/main/java/com/otaliastudios/zoom/demo/ColorGridView.java @@ -5,9 +5,6 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.ColorDrawable; -import androidx.annotation.AttrRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -15,6 +12,10 @@ import android.view.View; import android.widget.GridLayout; +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.Random; @@ -65,6 +66,13 @@ public void onClick(View view) { view.setBackgroundColor(Color.BLACK); } }); + view.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + view.setBackgroundColor(Color.WHITE); + return true; + } + }); return view; } diff --git a/library/src/main/java/com/otaliastudios/zoom/ZoomEngine.kt b/library/src/main/java/com/otaliastudios/zoom/ZoomEngine.kt index ee2fa90..b80b143 100644 --- a/library/src/main/java/com/otaliastudios/zoom/ZoomEngine.kt +++ b/library/src/main/java/com/otaliastudios/zoom/ZoomEngine.kt @@ -159,6 +159,7 @@ internal constructor(context: Context) : ZoomApi { // Internal private lateinit var container: View private val callbacks = Callbacks() + @Suppress("LeakingThis") private val dispatcher = UpdatesDispatcher(this) private val stateController = StateController(callbacks) @@ -516,7 +517,7 @@ internal constructor(context: Context) : ZoomApi { */ internal fun setContainer(container: View) { this.container = container - this.container.addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener { + this.container.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(view: View) { view.viewTreeObserver.addOnGlobalLayoutListener(callbacks) } @@ -833,7 +834,10 @@ internal constructor(context: Context) : ZoomApi { * @return true if anything was cancelled, false otherwise */ override fun cancelAnimations(): Boolean { - if (stateController.isFlinging() || stateController.isAnimating()) { + if (stateController.isFlinging()) { + scrollFlingDetector.cancelFling() + return true + } else if (stateController.isAnimating()) { stateController.makeIdle() return true } diff --git a/library/src/main/java/com/otaliastudios/zoom/internal/StateController.kt b/library/src/main/java/com/otaliastudios/zoom/internal/StateController.kt index b88a92c..766394b 100644 --- a/library/src/main/java/com/otaliastudios/zoom/internal/StateController.kt +++ b/library/src/main/java/com/otaliastudios/zoom/internal/StateController.kt @@ -3,6 +3,7 @@ package com.otaliastudios.zoom.internal import android.view.MotionEvent import androidx.annotation.IntDef import com.otaliastudios.zoom.ZoomLogger +import com.otaliastudios.zoom.internal.StateController.Callback /** * Deals with touch input, holds the internal [state] integer, @@ -50,6 +51,7 @@ internal class StateController(private val callback: Callback) { /** * Private function to set the current state. * External callers should use [setPinching], [setScrolling], [makeIdle]... instead. + * @return true if the new state was applied, false otherwise */ private fun setState(@State newState: Int): Boolean { LOG.v("trySetState:", newState.toStateName()) diff --git a/library/src/main/java/com/otaliastudios/zoom/internal/gestures/ScrollFlingDetector.kt b/library/src/main/java/com/otaliastudios/zoom/internal/gestures/ScrollFlingDetector.kt index f5a8996..01a2d54 100644 --- a/library/src/main/java/com/otaliastudios/zoom/internal/gestures/ScrollFlingDetector.kt +++ b/library/src/main/java/com/otaliastudios/zoom/internal/gestures/ScrollFlingDetector.kt @@ -7,8 +7,8 @@ import android.widget.OverScroller import com.otaliastudios.zoom.ScaledPoint import com.otaliastudios.zoom.ZoomApi import com.otaliastudios.zoom.ZoomLogger -import com.otaliastudios.zoom.internal.matrix.MatrixController import com.otaliastudios.zoom.internal.StateController +import com.otaliastudios.zoom.internal.matrix.MatrixController import com.otaliastudios.zoom.internal.movement.PanManager import kotlin.math.abs import kotlin.math.pow @@ -65,17 +65,28 @@ internal class ScrollFlingDetector( * idle state. */ internal fun cancelScroll() { + if (!correctOverpan()) { + stateController.makeIdle() + } + } + + /** + * Initiates an animation to correct any existing overpan + * @return true if a correction was initiated, false otherwise + */ + private fun correctOverpan(): Boolean { if (panManager.isOverEnabled) { val fix = panManager.correction if (fix.x != 0f || fix.y != 0f) { matrixController.animateUpdate { panBy(fix, true) } - return + return true } } - stateController.makeIdle() + return false } override fun onDown(e: MotionEvent): Boolean { + cancelFling() return true // We are interested in the gesture. } @@ -107,6 +118,9 @@ internal class ScrollFlingDetector( } // Must be after the other conditions. if (!stateController.setFlinging()) return false + // disable long press detection while we are flinging + // to prevent long presses from interrupting a possible followup scroll gesture + detector.setIsLongpressEnabled(false) @ZoomApi.ScaledPan val overScrollX = if (panManager.horizontalOverPanEnabled) panManager.maxOverPan else 0F @ZoomApi.ScaledPan val overScrollY = if (panManager.verticalOverPanEnabled) panManager.maxOverPan else 0F @@ -122,6 +136,8 @@ internal class ScrollFlingDetector( override fun run() { if (flingScroller.isFinished) { stateController.makeIdle() + // re-enable long press detection + detector.setIsLongpressEnabled(true) } else if (flingScroller.computeScrollOffset()) { val newPan = ScaledPoint(flingScroller.currX.toFloat(), flingScroller.currY.toFloat()) // OverScroller will eventually go back to our bounds.