如何对画布的平移矩阵施加限制?
因此,我正在扩展自定义 SurfaceView 并尝试使其具有捏缩放和滚动功能。
我如何滚动:
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// subtract scrolled amount
matrix.postTranslate(-distanceX, -distanceY);
rebound();
invalidate();
// handled
return true;
}
如何缩放:(
@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;
}
作为参考,我将此代码用于 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
}
目前这两种方法都运行良好。缩放正确地停止在最小(0f 和 1f 之间)和最大(当前始终为 1f)缩放值。
使用的全局字段:
drawW
、drawH
=float
,画布数据在比例 = 1 时的大小(以像素为单位)。parentW,
parentH
=float
,可见视图的大小(以像素为单位)。矩阵
=android.graphics.Matrix
。
问题是我试图实现的“rebound()
”(也许需要一个更好的名称,呵呵)方法会自动强制内容保持在视图中。
我尝试了各种方法来尝试计算边界应该在哪里(在矩形(0,0,parentW,parentH)内),并在矩阵走得太远时将矩阵“向后”平移。
这是我目前所拥有的,这绝对行不通,而是将其向右推得更远。我觉得问题是我的数学,而不是我的想法。 有人可以想出更简单或更清晰的代码,将矩阵转换到边缘(如果矩阵太远)和/或修复我尝试此实现的问题吗? 用于使其出现在中的 if 检查左上角
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);
}
我在矩阵之前编写的先前版本是一个字段(我只是在 onDraw< 中使用了
canvas.scale()
然后使用了 canvas.translate()
/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 >= 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;
}
}
我正在使用新方法我可以以 detector.getFocusX() 、
detector.getFocusY() 为中心正确缩放。
更新:我将方法更改为现在的方法。它只能在一定程度上起作用,仍然使 y 方向偏离中心,并且在更改缩放级别后是错误的。我还将其设置为“preScale”和“postTranslate”,以便(据我所知)它应该始终先应用比例,然后进行翻译,而不是混合它们。
最终更新:现在可以使用了。这是一个带有注释的有效 rebound
方法:
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);
}
它通过将其转换回边界来否定任何我不想要的滚动。如果内容太小,它会完全取消滚动,强制内容位于左上角。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我实现了与此完全相同的东西来滚动我自己的捏合来缩放。我怀疑您的 y 轴偏离中心的问题可能是由于视图不是全屏尺寸,或者您可能没有更改坐标以匹配比例。
我的实现计算比例因子,并将其应用于:
canvas.scale(pinchZoomScale,pinchZoomScale);
然后计算屏幕的物理尺寸(以像素为单位),将其转换为米,最后应用缩放因子,以便绘制的所有对象都正确偏移。
此实现依赖于始终了解屏幕中心的内容并锁定它,无论缩放级别如何。
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.