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();
Related
I am learning Custom Views and succeeded in creating three circle and lines between them. How could I make those circle's draggable.
First of all I want to know that I click on inside the circle using onTouch() and then update these circle position accordingly.
MyDrawingView
public class CustomDrawing extends View {
private static final String TAG = "CustomDrawing";
private Paint circlePaint;
private Paint linePaint;
private Paint textPaint;
private int centerX,centerY;
private float circleSize = 80;
public CustomDrawing(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
setupPaint();
}
private void setupPaint() {
circlePaint = new Paint();
circlePaint.setColor(Color.BLACK);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth(5);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeJoin(Paint.Join.ROUND);
circlePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint = new Paint();
linePaint.setColor(Color.WHITE);
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth((float) 1.5);;
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(60);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setFakeBoldText(true);
}
#Override
protected void onDraw(Canvas canvas) {
///super.onDraw(canvas);
centerX = canvas.getWidth()/2;
centerY = canvas.getHeight()/2;
//Top Left Circle
canvas.drawCircle(circleSize, circleSize, 80, circlePaint);
canvas.drawText("LC",circleSize,getyPositionOfText(circleSize,textPaint),textPaint);
//Center Circle
circlePaint.setColor(Color.GREEN);
canvas.drawCircle(centerX, centerY, circleSize, circlePaint);
////int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
//((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center.
canvas.drawText("CC",centerX,getyPositionOfText(canvas.getHeight()/2,textPaint),textPaint);
///canvas.drawText("CC",50,50,20,20,textPaint);
//Bottom Right Circle
circlePaint.setColor(Color.BLACK);
canvas.drawCircle(canvas.getWidth() - circleSize, canvas.getHeight() - circleSize, 80, circlePaint);
//Center to Left TOP and Center to Right TOP LINE
canvas.drawLine(centerX,centerY,circleSize,circleSize,linePaint);//center to top left
canvas.drawLine(centerX,centerY,canvas.getWidth() - circleSize,circleSize,linePaint);//center to top right
//Center to Left BOTTOM and Center to Right BOTTOM LINE
linePaint.setColor(Color.BLACK);
canvas.drawLine(centerX,centerY, circleSize,
canvas.getHeight() - circleSize,linePaint);// center to bottom left
canvas.drawLine(centerX,centerY,canvas.getWidth() - circleSize,
canvas.getHeight() - circleSize,linePaint);// center to bottom right
linePaint.setColor(Color.WHITE);
canvas.drawLine(centerX,centerY,circleSize,canvas.getHeight()/2,linePaint);
linePaint.setColor(Color.BLACK);
canvas.drawLine(centerX,centerY,canvas.getWidth() - circleSize,canvas.getHeight()/2,linePaint);
//Left top to left bottom
canvas.drawLine(circleSize,circleSize,circleSize,canvas.getHeight() - circleSize,linePaint);
//Right t top to Right bottom
canvas.drawLine(canvas.getWidth() - circleSize,circleSize,canvas.getWidth() - circleSize,canvas.getHeight() - circleSize,linePaint);
linePaint.setColor(Color.GREEN);
canvas.drawLine(circleSize,circleSize,canvas.getWidth()-circleSize,circleSize,linePaint);
canvas.drawLine(circleSize,canvas.getHeight() -circleSize,canvas.getWidth()-circleSize,canvas.getHeight() -circleSize,linePaint);
}
private int getyPositionOfText(float yPositionOfText,Paint mPaint){
return (int) ((yPositionOfText) - ((mPaint.descent() + mPaint.ascent()) / 2)) ;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float pointX = event.getX();
float pointY = event.getY();
// Checks for the event that occurs
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
break;
default:
return false;
}
// Force a view to draw again
postInvalidate();
return true;
}
}
Also give suggestion to improve..
To make a View draggable I use the below code..
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
dX = v.getX() - event.getRawX();
dY = v.getY() - event.getRawY();
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_MOVE:
v.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
break;
}
invalidate();//reDraw
return true;
}
The above code working fine for View. How could I use it for animating(Dragging) Circle?
And in order to detect any position inside circle...
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
It seems that you might have issues with handling of multi-touch / drawing. There's some usefull tutorials about it on Android Developer site and on Android Blog.
Based on this I was able to create an example which I think quite similar to that You're trying to achieve (without complete circle drawing - circles get generated by single touch):
public class CirclesDrawingView extends View {
private static final String TAG = "CirclesDrawingView";
/** Main bitmap */
private Bitmap mBitmap = null;
private Rect mMeasuredRect;
/** Stores data about single circle */
private static class CircleArea {
int radius;
int centerX;
int centerY;
CircleArea(int centerX, int centerY, int radius) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
}
#Override
public String toString() {
return "Circle[" + centerX + ", " + centerY + ", " + radius + "]";
}
}
/** Paint to draw circles */
private Paint mCirclePaint;
private final Random mRadiusGenerator = new Random();
// Radius limit in pixels
private final static int RADIUS_LIMIT = 100;
private static final int CIRCLES_LIMIT = 3;
/** All available circles */
private HashSet<CircleArea> mCircles = new HashSet<CircleArea>(CIRCLES_LIMIT);
private SparseArray<CircleArea> mCirclePointer = new SparseArray<CircleArea>(CIRCLES_LIMIT);
/**
* Default constructor
*
* #param ct {#link android.content.Context}
*/
public CirclesDrawingView(final Context ct) {
super(ct);
init(ct);
}
public CirclesDrawingView(final Context ct, final AttributeSet attrs) {
super(ct, attrs);
init(ct);
}
public CirclesDrawingView(final Context ct, final AttributeSet attrs, final int defStyle) {
super(ct, attrs, defStyle);
init(ct);
}
private void init(final Context ct) {
// Generate bitmap used for background
mBitmap = BitmapFactory.decodeResource(ct.getResources(), R.drawable.up_image);
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.BLUE);
mCirclePaint.setStrokeWidth(40);
mCirclePaint.setStyle(Paint.Style.FILL);
}
#Override
public void onDraw(final Canvas canv) {
// background bitmap to cover all area
canv.drawBitmap(mBitmap, null, mMeasuredRect, null);
for (CircleArea circle : mCircles) {
canv.drawCircle(circle.centerX, circle.centerY, circle.radius, mCirclePaint);
}
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
boolean handled = false;
CircleArea touchedCircle;
int xTouch;
int yTouch;
int pointerId;
int actionIndex = event.getActionIndex();
// get touch event coordinates and make transparent circle from it
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// it's the first pointer, so clear all existing pointers data
clearCirclePointer();
xTouch = (int) event.getX(0);
yTouch = (int) event.getY(0);
// check if we've touched inside some circle
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
mCirclePointer.put(event.getPointerId(0), touchedCircle);
invalidate();
handled = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.w(TAG, "Pointer down");
// It secondary pointers, so obtain their ids and check circles
pointerId = event.getPointerId(actionIndex);
xTouch = (int) event.getX(actionIndex);
yTouch = (int) event.getY(actionIndex);
// check if we've touched inside some circle
touchedCircle = obtainTouchedCircle(xTouch, yTouch);
mCirclePointer.put(pointerId, touchedCircle);
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
invalidate();
handled = true;
break;
case MotionEvent.ACTION_MOVE:
final int pointerCount = event.getPointerCount();
Log.w(TAG, "Move");
for (actionIndex = 0; actionIndex < pointerCount; actionIndex++) {
// Some pointer has moved, search it by pointer id
pointerId = event.getPointerId(actionIndex);
xTouch = (int) event.getX(actionIndex);
yTouch = (int) event.getY(actionIndex);
touchedCircle = mCirclePointer.get(pointerId);
if (null != touchedCircle) {
touchedCircle.centerX = xTouch;
touchedCircle.centerY = yTouch;
}
}
invalidate();
handled = true;
break;
case MotionEvent.ACTION_UP:
clearCirclePointer();
invalidate();
handled = true;
break;
case MotionEvent.ACTION_POINTER_UP:
// not general pointer was up
pointerId = event.getPointerId(actionIndex);
mCirclePointer.remove(pointerId);
invalidate();
handled = true;
break;
case MotionEvent.ACTION_CANCEL:
handled = true;
break;
default:
// do nothing
break;
}
return super.onTouchEvent(event) || handled;
}
/**
* Clears all CircleArea - pointer id relations
*/
private void clearCirclePointer() {
Log.w(TAG, "clearCirclePointer");
mCirclePointer.clear();
}
/**
* Search and creates new (if needed) circle based on touch area
*
* #param xTouch int x of touch
* #param yTouch int y of touch
*
* #return obtained {#link CircleArea}
*/
private CircleArea obtainTouchedCircle(final int xTouch, final int yTouch) {
CircleArea touchedCircle = getTouchedCircle(xTouch, yTouch);
if (null == touchedCircle) {
touchedCircle = new CircleArea(xTouch, yTouch, mRadiusGenerator.nextInt(RADIUS_LIMIT) + RADIUS_LIMIT);
if (mCircles.size() == CIRCLES_LIMIT) {
Log.w(TAG, "Clear all circles, size is " + mCircles.size());
// remove first circle
mCircles.clear();
}
Log.w(TAG, "Added circle " + touchedCircle);
mCircles.add(touchedCircle);
}
return touchedCircle;
}
/**
* Determines touched circle
*
* #param xTouch int x touch coordinate
* #param yTouch int y touch coordinate
*
* #return {#link CircleArea} touched circle or null if no circle has been touched
*/
private CircleArea getTouchedCircle(final int xTouch, final int yTouch) {
CircleArea touched = null;
for (CircleArea circle : mCircles) {
if ((circle.centerX - xTouch) * (circle.centerX - xTouch) + (circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) {
touched = circle;
break;
}
}
return touched;
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMeasuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
}
Activity contains only setContentView(R.layout.main) there main.xml is the following:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/scroller">
<com.example.TestApp.CirclesDrawingView
android:layout_width="match_parent"
android:layout_height="match_parent" />
I'm trying to implement pinch zoom on a SurfaceView. I've done a lot of research regarding this and I found this class to implement pinch zoom. Here is how I modified it:
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private SurfaceView mSurfaceView;
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) {
ZoomLayout.this.mSurfaceView = (SurfaceView) view.findViewById(R.id.mSurfaceView);
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.NONE;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "mode:" + this.mode + ", onScale:" + scaleFactor);
if (this.lastScaleFactor == 0.0f || Math.signum(scaleFactor) == Math.signum(this.lastScaleFactor)) {
this.scale *= scaleFactor;
this.scale = Math.max(MIN_ZOOM, Math.min(this.scale, MAX_ZOOM));
this.lastScaleFactor = scaleFactor;
} else {
this.lastScaleFactor = 0.0f;
}
if (this.mSurfaceView != null) {
int orgWidth = getWidth();
int _width = (int) (((float) orgWidth) * this.scale);
int _height = (int) (((float) getHeight()) * this.scale);
LayoutParams params = (LayoutParams) this.mSurfaceView.getLayoutParams();
params.height = _height;
params.width = _width;
this.mSurfaceView.setLayoutParams(params);
child().setScaleX(this.scale);
child().setScaleY(this.scale);
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
and I implement it into my layout like this:
<pacageName.control.ZoomLayout
android:id="#+id/mZoomLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:measureAllChildren="true">
<SurfaceView
android:id="#+id/mSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</pacageName.control.ZoomLayout>
The problem I'm having is that the zoom is not 'smooth' and the behavior of the zoom is different in different devices (Samsung S4 (Lollipop) and J7Pro (Nougat)).
I'm not sure why pinch zoom is "glitchy" and why it doesn't zoom the same way on the two different devices.
Edit 1: Memory and CPU consumption image added below -
EDIT 2: After trying something else I'm facing a new issue -
I changed my ZoomLayout completely to the following:
public class ZoomableSurfaceView extends SurfaceView {
private ScaleGestureDetector SGD;
private Context context;
private boolean isSingleTouch;
private float width, height = 0;
private float scale = 1f;
private float minScale = 1f;
private float maxScale = 5f;
int left, top, right, bottom;
public ZoomableSurfaceView(Context context) {
super(context);
this.context = context;
init();
}
public ZoomableSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public ZoomableSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private void init() {
setOnTouchListener(new MyTouchListeners());
SGD = new ScaleGestureDetector(context, new ScaleListener());
this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
}
});
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (width == 0 && height == 0) {
width = ZoomableSurfaceView.this.getWidth();
height = ZoomableSurfaceView.this.getHeight();
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
}
}
private class MyTouchListeners implements View.OnTouchListener {
float dX, dY;
MyTouchListeners() {
super();
}
#Override
public boolean onTouch(View view, MotionEvent event) {
SGD.onTouchEvent(event);
if (event.getPointerCount() > 1) {
isSingleTouch = false;
} else {
if (event.getAction() == MotionEvent.ACTION_UP) {
isSingleTouch = true;
}
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
dX = ZoomableSurfaceView.this.getX() - event.getRawX();
dY = ZoomableSurfaceView.this.getY() - event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (isSingleTouch) {
ZoomableSurfaceView.this.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
checkDimension(ZoomableSurfaceView.this);
}
break;
default:
return true;
}
return true;
}
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
//Log.e("onGlobalLayout: ", scale + " " + width + " " + height);
scale *= detector.getScaleFactor();
scale = Math.max(minScale, Math.min(scale, maxScale));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (width * scale), (int) (height * scale));
Log.e("onGlobalLayout: ", (int) (width * scale) + " " + (int) (height * scale));
ZoomableSurfaceView.this.setLayoutParams(params);
checkDimension(ZoomableSurfaceView.this);
return true;
}
}
private void checkDimension(View vi) {
if (vi.getX() > left) {
vi.animate()
.x(left)
.y(vi.getY())
.setDuration(0)
.start();
}
if ((vi.getWidth() + vi.getX()) < right) {
vi.animate()
.x(right - vi.getWidth())
.y(vi.getY())
.setDuration(0)
.start();
}
if (vi.getY() > top) {
vi.animate()
.x(vi.getX())
.y(top)
.setDuration(0)
.start();
}
if ((vi.getHeight() + vi.getY()) < bottom) {
vi.animate()
.x(vi.getX())
.y(bottom - vi.getHeight())
.setDuration(0)
.start();
}
}
}
The problem I'm facing now is that it zooms to X - 0 and Y - 0 meaning that it zooms to the top-left of the screen instead of zooming to the point between my fingers.. I think it could be related to the following:
#Override
public boolean onScale(ScaleGestureDetector detector) {
//Log.e("onGlobalLayout: ", scale + " " + width + " " + height);
scale *= detector.getScaleFactor();
scale = Math.max(minScale, Math.min(scale, maxScale));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (width * scale), (int) (height * scale));
Log.e("onGlobalLayout: ", (int) (width * scale) + " " + (int) (height * scale));
ZoomableSurfaceView.this.setLayoutParams(params);
checkDimension(ZoomableSurfaceView.this);
return true;
}
I have an idea that I should detect the centre point of the 2 fingers and add that to OnScale. How should I proceed?
EDIT 3:
Ok I finally got the zoom working perfectly on my J7 Pro running Nougat. But the problem now, is that my SurfaceView in my S4 running Lollipop doesn't get zoomed in, instead my SurfaceView gets moved to the top left corner. I have tested placing a ImageView inside my custom zoomview and the image gets zoomed perfectly like expected. This is how my custom zoom class looks like now:
public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.NONE; // changed from DRAG, was messing up zoom
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = child().getWidth() * (scale - 1); // adjusted for zero pivot
float maxDy = child().getHeight() * (scale - 1); // adjusted for zero pivot
dx = Math.min(Math.max(dx, -maxDx), 0); // adjusted for zero pivot
dy = Math.min(Math.max(dy, -maxDy), 0); // adjusted for zero pivot
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
#Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale(), scaleFactor = " + scaleFactor);
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
float prevScale = scale;
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
float adjustedScaleFactor = scale / prevScale;
// added logic to adjust dx and dy for pinch/zoom pivot point
Log.d(TAG, "onScale, adjustedScaleFactor = " + adjustedScaleFactor);
Log.d(TAG, "onScale, BEFORE dx/dy = " + dx + "/" + dy);
float focusX = scaleDetector.getFocusX();
float focusY = scaleDetector.getFocusY();
Log.d(TAG, "onScale, focusX/focusy = " + focusX + "/" + focusY);
dx += (dx - focusX) * (adjustedScaleFactor - 1);
dy += (dy - focusY) * (adjustedScaleFactor - 1);
Log.d(TAG, "onScale, dx/dy = " + dx + "/" + dy);
} else {
lastScaleFactor = 0;
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setPivotX(0f); // default is to pivot at view center
child().setPivotY(0f); // default is to pivot at view center
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
Edit 4: Added video of behaviour:
Please see how the current zoom looks like after updating my zoomLayout class to edit 3 -
Video of SurfaceView zoom behaviour
Instead of using SurfaceView, use TextureView which has more features and is preferred over SurfaceView. See TextureView Documentation
Your code is indeed right and should work but in some cases, The MediaPlayer drawn on the SurfaceView does not resize and makes an illusion of the zoom happening at the position 0,0 of the phone. If you set a size smaller than the parent for the SurfaceView and set a background for the parent, you will notice this
SOLUTION IN CODE
Sample Project: Pinch Zoom
The project extends the TextureView and implements the touch to create the zoom effect.
IMAGE: DEMONSTRATION
scaling SurfaceView by scaling its parent FrameLayout will work only in Nougat (API 24) and above
this scaling does not change the SurfaceView resolution i.e. it is much safer and smooth than scaling SurfaceView by changing its LayoutParams (but resolution is fixed). This is the only option for custom view e.g. GStreamerSurfaceView
I am developing an app which takes a picture from camera and then applies an imageview on top of it.
Now, I need to move and resize (with pinch) the imageview above the captured picture.
First I've implemented a simple drag behavior which worked well (I cannot use the drag event since min sdk level), but problems have came when I tried to add a pinch-to-zoom action too.
Right now I've tried several solutions found on stackoverflow, but none of them has worked for my scenario.
Here is the custom view that I'm using (found after some searchs)
https://dl.dropboxusercontent.com/u/230145/PanZoomView.java
but it has a weird behavior since when I try to do a zoom or drag it, the object gets crazy on the screen.
Anyone has an idea or solution for this?
EDIT
Finally I've solved with the following code:
Code from Java Class:
private ImageView mOrologio;
private FrameLayout mPhotoBox;
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
...
...
private void bindViews(View rootView) {
...
mPhotoBox = (FrameLayout) rootView.findViewById(R.id.fotoBox);
mOrologio = (ImageView) rootView.findViewById(R.id.imgOrologio);
mOrologio.setOnTouchListener(this);
...
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
// ...
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x,
event.getY() - start.y);
}
else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
view.setImageMatrix(matrix);
return true; // indicate event was handled
}
}
Code from XML Layout:
<FrameLayout
android:id="#+id/fotoBox"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/prev_list" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/imgOrologio"
android:scaleType="matrix"
android:layout_gravity="right|center_vertical" />
</FrameLayout>
So basically I've learned this lesson:
FrameLayout is needed as rootView if you want to move/zoom your ImageView
If you want to move/zoom an ImageView like in my scenario you don't need to override the ImageView class but only handle the touch events.
Thank you all for replies and hope this solution will be usefull =)
You can use a custom image view above the current screen using a frame layout. You can use the custom class here will may help you
public class CustomZoomableImageView extends ImageView {
private Paint borderPaint = null;
private Paint backgroundPaint = null;
private float mPosX = 0f;
private float mPosY = 0f;
private float mLastTouchX;
private float mLastTouchY;
private static final int INVALID_POINTER_ID = -1;
private static final String LOG_TAG = "TouchImageView";
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
public CustomZoomableImageView (Context context) {
this(context, null, 0);
}
public CustomZoomableImageView (Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
// Existing code ...
public CustomZoomableImageView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Create our ScaleGestureDetector
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
borderPaint = new Paint();
borderPaint.setARGB(255, 255, 128, 0);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(4);
backgroundPaint = new Paint();
backgroundPaint.setARGB(32, 255, 255, 255);
backgroundPaint.setStyle(Paint.Style.FILL);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
/*
* (non-Javadoc)
*
* #see android.view.View#draw(android.graphics.Canvas)
*/
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawRect(0, 0, getWidth() - 1, getHeight() - 1, borderPaint);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth() - 1, getHeight() - 1, backgroundPaint);
if (this.getDrawable() != null) {
canvas.save();
canvas.translate(mPosX, mPosY);
Matrix matrix = new Matrix();
matrix.postScale(mScaleFactor, mScaleFactor, pivotPointX,
pivotPointY);
// canvas.setMatrix(matrix);
canvas.drawBitmap(
((BitmapDrawable) this.getDrawable()).getBitmap(), matrix,
null);
// this.getDrawable().draw(canvas);
canvas.restore();
}
}
/*
* (non-Javadoc)
*
* #see
* android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable
* )
*/
#Override
public void setImageDrawable(Drawable drawable) {
// Constrain to given size but keep aspect ratio
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
mLastTouchX = mPosX = 0;
mLastTouchY = mPosY = 0;
int borderWidth = (int) borderPaint.getStrokeWidth();
mScaleFactor = Math.min(((float) getLayoutParams().width - borderWidth)
/ width, ((float) getLayoutParams().height - borderWidth)
/ height);
pivotPointX = (((float) getLayoutParams().width - borderWidth) - (int) (width * mScaleFactor)) / 2;
pivotPointY = (((float) getLayoutParams().height - borderWidth) - (int) (height * mScaleFactor)) / 2;
super.setImageDrawable(drawable);
}
float pivotPointX = 0f;
float pivotPointY = 0f;
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
pivotPointX = detector.getFocusX();
pivotPointY = detector.getFocusY();
Log.d(LOG_TAG, "mScaleFactor " + mScaleFactor);
Log.d(LOG_TAG, "pivotPointY " + pivotPointY + ", pivotPointX= "
+ pivotPointX);
mScaleFactor = Math.max(0.05f, mScaleFactor);
invalidate();
return true;
}
}
And call the setImageDrawableMethod in the custom imageView
Are you trying to crop the image. I can not comment on your post but just answer. If so then let me know because you can use CROP intent to for what you are looking for. I have implemented it and it basically takes picture with camera... crops,zooms and drag .
Try this.
Something like below will do it.
#Override public boolean onTouch(View v,MotionEvent e)
{
tap=tap2=drag=pinch=none;
int mask=e.getActionMasked();
posx=e.getX();posy=e.getY();
float midx= img.getWidth()/2f;
float midy=img.getHeight()/2f;
int fingers=e.getPointerCount();
switch(mask)
{
case MotionEvent.ACTION_POINTER_UP:
tap2=1;break;
case MotionEvent.ACTION_UP:
tap=1;break;
case MotionEvent.ACTION_MOVE:
drag=1;
}
if(fingers==2){nowsp=Math.abs(e.getX(0)-e.getX(1));}
if((fingers==2)&&(drag==0)){ tap2=1;tap=0;drag=0;}
if((fingers==2)&&(drag==1)){ tap2=0;tap=0;drag=0;pinch=1;}
if(pinch==1)
{
if(nowsp>oldsp)scale+=0.1;
if(nowsp<oldsp)scale-=0.1;
tap2=tap=drag=0;
}
if(tap2==1)
{
scale-=0.1;
tap=0;drag=0;
}
if(tap==1)
{
tap2=0;drag=0;
scale+=0.1;
}
if(drag==1)
{
movx=posx-oldx;
movy=posy-oldy;
x+=movx;
y+=movy;
tap=0;tap2=0;
}
m.setTranslate(x,y);
m.postScale(scale,scale,midx,midy);
img.setImageMatrix(m);img.invalidate();
tap=tap2=drag=none;
oldx=posx;oldy=posy;
oldsp=nowsp;
return true;
}
public void onCreate(Bundle b)
{
super.onCreate(b);
img=new ImageView(this);
img.setScaleType(ImageView.ScaleType.MATRIX);
img.setOnTouchListener(this);
path=Environment.getExternalStorageDirectory().getPath();
path=path+"/DCIM"+"/behala.jpg";
byte[] bytes;
bytes=null;
try{
FileInputStream fis;
fis=new FileInputStream(path);
BufferedInputStream bis;
bis=new BufferedInputStream(fis);
bytes=new byte[bis.available()];
bis.read(bytes);
if(bis!=null)bis.close();
if(fis!=null)fis.close();
}
catch(Exception e)
{
ret="Nothing";
}
Bitmap bmp=BitmapFactory.decodeByteArray(bytes,0,bytes.length);
img.setImageBitmap(bmp);
setContentView(img);
}
For viewing complete program see here: Program to zoom image in android
I've implemented a drag and drop system and now I want to draw a line from the source to destination. How can I draw that line? I already used an extension of the View class, but it's not working. Here is my code:
public class TempDDActivity extends Activity implements OnTouchListener {
/** Called when the activity is first created. */
private View selected_item = null;
private int offset_x = 0;
private int offset_y = 0;
Boolean touchFlag=false;
boolean dropFlag=false;
LayoutParams imageParams;
ImageView imageDrop,image1,image2;
int crashX,crashY;
Drawable dropDrawable,selectDrawable;
Rect dropRect,selectRect;
int topy,leftX,rightX,bottomY;
int dropArray[];
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
ViewGroup container = (ViewGroup) findViewById(R.id.container);
imageDrop=(ImageView) findViewById(R.id.ImgDrop);
image1=(ImageView) findViewById(R.id.img);
image2=(ImageView) findViewById(R.id.img2);
container.setOnTouchListener(new View.OnTouchListener()
{
public boolean onTouch(View v, MotionEvent event)
{
if(touchFlag==true)
{
System.err.println("Display If Part ::->"+touchFlag);
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN :
topy=imageDrop.getTop();
leftX=imageDrop.getLeft();
rightX=imageDrop.getRight();
bottomY=imageDrop.getBottom();
System.err.println("Display Top-->"+topy);
System.err.println("Display Left-->"+leftX);
System.err.println("Display Right-->"+rightX);
System.err.println("Display Bottom-->"+bottomY);
//opRect.
break;
case MotionEvent.ACTION_MOVE:
crashX=(int) event.getX();
crashY=(int) event.getY();
System.err.println("Display Here X Value-->"+crashX);
System.err.println("Display Here Y Value-->"+crashY);
int x = (int) event.getX() - offset_x;
int y = (int) event.getY() - offset_y;
//int w = getWindowManager().getDefaultDisplay().getWidth() - 100;
//int h = getWindowManager().getDefaultDisplay().getHeight() - 100;
int w = getWindowManager().getDefaultDisplay().getWidth() - 50;
int h = getWindowManager().getDefaultDisplay().getHeight() - 10;
if (x > w)
x = w;
if (y > h)
y = h;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(new ViewGroup.MarginLayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
lp.setMargins(x, y, 0, 0);
//Drop Image Here
if(crashX > leftX && crashX < rightX && crashY > topy && crashY < bottomY )
{
Drawable temp=selected_item.getBackground();
imageDrop.setBackgroundDrawable(temp);
imageDrop.bringToFront();
dropFlag=true;
selected_item.setVisibility(View.INVISIBLE);
}
//Drop Image Here
selected_item.setLayoutParams(lp);
break;
case MotionEvent.ACTION_UP:
//
touchFlag=false;
if(dropFlag==true)
{
dropFlag=false;
}
else
{
selected_item.setLayoutParams(imageParams);
}
break;
default:
break;
}
}else
{
System.err.println("Display Else Part ::->"+touchFlag);
}
return true;
}
});
image1.setOnTouchListener(this);
image2.setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
touchFlag=true;
offset_x = (int) event.getX();
offset_y = (int) event.getY();
selected_item = v;
imageParams=v.getLayoutParams();
break;
case MotionEvent.ACTION_UP:
selected_item=null;
touchFlag=false;
break;
default:
break;
}
return false;
}
}
now I want to draw a line from source to destination
First you need to have a custom view for actually drawing the line. This would be the layout that wraps the ImageViews, which in your case I think is a RelativeLayout. That class would be something like:
public class DragObserverLayout extends RelativeLayout {
float startX, startY, stopX, stopY;
private Paint mPaint = new Paint();
private List<Rect> lines = new ArrayList<Rect>();
public DragObserverLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(2.0f);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
final int count = lines.size();
for (int i = 0; i < count; i++) {
final Rect r = lines.get(i);
canvas.drawLine(r.left, r.top, r.right, r.bottom, mPaint);
}
}
public void addLine(Rect r) {
lines.add(r);
invalidate();
}
}
Then in the OnTouchListener for the container where you do the drag operation you would simply do:
final int[] location = new int[2];
final DragObserverLayout container = (DragObserverLayout ) findViewById(R.id.container);
container.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if(touchFlag==true) {
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN :
//...
selected_item.getLocationOnScreen(location);
container.startX = location[0];
container.startY = location[1];
break;
case MotionEvent.ACTION_MOVE:
//...
selected_item.getLocationOnScreen(location);
break;
case MotionEvent.ACTION_UP:
// ...
// .. the item was dragged on the target
if (selected_item.getVisibility() != View.VISIBLE) {
Rect r = new Rect();
r.left = (int) container.startX;
r.top = (int) container.startY;
imageDrop.getLocationInWindow(location);
r.right = location[0];
r.bottom = location[1];
container.addLine(r);
}
This will draw a straight line from the initial position of the dragged ImageView(top left point of the view) to the current position until the ImageView is "dropped". If you want you could offset the start line's position to point to the real touched position by making some simple calculation in the onTouch method for the ImageViews. This also works as you have a full screen application covering the entire screen, otherwise you'll have to offset the return from getLocationOnScreen() to take in consideration the status bar. If you want to keep the line on the screen after the drag operation finishes you'll need to store it in the container.
I am new to android development, sorry if I ask some stupid question please try to help me. I am trying to implement the Zoom and pinch in RelativeLayout. I want to make my own map view in which I'll get the image of floor map and draw the pins(ImageVIew) on it. I've tried it out but i am currently unable to click on the pins. I've done the code with the help of these posts
Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroup
and
Android - zoom in/out RelativeLayout with spread/pinch
My Code is
public class TempView extends RelativeLayout {
private static final int INVALID_POINTER_ID = -1;
private Drawable mIcon;
private float mPosX;
private float mPosY;
TempView temp;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public TempView(Context context) {
this(context, null, 0);
temp = this;
}
public TempView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
temp = this;
}
public TempView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mIcon = context.getResources().getDrawable(R.drawable.ic_launcher);
mIcon.setBounds(0, 0, mIcon.getIntrinsicWidth(), mIcon.getIntrinsicHeight());
temp = this;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
Log.d("onLayout", ""+count);
for(int i=0;i<count;i++){
View child = getChildAt(i);
if(child.getVisibility()!=GONE ){
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
child.layout(
(int)(params.leftMargin * mScaleFactor),
(int)(params.topMargin * mScaleFactor),
(int)((params.leftMargin + child.getMeasuredWidth()) * mScaleFactor),
(int)((params.topMargin + child.getMeasuredHeight()) * mScaleFactor)
);
child.setLayoutParams(params);
}
}
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
Log.d("onDraw", ""+mScaleFactor);
int count = getChildCount();
for(int i=0;i<count;i++){
View child = getChildAt(i);
if(child.getVisibility()!=GONE){
child.draw(canvas);
Log.d("onDraw", ""+mScaleFactor);
}
}
canvas.restore();
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
super.dispatchDraw(canvas);
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, 5.0f));
// Log.d("onScale", ""+mScaleFactor);
temp.invalidate();
invalidate();
return true;
}
}
}
Questions:
How to make Only Floor Map Image to zoom?
Keep Pins non-zoom but Move Relatively?
Get Accurate Click Event when clicked on a pin
Any Suggestions and Answers will be very much appriciated!
Thanks in Advance,
Qamar
An implementation of an HTML map like element in an Android View:
Supports images as drawable or bitmap in layout
Allows for a list of area tags in xml
Enables use of cut and paste HTML area tags to a resource xml (ie, the ability to take an HTML map - and image and use it with minimal editing)
Supports panning if the image is larger than the device screen
Supports pinch-zoom
Supports callbacks when an area is tapped.
Supports showing annotations as bubble text and provide callback if the bubble is tapped
see this link you will got your solution https://github.com/catchthecows/AndroidImageMap