I’ve been stuck on this problem for eight hours, so I figured it was time to get some help.
Before I begin my problem, I’ll just let it be known that I’ve been through this site and Google, and none of the answers I’ve found have helped. (This is one, another, and another.)
Here’s the deal: I have a class that extends SurfaceView (let’s call it MySurface) and overrides many methods in it. Normally, it draws several squares and text boxes, which is all fine. As soon as a user starts touching, it converts to a Bitmap, then draws each frame that until the user releases.
Here’s the rub: I want to implement such a functionality that the user can place two fingers on the screen, pinch to zoom, and also pan around (but ONLY with two fingers down).
I found a few implementations of pinch-to-zoom and adapted them to my Canvas object in MySurface via the following:
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.scale(mScaleVector.z, mScaleVector.z); // This is the scale factor as seen below
canvas.translate(mScaleVector.x, mScaleVector.y); // These are offset values from 0,0, both working fine
// Start draw code
// ...
// End draw code
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float factor = detector.getScaleFactor();
if (Math.abs(factor - 1.0f) >= 0.0075f) {
mScaleVector.z *= factor;
mScaleVector.z = Math.max(MIN_ZOOM, Math.min(mScaleVector.z, MAX_ZOOM));
}
// ...
invalidate();
return true;
}
}
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
if (event.getPointerCount() == 2) {
if (action == MotionEvent.ACTION_POINTER_DOWN && pointerIndex == 1) {
// The various pivot coordinate codes would belong here
}
}
detector.onTouchEvent(event); // Calls the Scale Gesture Detector
return true;
}
While both elements work fine–the scrolling back and forth and the pinch-to-zoom–there is one large problem. The pinch-to-zoom, when used, zooms into the point 0,0, instead of zooming into the finger point.
I’ve tried a lot of ways to fix this:
- Using
canvas.scale(mScaleVector.z, mScaleVector.z, mScaleVector.x, mScaleVector.y);; obviously, this produces unwanted results as themScaleVectorx and y values are 0-offsets. - Managing a “pivot” coordinate that uses the same offset as the
translate()method, but this produces either the same0,0issue, or jumping around when the view is touched. - Numerous other things… I’ve done a lot with the aforementioned pivot coordinate, trying to base its location on the user’s first touch, and moving it relative to that touch each successive gesture.
Additionally, this canvas must be bounded, so the user cannot scroll forever. However, when I use the .scale(sx, sy, px, py) method, it pushes things beyond any bounds I set in .translate().
I’m… pretty much open to anything at this point. I know this functionality can be added, as it is seen in the Android 4.0 gallery (when viewing a single image). I’ve tried to track down the source code that handles this, to no avail.
Here is the code I use to implement pinch zoom in an
ImageViewusingScaleGestureDetector. With little or no modification you should be able to use it too, since you can use transformation marices too, to draw on aCanvas.In my case, I computed the neccesary values in the
onMeasure()method of the View, you might want to do this somewhere else in yourSurfaceViewA little explanation:
saveScaleis the current scale ratio of theBitmapmScaleFactoris the factor you have to multiply your scale ratio with.maxScaleandminScalecan be constant values.widthandheightare the dimensions of the screen.redundantXSpaceandredundantYSpaceare the empty between the image borders and screen borders since the image is centered when in it is smaller then the screenorigHeightandorigWidthare the sizes of the bitmapmatrixis the current transformation matrix used to draw the bitmapThe trick is, that when I first scaled and centered the image on initialization, I picked that scale ratio to be 1 and with
scaleMappingRatioI mapped the actual scale values of the image relative to that.