Canvas drawables and centered zooming - android

I'm playing with a Google tutorial to implement zooming capabilities to my canvas. I'm drawing several items onthe canvas and I'm able to move it via Canvas.translate(dx,dy) along with the items. In order for me to determine if these items are touched after the canvas translation, I take the canvas offset used for Canvas.translate(dx,dy) and subtract the touch position. Once I introduce scaling into the picture, it get's a little tricky. If I use Canvas.scale(sx,sy) and do (touchX-offsetX)/scaleFactor the translation of the touch position after the scale works perfect. However, if include a pivot position Canvas.scale(sx,sy,px,py) the code above those not work. How do I go about factoring in the pivot position as a part of my touch translation? I set my pivot position by doing the following,
public boolean onScaleBegin(ScaleGestureDetector detector) {
mScaleX = detector.getFocusX();
mScaleY = detector.getFocusY();
return true;
}
Any help would be greatly appreciated.

Simple solution
public class ScalableImageView extends View {
Matrix drawMatrix = new Matrix();
ScaleGestureDetector detector;
public ScalableImageView(Context context, AttributeSet attrs) {
#Override
public boolean onScale(ScaleGestureDetector detector) {
drawMatrix.postScale(detector.getScaleFactor(), detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY());
invalidate();
return true;
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return drawMatrix != null;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.concat(drawMatrix);
// ... draw here
// for example drawable.draw()
canvas.restore();
}
}
}

Related

Android canvas Pinch to Zoom with pivot point

I'm trying to implement Pinch to Zoom to position. I'm working on it over a week now and I can't figure it out.
Here is my problem:
I created a custom view, extending SurfaceViewand implementing ScalingGestureDetector. Everything works fine, while I haven't scaled my canvas yet or pivot point are the same as in the previous scaling. When pivot point is different than in the previous scaling, canvas jumps to different x,y position and scaling to it not to finger focus point. I can't use matrix, because I draw text and rects, not bitmap.
Here's my code
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, ScaleGestureDetector.OnScaleGestureListener {
private ScaleGestureDetector mScaleDetector;
private float focusX, focusY;
private float scale = 1f;
private void init(Context context) {
mScaleDetector = new ScaleGestureDetector(context, this);
getHolder().addCallback(this);
// other init code
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
tryDrawing(holder);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int frmt, int w, int h) {
tryDrawing(holder);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {}
public void tryDrawing() {
tryDrawing(getHolder());
}
private void drawMyStuff(final Canvas canvas) {
Log.i(TAG, "Drawing...");
for(Pixel pixel : pixels) {
drawRect(canvas, pixel);
}
}
public void tryDrawing(SurfaceHolder holder) {
Log.i(TAG, "Trying to draw...");
Canvas canvas = holder.lockCanvas();
if (canvas == null) {
Log.e(TAG, "Cannot draw onto the canvas as it's null");
} else {
canvas.scale(scale, scale, focusX , focusY);
canvas.drawColor(Color.WHITE);
drawMyStuff(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
scale *= detector.getScaleFactor();
// Don't let the object get too small or too large.
scale = Math.max(1.0f, Math.min(scale, 5.0f));
tryDrawing();
return true;
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
focusX = detector.getFocusX();
focusY = detector.getFocusY();
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}`
If someone don't understand exactly what I mean, I can create some video later.
Thanks!
I had a problem like yours in one of my projects. I tried to think out of the box and instead of trying to give a pivot for the scale.
I used the default pivot (top-left corner..) but I translated the canvas to the pivot, so the top left corner will be at the position of your pivot. After that I translated my bitmaps and rects back to their proper place. Something like this might do it:
private void drawMyStuff(final Canvas canvas) {
Log.i(TAG, "Drawing...");
for(Pixel pixel : pixels) {
drawRect(canvas, pixel); // translate your rect with x : -focusX, y: -focusY
}
}
public void tryDrawing(SurfaceHolder holder) {
Log.i(TAG, "Trying to draw...");
Canvas canvas = holder.lockCanvas();
if (canvas == null) {
Log.e(TAG, "Cannot draw onto the canvas as it's null");
} else {
// Translate the canvas (so the top left corner will be in the wanted position and you can use it as a pivot)
canvas.translate(focusX , focusY);
canvas.scale(scale, scale); // Use the default 0,0 pivot
canvas.drawColor(Color.WHITE);
drawMyStuff(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
Hope it helps..

Get location on canvas from motion event location when scaled using matrix

Im creating a drawing app, and i have a view that uses a canvas to draw onto. I'm scaling the image using a matrix, and when the image is zoomed in and there is a touch event i try getting the inverse of the matrix and then map the points using the inverse matrix to get the points that are on the canvas from the motionevent points but its not matching up how it should and wondering what I'm doing wrong to get the canvas location from the screen touch location.
public class CustomDrawableView extends View {
private Paint mBitmapPaint;
private ShapeDrawable mDrawable;
private ScaleGestureDetector detector;
Matrix drawMatrix;
Bitmap bm;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.drawBitmap(bm, drawMatrix, mBitmapPaint);
mDrawable.draw(canvas);
canvas.restore();
}
public Pair<Float,Float> GetRealValues(float eventX, float eventY){
Matrix inverse = new Matrix();
drawMatrix.invert(inverse);
float[] point = {eventX,eventY};
inverse.mapPoints(point);
return new Pair<>(point[0],point[1]);
}
#Override
//reads motions and calls methods to set starting and ending points and to
//draw canvas depending on the motion
public boolean onTouchEvent(MotionEvent event) {
//get the canvas location from the MotionEvent location
Pair<Float,Float> realvals = GetRealValues(event.getX(),event.getY());
//drawing mode where you can draw or zoom in on canvas
if(drawing) {
//not zooming mode where you draw on canvas
if (!zoom) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//do stuff
break;
case MotionEvent.ACTION_MOVE:
//do stuff
break;
case MotionEvent.ACTION_UP:
//do stuff
break;
case MotionEvent.ACTION_POINTER_UP:
break;
}
}
//zoom mode where you can scale the canvas
if (zoom) {
detector.onTouchEvent(event);
invalidate();
}
}
return true;
}
public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private float MIN_ZOOM = 1f;
private float MAX_ZOOM = 10f;
#Override
public boolean onScale(ScaleGestureDetector detector) {
Matrix transformationMatrix = new Matrix();
scaleFactor *= detector.getScaleFactor();
float tscale = detector.getScaleFactor();
if(scaleFactor < MIN_ZOOM || scaleFactor > MAX_ZOOM){
float prescaleFactor = scaleFactor/tscale;
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
tscale = scaleFactor/prescaleFactor;
}
transformationMatrix.postScale(tscale, tscale);
drawMatrix.postConcat(transformationMatrix);
return true;
}
}
Found an answer. My ondraw method needs to be like this
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
float[] mvals = new float[9];
drawMatrix.getValues(mvals);
canvas.translate(mvals[Matrix.MTRANS_X], mvals[Matrix.MTRANS_Y]);
canvas.scale(mvals[Matrix.MSCALE_X], mvals[Matrix.MSCALE_Y]);
canvas.drawBitmap(bm, 0, 0, mBitmapPaint);
mDrawable.draw(canvas);
canvas.restore();
}
I'm not sure why i need to translate and scale the canvas individually in order for the inverse matrix to give me the correct canvas points from the touch points, I'm guessing that
canvas.drawBitmap(bm, drawMatrix, mBitmapPaint);
rounds the matrix values to ints because the canvas points i got from mapping the points on the inverse matrix were just slightly off when drawing the canvas that way.

Scalefactor zoom amount on WebView double tap

I'm implementing a canvas on a WebView and I need to have the canvas scale according to the zoom level of the WebView. Combining canvas.scale() with a SimpleOnScaleListener does the trick. For the double tap, I'm using a GestureDetector which detects when there's a double tap and sets the scale factor accordingly. I'm merely guessing how much it scales when there is a double tap: Setting the scale factor to 2.2f works on a 7in tablet but it scales too much for a 10in.
Is there anyway to programmatically figure out how much the WebView has zoomed in on double tap? I basically need the canvas to synchronize zooming with the WebView.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.scale(mScaleFactor, mScaleFactor, 0, 0);
canvas.drawPath(drawPath, drawPaint);
invalidate();
}
public class ScaleGestureListener extends SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
// Don't let the object get too small or too large.
if(Deal.on == false) {
mScaleFactor *= detector.getScaleFactor();
mScaleFactor = Math.max(1f, Math.min(mScaleFactor, 30.0f));
System.out.println("mScaleFactor: " + mScaleFactor);
}
invalidate();
return true;
}
}
public class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onDoubleTap(MotionEvent e) {
if (Deal.on == false) {
if (mScaleFactor > 1.0f) {
mScaleFactor = 1.0f;
}else {
mScaleFactor = 2.2f;
}
}
return true;
}
}
You can catch zoom change by extending WebViewClient
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
setWebViewClient(new WebViewClient() {
#Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
currentScale = newScale;
}
});
}

How to Move a ShapeDrawable in Canvas on Touch Events

I am trying to implement a Drawing Application in Android. Where the user should be able to select and move the drawn shapes.
Currently i have statically drawn some rects and text on my Drawing Canvas:
View mDrawingCanvas = new View(mContext)
{
ShapeDrawable rectangle;
#Override
public boolean isFocused() {
// TODO Auto-generated method stub
Log.d(TAG, "View's On focused is called !");
return super.isFocused();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
return super.onTouchEvent(event);
}
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
// Work out current total scale factor
// from source to view
final float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
//Custom View
rectangle = new ShapeDrawable(new RectShape());
rectangle.getPaint().setColor(Color.GRAY);
rectangle.getPaint().setStyle(Paint.Style.FILL_AND_STROKE);
rectangle.getPaint().setStrokeWidth(3);
rectangle.setBounds((int)(50*scale), (int)(30*scale), (int)(200*scale), (int)(150*scale));
rectangle.draw(canvas);
rectangle.getPaint().setColor(Color.BLUE);
rectangle.getPaint().setStyle(Paint.Style.FILL_AND_STROKE);
rectangle.getPaint().setStrokeWidth(3);
rectangle.setBounds((int)(200*scale), (int)(200*scale), (int)(400*scale), (int)(350*scale));
rectangle.draw(canvas);
}
};
I want to select (draw borders on the selected shape) and move the drawn Shapes in onTouch events of the drawing canvas.
Can some one please guide me about this, any help is Highly Appreciated.
This answer has demonstrated the Shape Moving Methodology that i was looking for.
And my problem is solved now. The Link is :
Drag and move a circle drawn on canvas
You should save the X and Y positions in the touch event and use them when drawing your shapes.
Below is a very basic example of how to do this, but you need to improve it (check if the touch is inside the object and only change values for that object)
Example:
public class DrawTest extends View {
private static final String TAG = "Desenho";
private ShapeDrawable rectangle;
private Paint paint;
private float currX, currY;
private Rect blue, gray;
public DrawTest(Context context) {
super(context);
currX = 1;
currY = 1;
gray = new Rect(50,30,200,150);
blue = new Rect(200,200,400,350);
paint = new Paint();
rectangle = new ShapeDrawable(new RectShape());
}
#Override
public boolean isFocused() {
Log.d(TAG, "View's On focused is called !");
return super.isFocused();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
currX = event.getX();
currY = event.getY();
invalidate();
Log.d(TAG, "View's On touch is called! X= "+currX + ", Y= "+currY);
return super.onTouchEvent(event);
}
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
//Custom View
rectangle.getPaint().setColor(Color.GRAY);
rectangle.getPaint().setStyle(Paint.Style.FILL_AND_STROKE);
rectangle.getPaint().setStrokeWidth(3);
gray.set((int)(50+currX), (int)(30+currY), (int)(200+currX), (int)(150+currY));
rectangle.setBounds(gray);
gray = rectangle.getBounds();
rectangle.draw(canvas);
rectangle.getPaint().setColor(Color.BLUE);
rectangle.getPaint().setStyle(Paint.Style.FILL_AND_STROKE);
rectangle.getPaint().setStrokeWidth(3);
blue.set((int)(200+currX), (int)(200+currY), (int)(400+currX), (int)(350+currY));
rectangle.setBounds(blue);
blue = rectangle.getBounds();
rectangle.draw(canvas);
}
}

how to get position(x and y coordinates) of circle drwan on canvas in android?

I have two doubts in canvas android.
They are explained below:
Firstly, I had drawn a row of circles on canvas, i want to capture the x and y coordinates of the circle(only on one circle)drawn so that I can draw a bitmap over the circle(in center).
Secondly, I want to imply touch event on circle i.e I want them to change color when some one touch them Can anybody help me with this?
for #2:
calculate the distance between the center of your point and the touch event - if the distance is smaller than the radius of your circle - you pressed on the circle
First you should create GridSector class for your "circle zone".
public class GridSector {
private int x = 0;
private int y = 0;
private Rect rect = new Rect(0, 0, 0, 0);
public GridSector(int x, int y, Rect rect) {
this.x = x;
this.y = y;
this.rect = rect;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Rect getRect() {
return rect;
}
public void setRect(Rect rect) {
this.rect.set(rect.left, rect.top, rect.right, rect.bottom);
}
}
Then Create view what you wanna touch.
public class GridSectorsView extends View {
GridSector currentSelectedSector;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas); // draw your circles;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
invalidate(currentSelectedSector.getRect()); // update your circles (call onDraw Function ) ;
}
}
}

Categories

Resources