Note that there are some explanatory texts on larger screens.

plurals
  1. POHow can I place limits on my canvas's translation matrix?
    text
    copied!<p>So I'm extending a custom <code>SurfaceView</code> and am attempting to make it have pinch-zoom and scrolling capability.</p> <p>How I scroll:</p> <pre><code> @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // subtract scrolled amount matrix.postTranslate(-distanceX, -distanceY); rebound(); invalidate(); // handled return true; } </code></pre> <p>How I zoom:</p> <pre><code> @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 &gt; 1f) { factor = 1f / scale; } else if (factor * scale &lt; minScale) { factor = minScale / scale; } // store local scale scale *= factor; // do the scale matrix.preScale(factor, factor, detector.getFocusX(), detector.getFocusY()); rebound(); invalidate(); } return true; } </code></pre> <p>(for reference I use this code for <code>onDraw</code>:)</p> <pre><code>@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 } </code></pre> <p>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.</p> <p>Global fields used:</p> <ul> <li><code>drawW</code>, <code>drawH</code> = <code>float</code>, the size in pixels of the canvas data at scale = 1.</li> <li><code>parentW</code>, <code>parentH</code> = <code>float</code>, the size in pixels of the visible view.</li> <li><code>matrix</code> = <code>android.graphics.Matrix</code>.</li> </ul> <hr> <p>The problem is the "<code>rebound()</code>" (maybe needs a better name, heh) method I'm attempting to implement that would automatically force the contents to stay in view.</p> <p>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.</p> <p>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. <strong>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?</strong> The if checks used to make it appear in the top left</p> <pre><code>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 &gt; parentBounds.left) { diff.x += (parentBounds.left - currentBounds.left); } if (currentBounds.top &gt; parentBounds.top) { diff.y += (parentBounds.top - currentBounds.top); } if (currentBounds.width() &gt; parentBounds.width()) { if (currentBounds.right &lt; parentBounds.right) { diff.x += (parentBounds.right - currentBounds.right); } if (currentBounds.bottom &lt; parentBounds.bottom) { diff.y += (parentBounds.bottom - currentBounds.bottom); } } matrix.postTranslate(diff.x, diff.y); } </code></pre> <p>A previous version that I wrote before the matrix was a field (I just used <code>canvas.scale()</code> then <code>canvas.translate()</code> in <code>onDraw</code>) that worked:</p> <pre><code>public void rebound() { // bounds int boundTop = 0; int boundLeft = 0; int boundRight = (int)(-scale * drawW + parentW); int boundBottom = (int)(-scale * drawH + parentH); if (boundLeft &gt;= boundRight) { mScrollX = Math.min(boundLeft, Math.max(boundRight, mScrollX)); } else { mScrollX = 0; } if (boundTop &gt;= boundBottom) { mScrollY = Math.min(boundTop, Math.max(boundBottom, mScrollY)); } else { mScrollY = 0; } } </code></pre> <p>I'm using the new way so that I can correctly scale centered on <code>detector.getFocusX()</code>, <code>detector.getFocusY()</code>.</p> <p><strong>UPDATE</strong>: 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.</p> <p><strong>FINAL UPDATE</strong>: It works now. Here's a working <code>rebound</code> method with comments:</p> <pre><code>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() &gt; areaBounds.width()) { // allow scrolling only if the amount of content is too wide at this scale if (currentBounds.left &gt; areaBounds.left) { // stop from scrolling too far left diff.x = (areaBounds.left - currentBounds.left); } if (currentBounds.right &lt; 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() &gt; areaBounds.height()) { // allow scrolling only if the amount of content is too tall at this scale if (currentBounds.top &gt; areaBounds.top) { // stop from scrolling too far above diff.y = (areaBounds.top - currentBounds.top); } if (currentBounds.bottom &lt; 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); } </code></pre> <p>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.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload