So, I have a custom BoardView class extends View. I implemented drawing board, lines and drawing "O" drawable when user press on the cell.
But, I could not implement following problems correctly:
1. Zoom BoardView when user doing pinch(multi touch).
2. Scroll BoardView to left, right, top, bottom if BoardView bigger than BoardView initial width or height.
3. Find right cell coordinate when user pressed on the cell after zooming or scrolling.
This is my first game project, please help me if anybody know how to solve this problem.
I tried but did not work properly. BoardView width equal width screen width and BoardView height equal to BoardView width. It is square board view.
I give 200 bounty to implementing this problem.
Here is my project, everyone can download and edit: https://drive.google.com/file/d/0BxNIUTd_m1x8cUQ2NGpSMDBuVVE/view?usp=sharing
Github: https://github.com/boyfox/GameTicTacToe
BoardView code: http://pastie.org/10109253 or http://pastebin.com/TRU8Ybds
I found solution self, but need improving code, you can answer to my question with your solution!
Could you please move your code to github? It would be much easier to download, edit and propose changes.
If you're looking for a generic implementation of two finger zoom/rotate, take a look at my game (https://github.com/ZieIony/Gravity). The most interesting part is the GamePanel view and the dispatchTouchEvent method:
private PointF prevPos = new PointF(), prevPos2 = new PointF();
float scale = 1;
final float MIN_SCALE = 0.2f, MAX_SCALE = 2.0f;
float rotation = 0;
Matrix matrix = new Matrix();
private float prevDist;
public boolean dispatchTouchEvent(android.view.MotionEvent event) {
if (event.getPointerCount() == 2) {
float d = dist(event.getX(0), event.getY(0), event.getX(1),
event.getY(1));
float pivotX = (event.getX(0) + event.getX(1)) / 2;
float pivotY = (event.getY(0) + event.getY(1)) / 2;
float prevPivotX = (prevPos.x + prevPos2.x) / 2;
float prevPivotY = (prevPos.y + prevPos2.y) / 2;
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float newScale = scale * d / prevDist;
newScale = Math.max(MIN_SCALE,
Math.min(newScale, MAX_SCALE));
float scaleFactor = newScale / scale;
scale = newScale;
matrix.postScale(scaleFactor, scaleFactor, pivotX, pivotY);
float prevAngle = (float) Math.atan2(
prevPos.x - prevPos2.x, prevPos.y - prevPos2.y);
float angle = (float) Math.atan2(
event.getX(0) - event.getX(1), event.getY(0)
- event.getY(1));
rotation += prevAngle - angle;
matrix.postRotate(
(float) ((prevAngle - angle) * 180.0f / Math.PI),
pivotX, pivotY);
matrix.postTranslate(-prevPivotX + pivotX, -prevPivotY
+ pivotY);
}
prevPos.x = event.getX(0);
prevPos.y = event.getY(0);
prevPos2.x = event.getX(1);
prevPos2.y = event.getY(1);
prevDist = d;
}
return true;
}
This method produces a transformation matrix, which you should use for drawing.
protected void dispatchDraw(Canvas canvas) {
canvas.save();
canvas.setMatrix(matrix);
// do your drawing here
canvas.restore();
}
Have a look at my answer here. If the answer seems satisfactory have a look at my github code. I think it is not difficult to implement your third point
"3. Find right cell coordinate when user pressed on the cell after zooming or scrolling.",
because the scaleFactor is available in MyView.java and scroll offsets could be obtained by getScrollX() and getScrollY(), by just doing simple math.
Related
I have used this PhotoView library for custom ImageView. I want to scale the image at particular point. Here is the method I found is setScale(float scale, float focalX, float focalY, boolean animate)
I am wondering what can I pass a value of focalX and focalY , I have X and Y coordinate which I am passing currently and it scales to very different position.
Here is a snippet,
intResultX = intTotalX / intArraySize;
intResultY = intTotalY / intArraySize;
mMap.setScale(5, intResultX, intResultY, true);
To zoom at particular XY coordinate in Imageview you can pass a value of focalX and focalY along with scale (must be between max scale an min scale of PhotoView) and boolean value to set animation.
Code to get max-min scales:
mPhotoView.getMinimumScale();
mPhotoView.getMaximumScale();
focalX and focalY It can be any points on screen, here I have taken two examples one is center of the screen and other is top-left corner. following is the code for both cases.
Code:
Random r = new Random();
float minScale = mPhotoView.getMinimumScale();
float maxScale = mPhotoView.getMaximumScale();
float randomScale = minScale + (r.nextFloat() * (maxScale - minScale));
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
int centerX=width/2;
int centerY =height/2;
/*pass a value of focalX and focalY to scale image to center*/
//mPhotoView.setScale(randomScale, centerX, centerY, true);
/*pass a value of focalX and focalY to scale image to top left corner*/
mPhotoView.setScale(randomScale, 0, 0, true);
Set zoom to the specified scale. Image will be centered around the point
(focusX, focusY). These floats range from 0 to 1 and denote the focus point
as a fraction from the left and top of the view. For example, the top left
corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) {
/*setZoom can be called before the image is on the screen, but at this point,
image and view sizes have not yet been calculated in onMeasure. Thus, we should
delay calling setZoom until the view has been measured.*/
if (!onDrawReady) {
delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType);
return;
}
if (scaleType != mScaleType) {
setScaleType(scaleType);
}
resetZoom();
scaleImage(scale, viewWidth / 2, viewHeight / 2, true);
matrix.getValues(m);
m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f));
m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f));
matrix.setValues(m);
fixTrans();
setImageMatrix(matrix);
}
Hope this helps. Happy coding.
i think this question has been asked quite often, but I couldn`t find an appropriate solution for my implementation. I built an custom imageview with an onScaleListener and an onGestureListener that scales and pans the containing image. The scaling is done with a matrix scaling. The function looks like that:
#Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
scaleFactor *= scaleGestureDetector.getScaleFactor();
scaleFactor = Math.max(initScale, Math.min(scaleFactor, initScale + 3.0f));
matrix = getImageMatrix();
matrix.setScale(scaleFactor, scaleFactor);
matrix.getValues(values);
matrix.postTranslate(-values[Matrix.MTRANS_X] + Math.max(0, centerX - centerImageX),
-values[Matrix.MTRANS_Y] + Math.max(0, centerY - centerImageY));
setImageMatrix(matrix);
}
postTranslate() is needed to center the image if needed. To finish this I need to scroll to (scrollTo(x,y)) the position where the focus of the scaling gesture stays in the same position on the screen. At the end it should look like scaling in a webview.
Can anybody help me with this?
When I use:
float scrollPosX = ((scrollwidth) * ((getScrollX() + touchX) / imagewidth));
float scrollPosY = ((scrollheight) * ((getScrollY() + touchY) / imageheight));
it will work for the first scaling, but when scaling in a scaled image it will scroll to the relative position. I think it is all related to the fact that I only get the touch position with getScrollX() and getScrollY() [it`s difficult to explain]
Now I found a solution that works for me. In my case I have to use the scaling step instead of the scaling factor.
scrollTo((int) (getScrollX() - x + (scaleGestureDetector.getScaleFactor() * x) ),
(int) (getScrollY() - y + (scaleGestureDetector.getScaleFactor() * y) ));
with x and y as the touch position on the scaled image.
I've just stated learning Opengl ES 2.0 in android and I run into a problem that i don't know how to solve.
I want to create a large plane field, I created it, put on texture but here comes my problem
It doesn't draw all of it it only displays about 10 unit on the Z axe.. X i fine.
So i want to create a big square but it displays a rectangle. It is like someone took a scissors and cut it off a certaint Z coordinate.
I don't even know what part of my code should i put it here, shader ? plane coordinates ? camera settings ?
Thank you for your patient.
It sounds like your plane is getting clipped by the frustum or viewing volume. That is typically set by either glOrtho() or glPerspective(). Try increasing the distance between the near and far plane parameters to these functions.
If you are relying on a default frustum provided by Android, you may have to construct your own frustum, which would look something like this for glOrtho():
typedef struct
{
float f0;
float f1;
float f2;
float f3;
float f4;
float f5;
float f6;
float f7;
float f8;
float f9;
float f10;
float f11;
float f12;
float f13;
float f14;
float f15;
} Mat4;
void Ortho(Mat4 * pMat4, float left, float top, float right, float bottom, float nearPlane, float farPlane)
{
float rcplmr = 1.0f / (left - right);
float rcpbmt = 1.0f / (bottom - top);
float rcpnmf = 1.0f / (nearPlane - farPlane);
pMat4->f0 = -2.0f * rcplmr;
pMat4->f1 = 0.0f;
pMat4->f2 = 0.0f;
pMat4->f3 = 0.0f;
pMat4->f4 = 0.0f;
pMat4->f5 = -2.0f * rcpbmt;
pMat4->f6 = 0.0f;
pMat4->f7 = 0.0f;
pMat4->f8 = 0.0f;
pMat4->f9 = 0.0f;
pMat4->f10 = -2.0f * rcpnmf;
pMat4->f11 = 0.0f;
pMat4->f12 = (right + left) * rcplmr;
pMat4->f13 = (top + bottom) * rcpbmt;
pMat4->f14 = (nearPlane + farPlane) * rcpnmf;
pMat4->f15 = 1.0f;
}
Please see each section below for a description of my problem described in three separate ways. Hopefully should help people to answer.
Problem: How do you find a pair of coordinate expressed in canvas/userspace when you only have the coordinate expressed in terms of a zoomed image, given the original scale point & scale factor?
Problem in practice:
I'm currently trying to replicate the zoom functionality used in apps such as the gallery / maps, when you can pinch to zoom/zoom out with the zoom moving towards the midpoint of the pinch.
On down I save the centre point of the zoom (which is in X,Y coordinates based on the current screen). I then have this function act when a "scale" gesture is detected:
class ImageScaleGestureDetector extends SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
if(mScaleAllowed)
mCustomImageView.scale(detector.getScaleFactor(), mCenterX, mCenterY);
return true;
}
}
The scale function of the CustomImageView look like this:
public boolean scale(float scaleFactor, float focusX, float focusY) {
mScaleFactor *= scaleFactor;
// Don't let the object get too small or too large.
mScaleFactor = Math.max(MINIMUM_SCALE_VALUE, Math.min(mScaleFactor, 5.0f));
mCenterScaleX = focusX;
mCenterScaleY = focusY;
invalidate();
return true;
}
The drawing of the scaled image is achieved by overriding the onDraw method which scales the canvas around the centre ands draw's the image to it.
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mCenterScaleX, mCenterScaleY);
canvas.scale(mScaleFactor, mScaleFactor);
canvas.translate(-mCenterScaleX, -mCenterScaleY);
mIcon.draw(canvas);
canvas.restore();
}
This all works fine when scaling from ScaleFactor 1, this is because the initial mCenterX and mCenterY are coordinates which are based on the device screen. 10, 10 on the device is 10, 10 on the canvas.
After you have already zoomed however, then next time you click position 10, 10 it will no longer correspond to 10, 10 in the canvas because of the scaling & transforming that has already been performed.
Problem in abstraction:
The image below is an example of a zoom operation around centre point A. Each box represents the position and size of the view when at that scale factor (1, 2, 3, 4, 5).
In the example if you scaled by a factor of 2 around A then you clicked on position B, the X, Y reported as B would be based on the screen position - not on the position relative to 0,0 of the initial canvas.
I need to find a way of getting the absolute position of B.
So, after redrawing the problem I've found the solution I was looking for. It's gone through a few iteration's but here's how I worked it out:
B - Point, Center of the scale operation
A1, A2, A3 - Points, equal in user-space but different in canvas-space.
You know the values for Bx and By because they are always constant no matter what the scale factor (You know this value in both canvas-space and in user-space).
You know Ax & Ay in user-space so you can find the distance between Ax to Bx and Ay to By. This measurement is in user-space, to convert it to a canvas-space measurement simply divide it by the scale factor. (Once converted to canvas-space, you can see these lines in red, orange and yellow).
As point B is constant, the distance between it and the edges are constant (These are represented by Blue Lines). This value is equal in user-space and canvas-space.
You know the width of the Canvas in canvas-space so by subtracting these two canvas space measurements (Ax to Bx and Bx to Edge) from the total width you are left with the coordinates for point A in canvas-space:
public float[] getAbsolutePosition(float Ax, float Ay) {
float fromAxToBxInCanvasSpace = (mCenterScaleX - Ax) / mScaleFactor;
float fromBxToCanvasEdge = mCanvasWidth - Bx;
float x = mCanvasWidth - fromAxToBxInCanvasSpace - fromBxToCanvasEdge;
float fromAyToByInCanvasSpace = (mCenterScaleY - Ay) / mScaleFactor;
float fromByToCanvasEdge = mCanvasHeight - By;
float y = mCanvasHeight - fromAyToByInCanvasSpace - fromByToCanvasEdge;
return new float[] { x, y };
}
The above code and image describe when you're clicking to the top left of the original centre. I used the same logic to find A no matter which quadrant it was located in and refactored to the following:
public float[] getAbsolutePosition(float Ax, float Ay) {
float x = getAbsolutePosition(mBx, Ax);
float y = getAbsolutePosition(mBy, Ay);
return new float[] { x, y };
}
private float getAbsolutePosition(float oldCenter, float newCenter, float mScaleFactor) {
if(newCenter > oldCenter) {
return oldCenter + ((newCenter - oldCenter) / mScaleFactor);
} else {
return oldCenter - ((oldCenter - newCenter) / mScaleFactor);
}
}
Here is my solution based on Graeme's answer:
public float[] getAbsolutePosition(float Ax, float Ay) {
MatrixContext.drawMatrix.getValues(mMatrixValues);
float x = mWidth - ((mMatrixValues[Matrix.MTRANS_X] - Ax) / mMatrixValues[Matrix.MSCALE_X])
- (mWidth - getTranslationX());
float y = mHeight - ((mMatrixValues[Matrix.MTRANS_Y] - Ay) / mMatrixValues[Matrix.MSCALE_X])
- (mHeight - getTranslationY());
return new float[] { x, y };
}
the parameters Ax and Ay are the points which user touch via onTouch(), I owned my static matrix instance in MatrixContext class to hold the previous scaled/translated values.
Really sorry this is a brief answer, in a rush. But I've been looking at this recently too - I found http://code.google.com/p/android-multitouch-controller/ to do what you want (I think - I had to skim read your post). Hope this helps. I'll have a proper look tonight if this doesn't help and see if I can help further.
So I have an ImageView using a Matrix to scale the Bitmap I'm displaying. I can double-tap to zoom to full-size, and my ScaleAnimation handles animating the zoom-in, it all works fine.
Now I want to double-tap again to zoom out, but when I animate this with ScaleAnimation, the ImageView does not draw the newly exposed areas of the image (as the current viewport shrinks), instead you see the portion of visible image shrinking in. I have tried using ViewGroup.setClipChildren(false), but this only leaves the last-drawn artifacts from the previous frame - leading to an trippy telescoping effect, but not quite what I was after.
I know there are many zoom-related questions, but none cover my situation - specifically animating the zoom-out operation. I do have the mechanics working - ie aside from the zoom-out animation, double-tapping to zoom in and out works fine.
Any suggestions?
In the end I decided to stop using the Animation classes offered by Android, because the ScaleAnimation applies a scale to the ImageView as a whole which then combines with the scale of the ImageView's image Matrix, making it complicated to work with (aside from the clipping issues I was having).
Since all I really need is to animate the changes made to the ImageView's Matrix, I implemented the OnDoubleTapListener (at the end of this post - I leave it as an "exercise to the reader" to add the missing fields and methods - I use a few PointF and Matrix fields to avoid excess garbage creation). Basically the animation itself is implemented by using View.post to keep posting a Runnable that incrementally changes the ImageView's image Matrix:
public boolean onDoubleTap(MotionEvent e) {
final float x = e.getX();
final float y = e.getY();
matrix.reset();
matrix.set(imageView.getImageMatrix());
matrix.getValues(matrixValues);
matrix.invert(inverseMatrix);
doubleTapImagePoint[0] = x;
doubleTapImagePoint[1] = y;
inverseMatrix.mapPoints(doubleTapImagePoint);
final float scale = matrixValues[Matrix.MSCALE_X];
final float targetScale = scale < 1.0f ? 1.0f : calculateFitToScreenScale();
final float finalX;
final float finalY;
// assumption: if targetScale is less than 1, we're zooming out to fit the screen
if (targetScale < 1.0f) {
// scaling the image to fit the screen, we want the resulting image to be centred. We need to take
// into account the shift that is applied to zoom on the tapped point, easiest way is to reuse
// the transformation matrix.
RectF imageBounds = new RectF(imageView.getDrawable().getBounds());
// set up matrix for target
matrix.reset();
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
matrix.postScale(targetScale, targetScale);
matrix.mapRect(imageBounds);
finalX = ((imageView.getWidth() - imageBounds.width()) / 2.0f) - imageBounds.left;
finalY = ((imageView.getHeight() - imageBounds.height()) / 2.0f) - imageBounds.top;
}
// else zoom around the double-tap point
else {
finalX = x;
finalY = y;
}
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final long startTime = System.currentTimeMillis();
final long duration = 800;
imageView.post(new Runnable() {
#Override
public void run() {
float t = (float) (System.currentTimeMillis() - startTime) / duration;
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = interpolator.getInterpolation(t);
float tempScale = scale + interpolatedRatio * (targetScale - scale);
float tempX = x + interpolatedRatio * (finalX - x);
float tempY = y + interpolatedRatio * (finalY - y);
matrix.reset();
// translate initialPoint to 0,0 before applying zoom
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
// zoom
matrix.postScale(tempScale, tempScale);
// translate back to equivalent point
matrix.postTranslate(tempX, tempY);
imageView.setImageMatrix(matrix);
if (t < 1f) {
imageView.post(this);
}
}
});
return false;
}