I currently can place and drag circles around on a canvas, but I'm having trouble understanding how to allow the user to scale circles with a pinch gesture.
The circles are drawn with 'canvas.drawCircle(x,y,radius,paint)'. My initial idea was to listen for the pinch gesture with a custom 'SimpleOnScaleGestureListener', and the multiply the radius of the selected circle by a scale factor. This has produced some pretty unpredictable results, with circles being scaled to tiny or epic proportions from a quick tap with two fingers.
Here's my custom listener:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector){
mScaleFactor *= detector.getScaleFactor()
if (isCircleToolSelected) {
// Checks if gesture was made inside of an existing circle
// CircleDrawing is a private class that holds x,y,radius,paint values
CircleDrawing circle = getCircle(detector.getFocusX(),
detector.getFocusY());
if (circle != null) {
touchedCircle.radius *= mScaleFactor;
}
}
return true;
}
}
This detects the gesture and knows whether the pinch was within a circle on my canvas, but the scaling that is applied is just totally out of control and unusable.
I tried only scaling when the difference between the current and previous scale factors is beyond a certain threshold value, but that just made the scaling less predictable and more choppy.
If anyone has implemented something similar, I would love some direction.
Answering my own question in case someone else gets themselves convinced that you have to scale everything using a ScaleFactor. For basic scaling of a circle, you just need to find the difference between the current and previous value of getCurrentSpan().
This is the code that I wound up using:
//Used elsewhere to detect if we've touched an existing circle
CircleDrawing touchedCircle;
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private float lastSpan;
private float lastRadius;
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
//If the user has not already selected a circle
if (null == touchedCircle) {
// Check if we started scale gesture within existing circle
touchedCircle = getCircle(detector.getFocusX(), detector.getFocusY());
}
//If a circle has been selected, save radius and span
if (touchedCircle != null) {
lastRadius = touchedCircle.radius;
lastSpan = detector.getCurrentSpan();
}
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector){
//If the user has the shapes tool selected - handled elsewhere
if (isShapesSelected) {
//If the user is touching a circle
if (touchedCircle != null) {
//Value used to calculate new radius
float currentSpan = detector.getCurrentSpan();
float diameterDiff = currentSpan - lastSpan;
//Keep track of scaling direction for min and max sizes
boolean scalingUp = diameterDiff > 0;
//Only scale within radius sizes between 150 and 500
//Likely will change these values to relate to size of canvas
if ((!scalingUp && (lastRadius >= 150)) || (scalingUp && (lastRadius < 500))) {
//Calculate and set new radius
touchedCircle.radius += (diameterDiff)/2;
//Save radius and span for next frame
lastRadius = touchedCircle.radius;
lastSpan = detector.getCurrentSpan();
}
}
}
return true;
}
}