How can I place restrictions on my canvas translation matrix?

So, I am expanding a custom SurfaceView and trying to make it available for scaling and scrolling.

How do 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 to zoom in:

  @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 } 

Both of these methods are currently functioning well. Scaling stops at least (between 0f and 1f) and the maximum (currently always 1f) scale value is correct.

Used global fields:

  • drawW , drawH = float , the size in pixels of the canvas data in scale = 1.
  • parentW , parentH = float , the size in pixels of the visible view.
  • matrix = android.graphics.Matrix .

The problem is that the " rebound() " method (maybe needs a better name, heh), which I am trying to implement, which will automatically force the content to remain in view.

I tried various methods to try to figure out where the borders should be (inside the rectangle (0, 0, parentW, parentH)) and translate the matrix "backwards" when it goes too far.

Here is what I now have that definitely does not work, instead pushing it further to the right. I feel that the problem is my math, not my idea. Can someone please come up with simpler or cleaner code that takes the matrix to the edge if it is too far and / or fixes the problems with my attempt at this implementation? . If the checks used so that they appear in the upper 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); } 

The previous version that I wrote before the matrix was a field (I just used canvas.scale() and then canvas.translate() in onDraw ), which 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 am using a new way so that I can correctly center-scale detector.getFocusX() , detector.getFocusY() .

UPDATE I changed the method to what it is now. It only works a few, still limiting the y-direction off-center and incorrectly after changing zoom levels. I also did this "preScale" and "postTranslate" so that (as I understand it) it should always apply scale and then translate, and not mix them.

FINAL UPDATE : it now works. Here's the 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); } 

This negates any scrolling that I don't want, moving it back to the borders. This completely negates scrolling if the content is too small, forcing the content to be in the upper left.

+8
java android matrix android-canvas
source share
1 answer

I implemented something like that to flip my own pinch to zoom. I suspect that the problem with your y axis out of center may be lower than the representation of the incomplete screen size, or you cannot change the coordinates to fit the scale.

My implementation calculates a scale factor, applies it with: canvas.scale (pinchZoomScale, pinchZoomScale); Then it calculates the physical size of the screen in pixels, converts it to meters, and finally applies a scale factor so that all the drawn objects are correctly shifted.

This implementation is based on always knowing what should be in the center of the screen and lock on it, regardless of the zoom level.

0
source share

All Articles