So I'm extending a custom SurfaceView
and am attempting to make it have pinch-zoom and scrolling capability.
How I scroll:
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// subtract scrolled amount
matrix.postTranslate(-distanceX, -distanceY);
rebound();
invalidate();
// handled
return true;
}
How I zoom:
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (detector.isInProgress()) {
// get scale
float factor = detector.getScaleFactor();
// Don't let the object get too small or too large.
if (factor * scale > 1f) {
factor = 1f / scale;
} else if (factor * scale < minScale) {
factor = minScale / scale;
}
// store local scale
scale *= factor;
// do the scale
matrix.preScale(factor, factor, detector.getFocusX(), detector.getFocusY());
rebound();
invalidate();
}
return true;
}
(for reference I use this code for onDraw
:)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.setMatrix(matrix);
// [...] stuff drawn with matrix settings
canvas.restore();
// [...] stuff drawn without matrix settings, as an overlay
}
Currently both of these methods are functioning well. The zooming is stopped at a minimum (between 0f and 1f) and maximum (currently always 1f) scale value correctly.
Global fields used:
drawW
, drawH
= float
, the size in pixels of the canvas data at scale = 1.parentW
, parentH
= float
, the size in pixels of the visible view.matrix
= android.graphics.Matrix
.The problem is the "rebound()
" (maybe needs a better name, heh) method I'm attempting to implement that would automatically force the contents to stay in view.
I've tried various methods to try to calculate where the bounds should be (within rectangle (0, 0, parentW, parentH)) and translate the matrix "back" when it goes too far.
Here's what I currently have, which definitely doesn't work, instead pushing it farther in the right a lot. I feel like the problem is my math, not my idea. Can someone please come up with simpler or cleaner code that translates the matrix to the edge if its too far and/or fix the problems with my attempt at this implementation? The if checks used to make it appear in the top left
public void rebound() {
// bounds
RectF currentBounds = new RectF(0, 0, drawW, drawH);
matrix.mapRect(currentBounds);
RectF parentBounds = new RectF(0, 0, parentW, parentH/2);
PointF diff = new PointF(0, 0);
if (currentBounds.left > parentBounds.left) {
diff.x += (parentBounds.left - currentBounds.left);
}
if (currentBounds.top > parentBounds.top) {
diff.y += (parentBounds.top - currentBounds.top);
}
if (currentBounds.width() > parentBounds.width()) {
if (currentBounds.right < parentBounds.right) {
diff.x += (parentBounds.right - currentBounds.right);
}
if (currentBounds.bottom < parentBounds.bottom) {
diff.y += (parentBounds.bottom - currentBounds.bottom);
}
}
matrix.postTranslate(diff.x, diff.y);
}
A previous version that I wrote before the matrix was a field (I just used canvas.scale()
then canvas.translate()
in onDraw
) that worked:
public void rebound() {
// bounds
int boundTop = 0;
int boundLeft = 0;
int boundRight = (int)(-scale * drawW + parentW);
int boundBottom = (int)(-scale * drawH + parentH);
if (boundLeft >= boundRight) {
mScrollX = Math.min(boundLeft, Math.max(boundRight, mScrollX));
} else {
mScrollX = 0;
}
if (boundTop >= boundBottom) {
mScrollY = Math.min(boundTop, Math.max(boundBottom, mScrollY));
} else {
mScrollY = 0;
}
}
I'm using the new way so that I can correctly scale centered on detector.getFocusX()
, detector.getFocusY()
.
UPDATE: I changed the method to what it is now. It works only somewhat, still bounding the y direction way off-center and is wrong after changing zoom levels. I also made it "preScale" and "postTranslate" so that (as I understand it) it should always be applying the scale then the translate and not mixing them.
FINAL UPDATE: It works now. Here's a working rebound
method with comments:
public void rebound() {
// make a rectangle representing what our current canvas looks like
RectF currentBounds = new RectF(0, 0, drawW, drawH);
matrix.mapRect(currentBounds);
// make a rectangle representing the scroll bounds
RectF areaBounds = new RectF((float) getLeft(),
(float) getTop(),
(float) parentW + (float) getLeft(),
(float) parentH + (float) getTop());
// the difference between the current rectangle and the rectangle we want
PointF diff = new PointF(0f, 0f);
// x-direction
if (currentBounds.width() > areaBounds.width()) {
// allow scrolling only if the amount of content is too wide at this scale
if (currentBounds.left > areaBounds.left) {
// stop from scrolling too far left
diff.x = (areaBounds.left - currentBounds.left);
}
if (currentBounds.right < areaBounds.right) {
// stop from scrolling too far right
diff.x = (areaBounds.right - currentBounds.right);
}
} else {
// negate any scrolling
diff.x = (areaBounds.left - currentBounds.left);
}
// y-direction
if (currentBounds.height() > areaBounds.height()) {
// allow scrolling only if the amount of content is too tall at this scale
if (currentBounds.top > areaBounds.top) {
// stop from scrolling too far above
diff.y = (areaBounds.top - currentBounds.top);
}
if (currentBounds.bottom < areaBounds.bottom) {
// stop from scrolling too far below
diff.y = (areaBounds.bottom - currentBounds.bottom);
}
} else {
// negate any scrolling
diff.y = (areaBounds.top - currentBounds.top);
}
// translate
matrix.postTranslate(diff.x, diff.y);
}
It negates any scrolling that I don't want by translating it back to the bounds. It completely negates scrolling if the content is too small, forcing the content to be in the top-left.
I implemented something exactly like this to roll my own pinch to zoom. I suspect the problem with your y-axis being off centre may be down to the view not being the full screen size, or you might not be changing the co-ordinates to match the scale.
My implementation calculates a scale factor, applies it with: canvas.scale( pinchZoomScale, pinchZoomScale ); Then calculates the physical size of the screen in pixels, converts it to metres, and finally applies the scaling factor so all objects drawn are offset correctly.
This implementation relies on always knowing what should be at the centre of the screen and locking on to it, whatever the zoom level.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With