It's common to react to double taps to control zoom. This should be a opt-in feature in ZoomEngine
, enabled by default in ZoomImageView
.
@natario1
For now, I have just implemented this functionality by my self with custom Gesture Listener class. Here is my code snippet if anyone need it.
private GestureDetector gestureDetector;
private View.OnTouchListener touchListener;
private ZoomImageView selectedImage;
then initialize these variables:
gestureDetector = new GestureDetector(mContext, new MyGestureListener());
touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// pass the events to the gesture detector
// a return value of true means the detector is handling it
// a return value of false means the detector didn't
// recognize the event
selectedImage = (ZoomImageView) v;
return gestureDetector.onTouchEvent(event);
}
};
then assign your touchlistener to zoomimageview object:
ZoomImageView image = layout.findViewById(R.id.imageViewItemImageSlider);
image.setOnTouchListener(touchListener);
and here is MyGestureListener class:
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
// @Override
// public boolean onDown(MotionEvent event) {
// Log.d("TAG","onDown: ");
//
// // don't return false here or else none of the other
// // gestures will work
// return true;
// }
//
// @Override
// public boolean onSingleTapConfirmed(MotionEvent e) {
// Log.i("TAG", "onSingleTapConfirmed: ");
// return true;
// }
//
// @Override
// public void onLongPress(MotionEvent e) {
// Log.i("TAG", "onLongPress: ");
// }
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i("TAG", "onDoubleTap: ");
if((selectedImage.getEngine().getZoom() >= 2.75F)) {
selectedImage.getEngine().zoomTo(1F, true);
} else if((selectedImage.getEngine().getZoom() < 1F)) {
selectedImage.getEngine().zoomTo(1F, true);
} else {
selectedImage.getEngine().zoomBy(2F, true);
}
return true;
}
// @Override
// public boolean onScroll(MotionEvent e1, MotionEvent e2,
// float distanceX, float distanceY) {
// Log.i("TAG", "onScroll: ");
// return true;
// }
// @Override
// public boolean onFling(MotionEvent event1, MotionEvent event2,
// float velocityX, float velocityY) {
// Log.d("TAG", "onFling: ");
// return true;
// }
}
Please do on override onScroll (MUST) and other method (Optional in my case) which you don't have anything to deal with as it will conflict with ZoomImageView's touch event and create an issue.
Hope this will help you.
I'm currently reacting to double-tap to center the screen where the event was done.
Simply by adding this:
private val simpleGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
//here is the method for double tap
override fun onDoubleTap(e: MotionEvent): Boolean {
Log.d("OnDoubleTapListener", "onDoubleTap")
centerElement(e.x.absoluteValue,e.y.absoluteValue)
return true
}
})
fun centerElement(clickedX: Float, clickedY: Float) {
val offsetX = (width.absoluteValue) / 2
val offsetY = (height.absoluteValue) / 2
val displacedX = engine.panX.absoluteValue
val displacedY = engine.panY.absoluteValue
val x = (displacedX + clickedX / engine.realZoom) - (offsetX / engine.realZoom)
val y = (displacedY + clickedY / engine.realZoom) - (offsetY / engine.realZoom)
val desiredX = if (x > 0) -x else 0f
val desiredY = if (y > 0) -y else 0f
engine.moveTo(engine.zoom, desiredX, desiredY, true)
}
Now I'm trying to move and zoom correctly. I can open a pull request if you want to make it work.
@AlvaroFalcon that would be cool! I can give you some tips.
ZoomEngine
that reacts to scroll and flingonScale()
. It works with coordinates of the center of the scale gesture and applies a zoom factor. You would have to assign a zoom factor yourself (I think zoomIn()
uses 1.3) but it's pretty much the same taskdoubleTapBehavior
?) which should have at least two options ("none" and "zoom"). Let's use none as a default so we don't change this for who is already using the lib.@natario1 Cool! I'll do it when I have some time...
I'm currently working in my project with a class that extends your zoomlayout so I add my logic there.
I'm trying to make zoom with movement at the same time using that, but no luck for the moment.
Thanks for the tips!
Could an action be added in the moveTo() method? To be called after it finishes, I think it would be more flexible that way so you can do multiple things.
Well, to moveTo, zoomBy, zoomTo, etc...
It could be like:
moveTo(zoom, x, y, action : ()->Unit={})
@AlvaroFalcon In Kotlin I would do that but this is still pure Java. We will move to Kotlin in the future though. For now, there is onIdle()
which anyone can access.
@AlvaroFalcon were you able to achieve zoom and pan at the same time? mind sharing your code?
I have implemented both double tap and pinch to zoom. The key to the working solution is to attach tap/double tap gesture detector to the content view inside zoom layout and configure zoom layout to have clickable children to true. Then in the onTouchEvent of the content view return true to consume touch events and pass them to the gesture detector. That way both pinch, zoom, and double tap work at the same time. Zooming in on double tap is no problem then.
However, I have also tried to zoom to the area that was double tapped, and given how ZoomEngine.moveTo
is implemented, the simultaneous interpolation of zoom and pan does not behave correctly and the image is sliding around different paths to reach the destination zoom and pan. It does not look visually pleasing, yet I'm unable to figure out what is happening. I think the interpolation algorithm should differently compute the actual pan based on what the actual zoom is, not just fraction of the animation.
zoom to the area that was double tapped
Yes, that's exactly what I've been trying to do. Any suggestions as to how to do this @natario1?
Hi @mman
We have also implemented a way to automatically zoom when the user taps the screen and the animation for zooming in kind of swirling toward the end position.
It would be very good, if we could control this animation - do you or others have suggestions for how to go about that?
Thanks in advance :)
I managed to do it with a dirty hack. I am tricking the zoom engine into thinking that a pinch is taking place.
class ReflectionHelper {
@Nullable
static ScaleGestureDetector.OnScaleGestureListener getScaleDetectorListenerUsingReflection(ZoomEngine zoomEngine) {
try {
Field mScaleDetector = zoomEngine.getClass().getDeclaredField("mScaleDetector");
mScaleDetector.setAccessible(true);
ScaleGestureDetector detector = (ScaleGestureDetector) mScaleDetector.get(zoomEngine);
Field mListener = detector.getClass().getDeclaredField("mListener");
mListener.setAccessible(true);
return (ScaleGestureDetector.OnScaleGestureListener) mListener.get(detector);
} catch (Exception e) {
return null;
}
}
}
private fun simulatePinch(fromScale: Float, toScale: Float, focusX: Float, focusY: Float) {
class MockDetector(var mockScaleFactor: Float, var mockFocusX: Float, var mockFocusY: Float) : ScaleGestureDetector(context, null) {
override fun getScaleFactor() = mockScaleFactor
override fun getFocusX() = mockFocusX
override fun getFocusY() = mockFocusY
}
val mockDetector = MockDetector(fromScale, focusX, focusY)
ValueAnimator.ofFloat(fromScale, toScale).apply {
addUpdateListener { animation ->
mockDetector.mockScaleFactor = animation.animatedValue as Float
zoomEngineScaleListener?.onScale(mockDetector)
}
doOnEnd { zoomEngineScaleListener?.onScaleEnd(mockDetector) }
start()
}
}
Hi,
Thanks for the effort! :)
That definitely seems like a bit of a hacky way of achieving it. I think we will however choose to live with the very animating animation for now.
Thanks,
Nikolaj
Hi,
Any news on how to achieve zooming + panning on a double tap?
Thanks,
Christophe
Most helpful comment
Hi,
Any news on how to achieve zooming + panning on a double tap?
Thanks,
Christophe