I have a canvas and I am able to zoom out and scroll with boundaries, the code for that is here
package com.mypackage.ui.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.provider.SyncStateContract;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
*
*/
public class MyDrawingView extends View {
private static final int INVALID_POINTER_ID = -1;
private int state = 0; //0 se dibuja - 1 se mueve
//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap bitmap;
protected String tool;
protected String color;
protected float brushSize;
//erase flag
private boolean erase=false;
public String getTool() {
return tool;
}
public void setTool(String tool) {
this.tool = tool;
}
public String getColor() {
return color;
}
public float getBrushSize() {
return brushSize;
}
public boolean isErase() {
return erase;
}
private String drawId;
// public OnSaveActionListener listener;
private float mPosX;
private float mPosY;
public float getMPosX() { return mPosX; }
public float getMPosY() { return mPosY; }
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float viewHeight;
private float viewWidth;
float canvasWidth, canvasHeight;
private float minScaleFactor;
private boolean panEnabled = true;
private boolean zoomEnabled = true;
public MyDrawingView(Context context) {
super(context);
setup();
}
public MyDrawingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public MyDrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setup();
}
//setup drawing
private void setup(){
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
//prepare for drawing and setup paint stroke properties
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
public void setState(int state) {
this.state = state;
}
public void setImageBitmap(Bitmap bmp) {
bitmap = bmp;
resetZoom();
resetPan();
invalidate();
}
public void setImageDrawable(Drawable drawable) {
setImageBitmap(((BitmapDrawable) drawable).getBitmap());
}
public void setBitmap(Bitmap bmp) {
setImageBitmap(bmp);
}
public void resetZoom() {
mScaleFactor = 1.0f;
}
public void resetPan() {
mPosX = 0f;
mPosY = 0f;
}
public BitmapDrawable getImageDrawable() {
BitmapDrawable bd = new BitmapDrawable(getContext().getResources(), bitmap);
return bd;
}
public BitmapDrawable getDrawable() {
return getImageDrawable();
}
//size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(bitmap);
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
canvasHeight = canvas.getHeight();
canvasWidth = canvas.getWidth();
//Save the canvas without translating (panning) or scaling (zooming)
//After each change, restore to this state, instead of compounding
//changes upon changes
canvas.save();
int maxX, minX, maxY, minY;
//Regardless of the screen density (HDPI, MDPI) or the scale factor,
//The image always consists of bitmap width divided by 2 pixels. If an image
//is 200 pixels wide and you scroll right 100 pixels, you just scrolled the image
//off the screen to the left.
minX = (int) (((viewWidth / mScaleFactor) - bitmap.getWidth()) / 2);
maxX = 0;
//How far can we move the image vertically without having a gap between image and frame?
minY = (int) (((viewHeight / mScaleFactor) - bitmap.getHeight()) / 2);
maxY = 0;
//Do not go beyond the boundaries of the image
if (mPosX > maxX) {
mPosX = maxX;
}
if (mPosX < minX) {
mPosX = minX;
}
if (mPosY > maxY) {
mPosY = maxY;
}
if (mPosY < minY) {
mPosY = minY;
}
if (state == 0) {
} else if (state == 1) {
canvas.scale(this.mScaleFactor, this.mScaleFactor, this.mScaleDetector.getFocusX(), this.mScaleDetector.getFocusY());
}
canvas.translate(mPosX, mPosY);
canvas.drawBitmap(bitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
canvas.restore();
invalidate();
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
float touchX = event.getX();
float touchY = event.getY();
if(isErase()){
drawCanvas.drawPath(drawPath, drawPaint);
invalidate();
}
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastTouchX = touchX;
mLastTouchY = touchY;
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (state == 0) {
} else if (state == 1) {
final int pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
final float y = event.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 = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.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 = event.getX(newPointerIndex);
mLastTouchY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
default:
return false;
}
return true;
}
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(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
}
The code is above is an adaptation that worked for me from the answer here
The problem I have is when I zoom in I can't scroll to any place of the image, is like the corners are unreacheable. I know I am making a mistake with the boundaries but I can't realize how to solve it
Thank you
Related
I'm trying to create an application in which the user can drag and zoom an ImageView. But I'm experiencing problems with the following code.
When the scaleFactor is not 1 and the second finger gets down it is translated a little to somewhere else. I don't know where this translate comes from...
Here's the complete class:
package me.miutaltbati.ramaview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import static android.view.MotionEvent.INVALID_POINTER_ID;
public class RamaView extends ImageView {
private Context context;
private Matrix matrix = new Matrix();
private Matrix translateMatrix = new Matrix();
private Matrix scaleMatrix = new Matrix();
// Properties coming from outside:
private int drawableLayoutId;
private int width;
private int height;
private static float MIN_ZOOM = 0.33333F;
private static float MAX_ZOOM = 5F;
private PointF mLastTouch = new PointF(0, 0);
private PointF mLastFocus = new PointF(0, 0);
private PointF mLastPivot = new PointF(0, 0);
private float mPosX = 0F;
private float mPosY = 0F;
public float scaleFactor = 1F;
private int mActivePointerId = INVALID_POINTER_ID;
private Paint paint;
private Bitmap bitmapLayout;
private OnFactorChangedListener mListener;
private ScaleGestureDetector mScaleDetector;
public RamaView(Context context) {
super(context);
initializeInConstructor(context);
}
public RamaView(Context context, #android.support.annotation.Nullable AttributeSet attrs) {
super(context, attrs);
initializeInConstructor(context);
}
public RamaView(Context context, #android.support.annotation.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initializeInConstructor(context);
}
public void initializeInConstructor(Context context) {
this.context = context;
paint = new Paint();
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mScaleDetector.setQuickScaleEnabled(false);
setScaleType(ScaleType.MATRIX);
}
public Bitmap decodeSampledBitmap() {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);
// Calculate inSampleSize
options.inSampleSize = Util.calculateInSampleSize(options, width, height); // e.g.: 4, 8
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);
}
public void setDrawable(int drawableId) {
drawableLayoutId = drawableId;
}
public void setSize(int width, int height) {
this.width = width;
this.height = height;
bitmapLayout = decodeSampledBitmap();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
int pointerIndex = event.getActionIndex();
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
// Remember where we started (for dragging)
mLastTouch = new PointF(x, y);
// Save the ID of this pointer (for dragging)
mActivePointerId = event.getPointerId(0);
}
case MotionEvent.ACTION_POINTER_DOWN: {
if (event.getPointerCount() == 2) {
mLastFocus = new PointF(mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
}
}
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
int pointerIndex = event.findPointerIndex(mActivePointerId);
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
// Calculate the distance moved
float dx = 0;
float dy = 0;
if (event.getPointerCount() == 1) {
// Calculate the distance moved
dx = x - mLastTouch.x;
dy = y - mLastTouch.y;
matrix.setScale(scaleFactor, scaleFactor, mLastPivot.x, mLastPivot.y);
// Remember this touch position for the next move event
mLastTouch = new PointF(x, y);
} else if (event.getPointerCount() == 2) {
// Calculate the distance moved
dx = mScaleDetector.getFocusX() - mLastFocus.x;
dy = mScaleDetector.getFocusY() - mLastFocus.y;
matrix.setScale(scaleFactor, scaleFactor, -mPosX + mScaleDetector.getFocusX(), -mPosY + mScaleDetector.getFocusY());
mLastPivot = new PointF(-mPosX + mScaleDetector.getFocusX(), -mPosY + mScaleDetector.getFocusY());
mLastFocus = new PointF(mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
}
mPosX += dx;
mPosY += dy;
matrix.postTranslate(mPosX, mPosY);
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = event.getActionIndex();
final int pointerId = event.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;
mLastTouch = new PointF(event.getX(newPointerIndex), event.getY(newPointerIndex));
mActivePointerId = event.getPointerId(newPointerIndex);
} else {
final int tempPointerIndex = event.findPointerIndex(mActivePointerId);
mLastTouch = new PointF(event.getX(tempPointerIndex), event.getY(tempPointerIndex));
}
break;
}
}
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.setMatrix(matrix);
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bitmapLayout, 0, 0, paint);
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
}
I think the problem is on this line:
matrix.setScale(scaleFactor, scaleFactor, -mPosX + mScaleDetector.getFocusX(), -mPosY + mScaleDetector.getFocusY());
I tried a lot of things but I couldn't get it to work properly.
UPDATE:
Here's how you can init a RamaView instance:
Main activity's onCreate:
rvRamaView = findViewById(R.id.rvRamaView);
final int[] rvSize = new int[2];
ViewTreeObserver vto = rvRamaView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
rvRamaView.getViewTreeObserver().removeOnPreDrawListener(this);
rvSize[0] = rvRamaView.getMeasuredWidth();
rvSize[1] = rvRamaView.getMeasuredHeight();
rvRamaView.setSize(rvSize[0], rvSize[1]);
return true;
}
});
rvRamaView.setDrawable(R.drawable.original_jpg);
It would be better to use the matrix to accumulate changes rather than try to recalculate the transformations yourself. You can do this with the matrix post... and pre... methods and stay away from the set... methods which reset the matrix.
Here is a rework of the RamaView class which was largely on target except for the specific handling of the matrix as noted above. The mods are to the onTouchEvent() method. The video is output of the code working in a sample app.
RamaView.java
public class RamaView extends ImageView {
private final Matrix matrix = new Matrix();
// Properties coming from outside:
private int drawableLayoutId;
private int width;
private int height;
private static final float MIN_ZOOM = 0.33333F;
private static final float MAX_ZOOM = 5F;
private PointF mLastTouch = new PointF(0, 0);
private PointF mLastFocus = new PointF(0, 0);
public float scaleFactor = 1F;
private int mActivePointerId = INVALID_POINTER_ID;
private Paint paint;
private Bitmap bitmapLayout;
// private OnFactorChangedListener mListener;
private ScaleGestureDetector mScaleDetector;
public RamaView(Context context) {
super(context);
initializeInConstructor(context);
}
public RamaView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initializeInConstructor(context);
}
public RamaView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initializeInConstructor(context);
}
public void initializeInConstructor(Context context) {
paint = new Paint();
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mScaleDetector.setQuickScaleEnabled(false);
setScaleType(ScaleType.MATRIX);
}
public Bitmap decodeSampledBitmap() {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);
options.inSampleSize = Util.calculateInSampleSize(options, width, height); // e.g.: 4, 8
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(getResources(), drawableLayoutId, options);
}
public void setDrawable(int drawableId) {
drawableLayoutId = drawableId;
}
public void setSize(int width, int height) {
this.width = width;
this.height = height;
bitmapLayout = decodeSampledBitmap();
}
private float mLastScaleFactor = 1.0f;
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
int pointerIndex = event.getActionIndex();
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
// Remember where we started (for dragging)
mLastTouch = new PointF(x, y);
// Save the ID of this pointer (for dragging)
mActivePointerId = event.getPointerId(0);
}
case MotionEvent.ACTION_POINTER_DOWN: {
if (event.getPointerCount() == 2) {
mLastFocus = new PointF(mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
}
}
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
int pointerIndex = event.findPointerIndex(mActivePointerId);
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
// Calculate the distance moved
float dx = 0;
float dy = 0;
if (event.getPointerCount() == 1) {
// Calculate the distance moved
dx = x - mLastTouch.x;
dy = y - mLastTouch.y;
// Remember this touch position for the next move event
mLastTouch = new PointF(x, y);
} else if (event.getPointerCount() == 2) {
// Calculate the distance moved
float focusX = mScaleDetector.getFocusX();
float focusY = mScaleDetector.getFocusY();
dx = focusX - mLastFocus.x;
dy = focusY - mLastFocus.y;
// Since we are accumating translation/scaling, we are just adding to
// the previous scale.
matrix.postScale(scaleFactor/mLastScaleFactor, scaleFactor/mLastScaleFactor, focusX, focusY);
mLastScaleFactor = scaleFactor;
mLastFocus = new PointF(focusX, focusY);
}
// Translation is cumulative.
matrix.postTranslate(dx, dy);
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = event.getActionIndex();
final int pointerId = event.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;
mLastTouch = new PointF(event.getX(newPointerIndex), event.getY(newPointerIndex));
mActivePointerId = event.getPointerId(newPointerIndex);
} else {
final int tempPointerIndex = event.findPointerIndex(mActivePointerId);
mLastTouch = new PointF(event.getX(tempPointerIndex), event.getY(tempPointerIndex));
}
break;
}
}
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.setMatrix(matrix);
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bitmapLayout, 0, 0, paint);
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
}
It seems the code is not complete (e. g. I cannot see how matrix is used and where scaleFactor is assigned), but I think the reason why the translation is inconsistent is because in case of 2 pointers you get the [x, y] position from the mScaleDetector.getFocus. As the documentation for ScaleGestureDetector.getFocusX() states:
Get the X coordinate of the current gesture's focal point. If a
gesture is in progress, the focal point is between each of the
pointers forming the gesture.
You should use the mScaleDetector only for getting the current scale, but translation should be always calculated as the difference between the mLastTouch and event.getXY(pointerIndex) so that only one pointer is considered for translation. In case the user adds a second finger and releases the first one, make sure to reassign the pointerIndex and do not perform any translation to avoid jumping.
I tried to create a zoom view with an image that is always scaled about its center. However, if I change the initial position of the image and pinch the image moves to the lower right corner of the window. As a reference I used the pinch zoom view of the Android Developer and changed some parameters. You have any ideas to fix that?
package android.oli.com.fitnessapp;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class ZoomView extends View{
private Drawable mIcon;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
Context context;
public ZoomView(Context context) {
super(context);
mIcon = context.getResources().getDrawable(R.drawable.image_1);
this.context = context;
init();
}
public ZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
mIcon = context.getResources().getDrawable(R.drawable.image_1);
this.context = context;
init();
}
public ZoomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mIcon = context.getResources().getDrawable(R.drawable.image_1);
this.context = context;
init();
}
public void init(){
mIcon.setBounds(0, 0, mIcon.getIntrinsicWidth(), mIcon.getIntrinsicHeight());
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mPosX - ((mIcon.getIntrinsicWidth()) * mScaleFactor) / 2, mPosY - ((mIcon.getIntrinsicWidth()) * mScaleFactor) / 2);
canvas.scale(mScaleFactor, mScaleFactor);
mIcon.draw(canvas);
//rectMid.set(0, 0, canvas.getWidth(), canvas.getHeight()/2);
//canvas.drawRect(rectMid, blue);
canvas.restore();
}
#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;
}
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(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
}
XML:
<android.oli.com.fitnessapp.ZoomView
android:layout_width="match_parent"
android:layout_height="match_parent" />
Ok i got it :D
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class ZoomView extends View{
private Drawable mIcon;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private static final int INVALID_POINTER_ID = -1;
// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
Context context;
int height, width;
public ZoomView(Context context) {
super(context);
mIcon = context.getResources().getDrawable(R.drawable.image_1);
this.context = context;
init();
}
public ZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
mIcon = context.getResources().getDrawable(R.drawable.image_1);
this.context = context;
init();
}
public ZoomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mIcon = context.getResources().getDrawable(R.drawable.image_1);
this.context = context;
init();
}
public void init(){
DisplayMetrics displaymetrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
height = displaymetrics.heightPixels;
width = displaymetrics.widthPixels;
mIcon.setBounds(0, 0, 0 + mIcon.getIntrinsicWidth(), 0 + mIcon.getIntrinsicHeight());
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(width/2 + mPosX - (mIcon.getIntrinsicWidth() * mScaleFactor) / 2, height/2 + mPosY - (mIcon.getIntrinsicWidth() * mScaleFactor) / 2);
canvas.scale(mScaleFactor, mScaleFactor);
mIcon.draw(canvas);
//rectMid.set(0, 0, canvas.getWidth(), canvas.getHeight()/2);
//canvas.drawRect(rectMid, blue);
canvas.restore();
}
#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;
}
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(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
}
I found this code on stack and it works well. However, there is an issue. While I'm able to set its background color, the color changes to black as soon as the clearSignature() function is called.
Why is that happening?
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* A simple view to capture a path traced onto the screen. Initially intended to
* be used to captures signatures.
*
* #author Andrew Crichton
* #version 0.1
*/
public class SignatureView extends View
{
private Path mPath;
private Paint mPaint;
private Paint bgPaint = new Paint(Color.TRANSPARENT);
private Bitmap mBitmap;
private Canvas mCanvas;
private float curX, curY;
private static final int TOUCH_TOLERANCE = 4;
private static final int STROKE_WIDTH = 4;
boolean modified = false;
public SignatureView(Context context)
{
super(context);
init();
}
public SignatureView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public SignatureView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init();
}
private void init()
{
setFocusable(true);
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(STROKE_WIDTH);
}
public void setSigColor(int color)
{
mPaint.setColor(color);
}
public void setSigColor(int a, int red, int green, int blue)
{
mPaint.setARGB(a, red, green, blue);
}
public boolean clearSignature()
{
if (mBitmap != null)
createFakeMotionEvents();
if (mCanvas != null)
{
mCanvas.drawColor(Color.BLACK);
mCanvas.drawPaint(bgPaint);
mPath.reset();
invalidate();
}
else
{
return false;
}
return true;
}
public Bitmap getImage()
{
return this.mBitmap;
}
public void setImage(Bitmap bitmap)
{
this.mBitmap = bitmap;
this.invalidate();
}
public boolean hasChanged()
{
return modified;
}
#Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
int bitmapWidth = mBitmap != null ? mBitmap.getWidth() : 0;
int bitmapHeight = mBitmap != null ? mBitmap.getWidth() : 0;
if (bitmapWidth >= width && bitmapHeight >= height)
return;
if (bitmapWidth < width)
bitmapWidth = width;
if (bitmapHeight < height)
bitmapHeight = height;
Bitmap newBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas newCanvas = new Canvas();
newCanvas.setBitmap(newBitmap);
if (mBitmap != null)
newCanvas.drawBitmap(mBitmap, 0, 0, null);
mBitmap = newBitmap;
mCanvas = newCanvas;
}
private void createFakeMotionEvents()
{
MotionEvent downEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_DOWN,
1f, 1f, 0);
MotionEvent upEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_UP, 1f,
1f, 0);
onTouchEvent(downEvent);
onTouchEvent(upEvent);
}
#Override protected void onDraw(Canvas canvas)
{
modified = true;
canvas.drawColor(Color.RED);
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
canvas.drawPath(mPath, mPaint);
}
#Override public boolean onTouchEvent(MotionEvent event)
{
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
touchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
touchUp();
break;
}
invalidate();
return true;
}
/**
* ---------------------------------------------------------- Private
* methods ---------------------------------------------------------
*/
private void touchDown(float x, float y)
{
mPath.reset();
mPath.moveTo(x, y);
curX = x;
curY = y;
}
private void touchMove(float x, float y)
{
float dx = Math.abs(x - curX);
float dy = Math.abs(y - curY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
mPath.quadTo(curX, curY, (x + curX) / 2, (y + curY) / 2);
curX = x;
curY = y;
}
}
private void touchUp()
{
mPath.lineTo(curX, curY);
if (mCanvas == null)
{
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
}
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
}
Well I have updated the original SignatureView code, now it supports a custom signature background color. This color is different from the view's background color!
setSigBackgroundColor()
I also made some other optimizations, use on your own risk as this is minimal tested!
Small list of optimizations:
Better bitmap recycling etc.
Recycling of MotionEvents
Added signature background color set method
Optimizations
Changed setImage method, although still not very safe to use!
New code:
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* A simple view to capture a path traced onto the screen. Initially intended to
* be used to captures signatures.
*
* #author Andrew Crichton
* #version 0.1.1
*
* Modified by Rolf Smit
* -Recycle bitmaps
* -Recycle MotionEvents
* -Signature Background color changes
* -Optimizations
* -Changed setImage method, although still unsafe to use!
*/
public class SignatureView extends View {
private Path mPath;
private Paint mPaint;
private Bitmap mBitmap;
private Canvas mCanvas;
private int sigBackgroundColor = Color.TRANSPARENT;
private float curX, curY;
private static final int TOUCH_TOLERANCE = 4;
private static final int STROKE_WIDTH = 4;
boolean modified = false;
public SignatureView(Context context) {
super(context);
init();
}
public SignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SignatureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setFocusable(true);
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(STROKE_WIDTH);
}
public void setSigColor(int color) {
mPaint.setColor(color);
}
public void setSigColor(int alpha, int red, int green, int blue) {
mPaint.setARGB(alpha, red, green, blue);
}
public void setSigBackgroundColor(int color){
sigBackgroundColor = color;
}
public void setSigBackgroundColor(int alpha, int red, int green, int blue){
sigBackgroundColor = Color.argb(alpha, red, green, blue);
}
public boolean clearSignature() {
if (mBitmap != null) {
createFakeMotionEvents();
}
if (mCanvas != null) {
mCanvas.drawColor(sigBackgroundColor);
mPath.reset();
invalidate();
} else {
return false;
}
return true;
}
public Bitmap getImage() {
return Bitmap.createBitmap(mBitmap);
}
public void setImage(Bitmap bitmap){
this.mBitmap = bitmap;
if(mCanvas != null){
mCanvas.setBitmap(mBitmap);
}
}
public boolean hasChanged() {
return modified;
}
#Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
int bitmapWidth = mBitmap != null ? mBitmap.getWidth() : 0;
int bitmapHeight = mBitmap != null ? mBitmap.getWidth() : 0;
if (bitmapWidth >= width && bitmapHeight >= height) {
return;
}
if (bitmapWidth < width) {
bitmapWidth = width;
}
if (bitmapHeight < height) {
bitmapHeight = height;
}
Bitmap newBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas newCanvas = new Canvas();
newCanvas.setBitmap(newBitmap);
mCanvas = newCanvas;
if (mBitmap != null) {
newCanvas.drawBitmap(mBitmap, 0, 0, null);
mBitmap.recycle();
} else {
newCanvas.drawColor(sigBackgroundColor);
}
mBitmap = newBitmap;
}
private void createFakeMotionEvents() {
MotionEvent downEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_DOWN, 1f, 1f, 0);
MotionEvent upEvent = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_UP, 1f, 1f, 0);
onTouchEvent(downEvent);
onTouchEvent(upEvent);
downEvent.recycle();
upEvent.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
modified = true;
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
canvas.drawPath(mPath, mPaint);
}
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
touchUp();
break;
}
invalidate();
return true;
}
private void touchDown(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
curX = x;
curY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - curX);
float dy = Math.abs(y - curY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(curX, curY, (x + curX) / 2, (y + curY) / 2);
curX = x;
curY = y;
}
}
private void touchUp() {
mPath.lineTo(curX, curY);
if (mCanvas == null) {
mCanvas = new Canvas();
mCanvas.setBitmap(mBitmap);
}
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
}
How can I perform rotate, zoom and drag together in an Android Imageview
For rotate, scale and rotate, try this. Worked 100%.
You can try something like this:
package com.my.test;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
public class ImageViewActivity extends Activity implements OnClickListener, SurfaceHolder.Callback {
ImageView view;
final static String TAG = "TAG";
ScaleGestureDetector scaleGestureDetector;
Thread mDrawingThread;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*PinchImageView view = new PinchImageView(this);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(view);*/
setContentView(R.layout.main);
PinchImageView view = (PinchImageView) findViewById(R.id.image);
view.setDrawable(getResources().getDrawable(R.drawable.pic));
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(this);
button = (Button) findViewById(R.id.button2);
button.setOnClickListener(this);
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(v.getId() == R.id.button1) {
PinchImageView view = (PinchImageView) findViewById(R.id.image);
view.setDrawable(getResources().getDrawable(R.drawable.pic));
}
if(v.getId() == R.id.button2) {
PinchImageView view = (PinchImageView) findViewById(R.id.image);
view.setDrawable(getResources().getDrawable(R.drawable.penguins));
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
}
/**
* #author
*
*/
public class PinchImageView extends SurfaceView {
final String TAG = "TAG";
final static int HORIZONTAL_SNAP_GAP = 30;
final static int VERTICAL_SNAP_GAP = 50;
private static final int INVALID_POINTER_ID = -1;
private Drawable mIcon;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public PinchImageView(Context context) {
this(context, null, 0);
}
public PinchImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PinchImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//mIcon = context.getResources().getDrawable(R.drawable.pic);
//mIcon.setBounds(0, 0, mIcon.getIntrinsicWidth(), mIcon.getIntrinsicHeight());
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;
if(mPosX > HORIZONTAL_SNAP_GAP) mPosX = HORIZONTAL_SNAP_GAP;
if(mPosY > VERTICAL_SNAP_GAP) mPosY = VERTICAL_SNAP_GAP;
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int displayWidth = display.getWidth();
int displayHeight = display.getHeight();
if(mPosX + mIcon.getBounds().width() < getWidth() - HORIZONTAL_SNAP_GAP) mPosX = getWidth() - HORIZONTAL_SNAP_GAP - mIcon.getBounds().width();
if(mPosY + mIcon.getBounds().height() < getHeight() - VERTICAL_SNAP_GAP) mPosY = getHeight() - VERTICAL_SNAP_GAP - mIcon.getBounds().height();
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);
}
/*if(mPosX > 0 || mPosY > 0) {
Animation an = new TranslateAnimation(mPosX, 0, mPosY, 0);
an.setDuration(2000);
an.setRepeatCount(-1);
//an.initialize(10, 10, 10, 10);
setAnimation(an);
an.startNow();
invalidate();
}*/
break;
}
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
if(mIcon != null) mIcon.draw(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(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
public void setDrawable(Drawable draw) {
mIcon = draw;
mIcon.setBounds(0, 0, mIcon.getIntrinsicWidth(), mIcon.getIntrinsicHeight());
invalidate();
}
}
How can we drag more than one image in a canvas? I have a code to drag single image. I tried for moving multiple image. But it didn't work. Is there any way to do this one? This is the code for moving one image.. Thanks in advance
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class MyImageView extends View {
private static final int INVALID_POINTER_ID = -1;
private Drawable mImage;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public MyImageView(Context context) {
this(context, null, 0);
mImage = getResources().getDrawable(R.drawable.baby1);
mImage.setBounds(0, 0, mImage.getIntrinsicWidth(), mImage.getIntrinsicHeight());
}
public MyImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
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
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
Log.d("DEBUG", "X: "+mPosX+" Y: "+mPosY);
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
mImage.draw(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(0.1f, Math.min(mScaleFactor, 10.0f));
invalidate();
return true;
}
}
}
I'm in search of the same thing ,so I guess is one of this 2 answers will work:
So chatgtp to find a answer just feed it your code and your problem and try it till it's what you need
or maybe store the X Y coordinates for the multiple views you want to drag and override the onTouch and check if the xy coordinates are inside any of the imageviews (this code can be found on geeksforgeeks website in java,cpp etc),if not then don't do anything else update the coordinates of the view to 'drag' the view to the new coordinates using the code from the single choice view.the only extra step is to check using xy coordinates of the imageviews if the touch event is 'inside' the view
or check Android drag two images on canvas where it gives a library that handles all of it
If you don't mind I just post my code snippet, maybe it will help you to figure out the problem.
To use this snippet just add in your layout xml file:
<com.m039.study.widgets.MultiTouch
android:layout_width = "match_parent"
android:layout_height = "match_parent"
/>
This snippet just draw circle on each finger on the view:
package com.m039.study.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.MotionEvent;
import android.view.GestureDetector;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Color;
import java.util.Stack;
public class MultiTouch extends View {
private static final String TAG = "m039";
public MultiTouch(Context c) {
super(c);
}
public MultiTouch(Context context, AttributeSet attrs) {
super(context, attrs);
}
static class Point {
public float x;
public float y;
public int pid = -1;
public boolean isOk = false;
}
Paint mPaintCircle = new Paint();
Paint mPaintText = new Paint();
Stack<Point> mPoints = new Stack<Point>();
{
mPaintCircle.setColor(Color.GREEN);
mPaintText.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Point point : mPoints) {
if (point.isOk) {
canvas.drawCircle(point.x, point.y, 50, mPaintCircle);
canvas.drawText(Integer.toString(point.pid), point.x, point.y + 50, mPaintText);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
int pid = (event.getAction() & event.ACTION_POINTER_ID_MASK) >> event.ACTION_POINTER_ID_SHIFT;
int index = (event.getAction() & event.ACTION_POINTER_INDEX_MASK) >> event.ACTION_POINTER_INDEX_SHIFT;
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
Point point = new Point();
point.isOk = true;
point.pid = pid;
point.x = event.getX(index);
point.y = event.getY(index);
mPoints.push(point);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
for (Point p: mPoints) {
int pindex = event.findPointerIndex(p.pid);
if (pindex != -1) {
p.x = event.getX(pindex);
p.y = event.getY(pindex);
}
}
invalidate();
break;
case MotionEvent.ACTION_POINTER_UP:
if (mPoints.size() >= 1) {
mPoints.pop();
}
invalidate();
break;
case MotionEvent.ACTION_UP:
default:
mPoints.clear();
invalidate();
break;
}
return true;
}
}
P.S. Please, tell me if it is helpful or not.