I'm trying to draw a resizable circle on top of my google map, which the user will be able to expand or shrink using touch gestures (for example to shrink the circle the user will pinch the circle on the screen, I want it to work like zooming in/out option in the map, only that just the circle will get bigger/smaller on the map).
Is this possible to implement? And if so how would I go about accomplishing that.
I searched Google and Stackoverflow and as I understand, I need to add a custom view on top of my map fragment and implement OnTouchListener to this View (and that is just the beginning). Can some one please advise on what to do or how to proceed?
I can draw a circle on the map but I don't know how to get it to respond to touch events.
Thanks in advance.
Based on your question, you want to overlay a "pinch listening" view that draws an oval shape based on the pinch. I made some poorly-tested code for this purpose, adapt it as you need:
MainLayout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Replace the ImageView with your MapView or whatever you are
overlaying with the oval shape -->
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#F00" />
<com.example.testapp.CircleTouchView
android:id="@+id/circle_drawer_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
CircleTouchView:
public class CircleTouchView extends View {
private static final int MODE_PINCH = 0;
private static final int MODE_DONT_CARE = 1;
ShapeDrawable mCircleDrawable;
int mTouchMode = MODE_DONT_CARE;
public CircleTouchView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mCircleDrawable = new ShapeDrawable(new OvalShape());
mCircleDrawable.getPaint().setColor(0x66FFFFFF);
}
public CircleTouchView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleTouchView(Context context) {
this(context, null, 0);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mCircleDrawable.setBounds(0, 0, 0, 0);
invalidate();
break;
case MotionEvent.ACTION_POINTER_DOWN:
prepareCircleDrawing(event);
break;
case MotionEvent.ACTION_MOVE:
if (mTouchMode == MODE_PINCH) {
prepareCircleDrawing(event);
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (event.getActionIndex() <= 1) {
mTouchMode = MODE_DONT_CARE;
}
break;
default:
super.onTouchEvent(event);
}
return true;
}
private void prepareCircleDrawing(MotionEvent event) {
int top, right, bottom, left;
int index = event.getActionIndex();
if (index > 1) {
return;
}
mTouchMode = MODE_PINCH;
if (event.getX(0) < event.getX(1)) {
left = (int) event.getX(0);
right = (int) event.getX(1);
} else {
left = (int) event.getX(1);
right = (int) event.getX(0);
}
if (event.getY(0) < event.getY(1)) {
top = (int) event.getY(0);
bottom = (int) event.getY(1);
} else {
top = (int) event.getY(1);
bottom = (int) event.getY(0);
}
mCircleDrawable.setBounds(left, top, right, bottom);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
mCircleDrawable.draw(canvas);
}
}
If you want a perfect circle instead of an oval shape, change the prepareCircleDrawing() method so that it takes the smallest values for X and Y between event 0 and 1.
Edit: you can add the snippet below before calling mCircleDrawable.setBounds(left, top, right, bottom);
to draw a perfect circle. There are other ways for drawing circles, it depends on how you want it to behave.
int height = bottom - top;
int width = right - left;
if (height > width) {
int delta = height - width;
top += delta / 2;
bottom -= delta / 2;
} else {
int delta = width - height;
left += delta / 2;
right -= delta / 2;
}
Hope I made myself clear, regards.
it has been a while since the question was asked but I used this in the past before switching to something different than a circle.
its not perfect but maybe it will help someone.
public class CircleView extends View {
private static final String TAG = "CircleView";
private static final double MOVE_SENSITIVITY = 1.25;
private Paint circlePaint;
private boolean isPinchMode;
private int lastCircleX;
private int lastCircleY;
public Circle circle;
private boolean isDoneResizing = true;
public CircleView(Context context) {
super(context);
setCirclePaint(0x220000ff);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
setCirclePaint(0x220000ff);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setCirclePaint(0x220000ff);
}
private void setCirclePaint(int color) {
circle = new Circle();
circlePaint = new Paint();
circlePaint.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(circle.centerX, circle.centerY, circle.radius, circlePaint);
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
int historySize;
double lastDistance;
double oneBeforeLastDistance;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
lastCircleX = circle.centerX;
lastCircleY = circle.centerY;
break;
case MotionEvent.ACTION_POINTER_DOWN:
isPinchMode = true;
isDoneResizing = false;
break;
case MotionEvent.ACTION_MOVE:
circle.centerX = lastCircleX;
circle.centerY = lastCircleY;;
if (getTouchedCircle((int) event.getX(), (int) event.getY()) && !isPinchMode && isDoneResizing) {
historySize = event.getHistorySize();
if (historySize > 0) {
oneBeforeLastDistance = Math.sqrt((event.getX() - event.getHistoricalX(0, historySize - 1)) *
(event.getX() - event.getHistoricalX(0, historySize - 1)) +
(event.getY() - event.getHistoricalY(0, historySize - 1)) *
(event.getY() - event.getHistoricalY(0, historySize - 1)));
if (oneBeforeLastDistance > MOVE_SENSITIVITY) {
circle.centerX = (int) event.getX();
circle.centerY = (int) event.getY();
lastCircleX = circle.centerX;
lastCircleY = circle.centerY;
}
}
}
if (isPinchMode) {
circle.centerX = lastCircleX;
circle.centerY = lastCircleY;
historySize = event.getHistorySize();
if (historySize > 0) {
lastDistance = Math.sqrt((event.getX(0) - event.getX(1)) * (event.getX(0) - event.getX(1)) +
(event.getY(0) - event.getY(1)) * (event.getY(0) - event.getY(1)));
oneBeforeLastDistance = Math.sqrt((event.getHistoricalX(0, historySize - 1) - event.getHistoricalX(1, historySize - 1)) *
(event.getHistoricalX(0, historySize - 1) - event.getHistoricalX(1, historySize - 1)) +
(event.getHistoricalY(0, historySize - 1) - event.getHistoricalY(1, historySize - 1)) *
(event.getHistoricalY(0, historySize - 1) - event.getHistoricalY(1, historySize - 1)));
if (lastDistance < oneBeforeLastDistance) {
circle.radius -= Math.abs(lastDistance - oneBeforeLastDistance);
} else {
circle.radius += Math.abs(lastDistance - oneBeforeLastDistance);
}
}
}
lastCircleX = circle.centerX;
lastCircleY = circle.centerY;
invalidate();
break;
case MotionEvent.ACTION_POINTER_UP:
circle.centerX = lastCircleX;
circle.centerY = lastCircleY;
isPinchMode = false;
break;
case MotionEvent.ACTION_UP:
circle.centerX = lastCircleX;
circle.centerY = lastCircleY;
isPinchMode = false;
isDoneResizing = true;
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_HOVER_MOVE:
break;
default:
super.onTouchEvent(event);
break;
}
return true;
}
private Boolean getTouchedCircle(final int xTouch, final int yTouch) {
if ((circle.centerX - xTouch) * (circle.centerX - xTouch) +
(circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) {
return true;
} else {
return false;
}
}
static class Circle {
int radius;
int centerX;
int centerY;
Circle() {
this.radius = 150;
this.centerX = 378;
this.centerY = 478;
}
}
}
It has been a while since this question was asked but I want to introduce another way of doing this. I created my own library to handle draggable resizable map area (circle). https://github.com/ac-opensource/MarkerBuilder
It can be implemented by just initialising the MarkerBuilderManager
markerBuilderManager = new MarkerBuilderManagerV2.Builder(this)
.map(mMap) // required
.build();