centered zoom in custom view - calculating canvas coordinates - android

I am working on my first "real" Android application, a graphical workflow editor. The drawing is done in a custom class, that is a subclass of View.At the moment my elements are rectangles, which are drawn on a canvas. To detect actions on elements I compare the coordinates and check for elements on the touch location.
To implement a zoom gesture I tried http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
With the 4 argument canvas.scale(...) function the centered zooming works well, but I lose the ability to calculate the canvas coordinates using the offset with mPosX and mPosY to detect if the touch after a zoom is on an element.
I tried to change the example in the blogpost above to center the canvas on the zoom gesture with:
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor, mScalePivotX, mScalePivotY);
//drawing ....
canvas.restore();
I did not find any examples on how this could be done without losing the reference offset to calculate the coordinates. Is there an easy workaround? I tried to calculate the offset with the gesture center and the scaling factor, but failed :/
I have seen that other examples which use an ImageView often use a Matrix to transform the image. Could this be done with a custom View and a Canvas? If yes, how can I get the x and y offset to check the coordinates?
Also, if my ideas are completely wrong, I would be very happy to see some examples on how this is done properly.
Thx! ;)

Perhaps the following code will help you to calculate coordinates with the gesture center and the scaling factor. I use this method in my class representing opengl-sprite.
void zoom(float scale, PointF midPoint) {
if (zoomFactor == MAX_ZOOM_FACTOR && scale > 1) return;
if (zoomFactor == MIN_ZOOM_FACTOR && scale < 1) return;
zoomFactor *= scale;
x = (x - midPoint.x) * scale + midPoint.x;
y = (y - height + midPoint.y) * scale + height - midPoint.y;
if (zoomFactor >= MAX_ZOOM_FACTOR) {
zoomFactor = MAX_ZOOM_FACTOR;
} else if (zoomFactor < MIN_ZOOM_FACTOR) {
zoomFactor = MIN_ZOOM_FACTOR;
x = 0;
y = 0;
}
}
X and Y coordinates are processed in different ways, because of distinction between directions of opengl coordinate system (right and up) and midPoint's coordinate system (right and down). midPoint is taken from MotionEvents coordinates.
All other operations are understandable, i think.
Hope it will help you.

Related

how to find the biggest possible Y point given X in Region in Android?

I have a virtual ruler being drawn on the screen. I want to be able to draw a straight line like the blue line above when touch event happen within the grown rectangle. but because touch can't be 100% straight, the movement might be like the red line. that's why I set a rectangle to listen to all the nearby touch events then draw a blue line.
I currently have
mRulerRect.set(mRulerCenter.x - mRulerBitmap.getWidth() / 2,
mRulerCenter.y - mRulerBitmap.getHeight()),
mRulerCenter.x + mRulerBitmap.getWidth() / 2,
mRulerCenter.y);
mPath.addRect(mRulerRect, Path.Direction.CCW);
mRulerMatrix.setRotate(mRulerAngle, mRulerCenter.x, mRulerCenter.y);
mPath.transform(mRulerMatrix);
mRegions.setPath(mPath, new Region(mRulerRect));
then I check if the touch even happen within brown rectangle by mRegions.contains(x,y). works perfect so far for touch detection, but the problem I have now is how to draw a straight line. I tried to fix X point then calculate Y. it works fine when ruler is horizontal then starts to behave very weird when turning from horizontal to vertical. I'm out of idea how to accomplish this. Please help! thank you.
Things that you know:
The center of that brown rect is mRulerCenter.x, mRulerCenter.y
The line that you want to draw pass through that point
The angle of the line is mRulerAngle
We're missing just one element, which is, the length of the line we want to draw. That's probably going to be a portion of the ruler's width and it should be very easy to compute mRulerRect.width() * someFactor
Now, we want to know what are the start and the end of the line, we can compute that with trigonometry functions
float halfLineLength = mRulerRect.width() * someFactor;
float startAngle = (float) Math.toRadians(mRulerAngle);
float endAngle = (float) Math.toRadians(mRulerAngle + 180);
float startX = mRulerCenter.x + (float) Math.cos(startAngle) * halfLineLength;
float startY = mRulerCenter.y + (float) Math.sin(startAngle) * halfLineLength;
float endX = mRulerCenter.x + (float) Math.cos(endAngle) * halfLineLength;
float endY = mRulerCenter.y + (float) Math.sin(endAngle) * halfLineLength;
and then draw your line from (startX, startY) to (endX, endY), actually doesn't really matter which is start and which is end

How to check if a touch event is outside or inside of a region in android

I have an image of a basketball pitch or any other pitch, and I want to check if the user checks outside or inside of 3 points area how can I achieve this? Shall I use predefined points of x,y?
Well if that area is a perfect half-circle, which I think it is, then the formula would be quite easy. You get X and Y coordinates of a touch event whichever way you prefer (that's not the issue here, use GestureDetector for example). I'll just give you an example of the isIn3PointArea() method:
private boolean isIn3PointArea(float touchX, float touchY, float centerX, float centerY, float r){
float x = touchX - centerX;
float y = touchY - centerY;
return (touchX > centerX && Math.sqrt(x*x+y*y) < r);
}
Explanation:
This is the method for the left 3 point area, for the right one you'd just need to swap out > operator with < in the touchX > centerX part. It's quite logical, your point needs to be less than r (which is the radius of your circle) from the center point (which has x coordinate of 0) and y, well whatever you give it, I'm not quite sure what you use to draw the court. Also it needs the touchX to be right of ( greater than > ) the centerX because if it's not, the touch is out of the field. The reverse logic applies to the right 3 point area.
The only thing that's up to you to deduce is what parameters to give to this method (you didn't share any code so I cannot know what your radius is or what are the coordinates of the court).
You can do this using x and y but believe me it will get very messy very soon. What I would suggest is use different image Views for different regions and add onClickListeners to the each of them . For this you will have to ask for different assets for each element in your court.

Draw on custom view with zoom not working properly

When drawing on view without zoom it works fine. See the screenshot
But when zooming and then drawing on view It is slightly up or down. See the screenshot
Here is my code for Custom View http://www.paste.org/78026 and for zoom http://www.paste.org/78027 and my xml http://www.paste.org/78028
Please can you tell me where I am wrong
Finally after lot of searching I found how to get relative X,Y when View is zoomed. It may be helpful to someone
// Get the values of the matrix
float[] values = new float[9];
matrix.getValues(values);
// values[2] and values[5] are the x,y coordinates of the top left corner of the drawable image, regardless of the zoom factor.
// values[0] and values[4] are the zoom factors for the image's width and height respectively. If you zoom at the same factor, these should both be the same value.
// event is the touch event for MotionEvent.ACTION_UP
float relativeX = (event.getX() - values[2]) / values[0];
float relativeY = (event.getY() - values[5]) / values[4];

Any ways of making pinch to zoom smoother

Is there any ways of implementing smooth pinch to zoom without using matrix?
I am building drawing app and I want it to have pinch to zoom.
I get pivot point for scaling with:
centerposX = mScaleDetector.getFocusX();
centerposY = mScaleDetector.getFocusY();
and for scaling then I use:
canvas.scale(scaleFactor, scaleFactor, centerposX, centerposY);
But the problem is that it immediately centers view at the pivot point and them zooms it, rather than using it as a guide for centering.
I've seen that this problem has been solved by using matrix, but I don't want to use them as I need to keep track of offsets, ZoomTranslations which are calculated from centerposX/Y and scaleFactor to put drawings where they belong on screen.
So is there any way to solve this pivot point problem smoothly?
Thanks!
After a week I understood that I need to use matrix and get absolute coordinates on the screen, so I used Gesture detector to set matrix scale
matrix.postScale(mScaleFactor, mScaleFactor, focusX, focusY);
In my onDraw method I used canvas.concat(matrix); so not only Bitmap, but WHOLE canvas get's matrix transformation, and to get real screen coordinates I used a method i found on stack-overflow:
public float[] getAbsolutePosition(float Ax, float Ay) {
matrix.getValues(m);
float x = width - ((m[Matrix.MTRANS_X] - Ax) / m[Matrix.MSCALE_X])
- (width - getTranslationX());
float y = height - ((m[Matrix.MTRANS_Y] - Ay) / m[Matrix.MSCALE_X])
- (height - getTranslationY());
return new float[] { x, y };
}
I call this every time in my onTouchEvent() method, supplying event.getX and event.getY() as arguments.
After that, everything is easy peasy, also because of this beautiful method I've got rid of ~4-5 variables which I had to use to find real touch location.

Simultaneous rotation, translation, and scaling of bitmap in android canvas

I'm trying to show a picture, which can be zoomed and panned, and that rotates with a compass reading. With the code below, all three actions kind of work, but they influence each other.
Here is what I want to achieve:
1. Rotate around the center of the screen
2. Scale leaving the same part of the picture in the center
3. Pan to the desired spot in the picture
Here is what is actually happening with the code below:
1. Rotation works as intended, around the center of the screen
2. Scaling works, but it scales around the center of the picture
3. Translation only works as intended if angle is zero, otherwise it's moving in a wrong direction
// the center of the view port
float centerX = screen.right/2;
float centerY = screen.bottom/2;
Matrix m = new Matrix();
m.preRotate(angle, centerX, centerY);
m.preScale(mScaleFactor, mScaleFactor, centerX, centerY);
// scaling the amount of translation,
// rotating the translation here gave crazy results
float x = mPosX / mScaleFactor;
float y = mPosY / mScaleFactor;
m.preTranslate(x, y);
canvas.drawBitmap(pic, m, null);
If I translate first, and later rotate, the translation works as intended but the rotation is not around the center of the view port anymore.
How can I apply all three transformations, without them influencing each other?
I'm not sure about the scaling around the centre of the image, but as for the translation being in the wrong direction is it not as a consequence of you rotating the image but not the translations? Maybe try something like this:
float x = (mPosX*(cos(angle) + sin(angle)) / mScaleFactor;
float y = (mPosY*(cos(angle) - sin(angle)) / mScaleFactor;
m.preTranslate(x, y);
Also does the matrix object have a method to apply an affine transformation directly? Because then you won't need to think about the order of operations.
The affine transformation might look something like this:
M = |mScaleFactor*cos(angle) sin(angle) x|
| -sin(angle) mScaleFactor*cos(angle) y|
| 0 0 1|
But this will rotate around the corner of the image so you need to use the preTranslate() function first like so:
Mt.preTranslate(-centerX,-centerY);
And APPLY Mt to pic before applying M and then after you need to apply -Mt

Categories

Resources