I'm drawing lines on a custom view and added pan and zoom functionality.
The problem is that when I zoom in with two fingers the middle points gets calculated right but when I do matrix.scale(scaleFactor, scaleFactor, midX, midY), the canvas jumps (translates) to the midX and midY which it was before.
Let me get this straight:
I initialize the mid point between my fingers with 0/0. As soon as the second Pointer is on the screen I calculate the mid point and invalidate the onDraw. Then the drawing jumps (translates) to the last known mid point and starts to zoom in.
I don't want the drawing to be translated to the mid point rather than just zoom to the selected mid point.
Here are the relevant code snippets:
onDraw method
private float startX = 0f;
private float startY = 0f;
private float translateX;
private float translateY;
private float previousTranslateX = 0f;
private float previousTranslateY = 0f;
private float midX = 0f;
private float midY = 0f;
...
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
cx = canvas.getWidth();
cy = canvas.getHeight();
mat.reset();
canvas.save();
int count = 0;
mat.postScale(scaleFactor, -scaleFactor, midX, midY);
mat.postTranslate(translateX, translateY);
canvas.setMatrix(mat);
// draw stuff here...
canvas.restore();
}
onTouch method
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() > 1) {
mode = ZOOM;
} else {
translateX = event.getX() - startX;
translateY = event.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = NONE;
getMidPoint(event);
break;
case MotionEvent.ACTION_UP:
mode = NONE;
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
if ((mode == DRAG) || mode == ZOOM) {
invalidate();
}
return true;
}
calculation of mid point
private void getMidPoint(MotionEvent event) {
//get offset screensize -> canvas
int offX = sx-cx;
int offY = sy-cy;
//get finger coordinates relative to offset
f1x = event.getX(0)+offX;
f1y = event.getY(0)+offY;
f2x = event.getX(1)+offX;
f2y = event.getY(1)+offY;
midX = (f1x + f2x)/2;
midY = (f1y + f2y)/2;
//because my points are in a local coordinate system the
screen coordinates need to be mapped to current canvas matrix
Matrix m = new Matrix(mat);
m.invert(m);
float[] touch = new float[] { midX, midY };
m.mapPoints(touch);
midX = touch[0];
midY = touch[1];
}
Here are some pictures:
This is when the activity starts. The red dot marks the 0/0 position of the initialized mid point
As soon as a put my fingers where the blue dots are and start zooming:
this will be the result. This happens as soon as I start to zoom.
I'd really appreciate if someone has an answer for me! Thanks in advance.
Related
I'm trying to implement a camera preview where user can insert images and drag/scale/rotate them as overlays. I'd also need to support zoom in/out on the camera preview which is in the background. User would be able to pinch to zoom the camera when pressing outside of the image.
The images which the user can add are ImageViews that have match_parent width and height and scale type matrix. I need to allow the touch events to pass thru to the background camera view if they're not directly touching the current image.
I found some code that allows me to perform the various image manipulations:
// ImageView drag, zoom, rotate related variable
// These matrices will be used to move and zoom image
Matrix mMatrix = new Matrix();
Matrix mSavedMatrix = new Matrix();
// We can be in one of these 3 states
public enum ImageTouchMode {
NONE,
DRAG,
ZOOM
}
ImageTouchMode mMode = ImageTouchMode.NONE;
// Remember some things for zooming
PointF mStartPoint = new PointF();
PointF mMidPoint = new PointF();
float mOldDist = 1f;
float[] mLastTouchEvent = null;
float mRotationDegrees = 0f;
float mBrightness = 0f;
public boolean onTouchEvent(MotionEvent event) {
if (!hitTestInImageMatrix(event)) {
// only handle touch events inside the transformed image matrix, not the whole image view
return false;
}
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: //first finger down only
mSavedMatrix.set(mMatrix);
mStartPoint.set(event.getX(), event.getY());
Log.d(TAG, "mode=DRAG");
mMode = ImageTouchMode.DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
mOldDist = spacing(event);
if (mOldDist > 10f) {
mSavedMatrix.set(mMatrix);
midPoint(mMidPoint, event);
mMode = ImageTouchMode.ZOOM;
}
mLastTouchEvent = new float[4];
mLastTouchEvent[0] = event.getX(0);
mLastTouchEvent[1] = event.getX(1);
mLastTouchEvent[2] = event.getY(0);
mLastTouchEvent[3] = event.getY(1);
mRotationDegrees = rotation(event);
break;
case MotionEvent.ACTION_UP: //first finger lifted
case MotionEvent.ACTION_POINTER_UP: //second finger lifted
mMode = ImageTouchMode.NONE;
Log.d(TAG, "mode=NONE");
break;
case MotionEvent.ACTION_MOVE:
if (mMode == ImageTouchMode.DRAG) {
float xDelta = event.getX() - mStartPoint.x;
float yDelta = event.getY() - mStartPoint.y;
mMatrix.set(mSavedMatrix);
mMatrix.postTranslate(xDelta, yDelta);
} else if (mMode == ImageTouchMode.ZOOM && event.getPointerCount() == 2) {
float newDist = spacing(event);
mMatrix.set(mSavedMatrix);
if (newDist > 10f) {
float scale = newDist / mOldDist;
Log.d(TAG, "Scale to " + scale);
mMatrix.postScale(scale, scale, mMidPoint.x, mMidPoint.y);
}
if (mLastTouchEvent != null) {
float newRot = rotation(event);
float r = newRot - mRotationDegrees;
mMatrix.postRotate(r, getMeasuredWidth() / 2,
getMeasuredHeight() / 2);
}
}
break;
}
// Perform the transformation
setImageMatrix(mMatrix);
return true; // indicate event was handled
}
private boolean hitTestInImageMatrix(MotionEvent event) {
float[] values = new float[9];
mMatrix.getValues(values);
float globalX = values[Matrix.MTRANS_X];
float globalY = values[Matrix.MTRANS_Y];
float width = values[Matrix.MSCALE_X] * getDrawable().getIntrinsicWidth();
float height = values[Matrix.MSCALE_Y] * getDrawable().getIntrinsicHeight();
float x = event.getX();
float y = event.getY();
if (x > globalX && x < (globalX + width) && y > globalY && y <
(globalY + height)) {
Log.d(TAG, "Hit test inside!");
return true;
} else {
Log.d(TAG, "Hit test outside");
return false;
}
}
This works pretty good initially, but as soon as the image is rotated to some degree the hit test no longer works correctly.
How could I correctly detect that the touch event happens inside the transformed matrix after rotation?
So I have been attempting to create a program that can drag, zoom and rotate a photo. The big problem I seem to be running into is that whenever I try to rotate the photo, it rotates along the corner, rather than around the center. This means that as I try to rotate the image, it quickly leaves my fingers.
Another big problem I have is that every time I touch with two fingers, the image resets to being perfectly upright, instead of the angle it held when i touched it.
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
//if(mode==DRAG)
canvas.translate(mPosX, mPosY);
if (myScale.isInProgress()) {
canvas.scale(mScaleFactor, mScaleFactor, myScale.getFocusX(), myScale.getFocusY());
}
else{
canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
}
if (myScale.isInProgress()) {
canvas.rotate(degrees, myScale.getFocusX(), myScale.getFocusY());
}
else{
canvas.rotate(degrees, mLastGestureX, mLastGestureY);
}
//canvas.setMatrix(matrix);
//setImageMatrix(matrix);
super.onDraw(canvas);
canvas.restore();
//canvas.drawBitmap(,matrix,new Paint());
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if(event.getPointerCount()>1){
myScale.onTouchEvent(event);
}
switch (event.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN: {
savedMatrix.set(matrix);
final float x = event.getX();
final float y = event.getY();
mode=DRAG;
// Remember where we started
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = event.getPointerId(0);
lastEvent = null;
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
oldDist = spacing(event);
//savedMatrix.set(matrix);
//midPoint(mid, event);
Log.d("touchResponse: ", "mode=ZOOM");
final float gx = myScale.getFocusX();
final float gy = myScale.getFocusY();
mLastGestureX=gx;
mLastGestureY=gy;
mode=ZOOM;
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
// Calculate the distance moved
if(!myScale.isInProgress()&&mode==DRAG){
// Move the object
float dx = x-mLastTouchX;
float dy = y-mLastTouchY;
mPosX+=dx;
mPosY+=dy;
// Remember this touch position for the next move event
// Invalidate to request a redraw
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
if(event.getPointerCount()==2){
if (lastEvent!=null){
newRot=rotation(event);
degrees = newRot-d;
}
}
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
}
case MotionEvent.ACTION_POINTER_UP: {
// Extract the index of the pointer that left the touch sensor\
mode=NONE;
mode = NONE;
lastEvent = null;
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);
}
invalidate();
break;
}
}
return true;
}
//this is a method i ripped from a tutoriaql
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);
}
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));
//matrix=temp;
invalidate();
return true;
}
}
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
//if (Constant.TRACE) Log.d("Rotation ~~~~~~~~~~~~~~~~~", delta_x+" ## "+delta_y+" ## "+radians+" ## "
// +Math.toDegrees(radians));
Log.d("Rotation ~~~~~~~~~~~~~~~~~", delta_x+" ## "+delta_y+" ## "+radians+" ## "
+Math.toDegrees(radians));
return (float) Math.toDegrees(radians);
}
If you want to rotate around the center, you need to translate to the center first. That will set the new origin as the center of the image, and rotate always rotates around the origin.
I Have this code to put a View where I touch in a Screen.
But the View doesn't follow exactly the point where my finger touch.
Can I improve this behavior?
To works like Drag and Drop API but using onTouchEvent?
Thanks for all!
#Override
public boolean onTouchEvent(final MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
moveView(x, y);
return super.onTouchEvent(event);
}
private void moveView(float x, float y) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
params.leftMargin = Math.round(x);
params.topMargin = Math.round(y);
selectedView.setLayoutParams(params);
}
try playing around with some offsets to your x and y coordinates.
//should be on the order of magnitude of the size of the view
float x_offset= 10; //Play with this.
float y_offset= 10; //Play with this.
final float x = event.getX() + x_offset;
final float y = event.getY() + y_offset;
moveView(x, y);
// These matrices will be used to scale points of the image
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
// The 3 states (events) which the user is trying to perform
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
//private static final float MAX_ZOOM = 5f;
int mode = NONE;
// these PointF objects are used to record the point(s) the user is touching
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
float[] lastEvent = null;
float degrees = 0f;
float newRot = 0f;
OnTouchListener touchAction = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
//perfrom zoom & drag & rotate operations on touch of imageview
dragZoomRotate((ImageView)v, event);
return true;
}
};
private void dragZoomRotate(ImageView view, MotionEvent event){
view.setScaleType(ImageView.ScaleType.MATRIX);
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX() , event.getY() );
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN: // first and second finger down
oldDist = spacing(event);
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
degrees = rotation(event);
break;
case MotionEvent.ACTION_UP: // first finger lifted
case MotionEvent.ACTION_POINTER_UP: // second finger lifted
mode = NONE;
lastEvent = null;
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 && event.getPointerCount()==2) {
float newDist = spacing(event);
matrix.set(savedMatrix);
if (newDist > 10f) {
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
if (lastEvent!=null){
newRot = rotation(event);
float r = newRot-degrees;
matrix.postRotate(r, view.getMeasuredWidth()/2, view.getMeasuredHeight()/2);
}
}
//
break;
}
view.setImageMatrix(matrix);
}
private float rotation(MotionEvent event)
{
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
/*
* --------------------------------------------------------------------------
* Method: spacing Parameters: MotionEvent Returns: float Description:
*
* checks the spacing between the two fingers on touch
* ----------------------------------------------------
*/
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);
}
/*
* --------------------------------------------------------------------------
* Method: midPoint Parameters: PointF object, MotionEvent Returns: void
* Description: calculates the midpoint between the two fingers
* ------------------------------------------------------------
*/
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);
}
Now add the touchAction listener to the view which you want to move...
myImageView.setOnTouchListener(touchAction);
try change your layout from RelativeLayout to AbsoluteLayout
i would like to ask can the three functions rotate, zoom and move the image with user's finger happen together in one application?
I found the source code to drag an image plus the zoom feature. However, i cannot find the way to add the rotation function to the code.
Can any one teach how to build these three functions together?
Try with below code its working for me.
float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
public static String fileNAME;
public static int framePos = 0;
private float scale = 0;
private float newDist = 0;
// Fields
private String TAG = this.getClass().getSimpleName();
// We can be in one of these 3 states
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
// Remember some things for zooming
private PointF start = new PointF();
private PointF mid = new PointF();
float oldDist = 1f;
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
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 && event.getPointerCount() == 2) {
float newDist = spacing(event);
matrix.set(savedMatrix);
if (newDist > 10f) {
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
if (lastEvent != null) {
newRot = rotation(event);
float r = newRot - d;
matrix.postRotate(r, view.getMeasuredWidth() / 2,
view.getMeasuredHeight() / 2);
}
}
break;
}
view.setImageMatrix(matrix);
return true;
}
//For rotate image on multi-touch.
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
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);
}
I would recommend using the android-multitouch-controller. It has worked great for me, and it offers multi-touch zoom, pan, and rotate just like you want. It's licensed under Apache v2. Be careful with the rotation. Multi-touch rotation only works well with certain devices.
NOTE: rotation is quirky on older touchscreen devices that use a
Synaptics or Synaptics-like "2x1D" sensor (G1, MyTouch, Droid, Nexus
One) and not a true 2D sensor like the HTC Incredible or HTC EVO 4G.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
android imageView: setting drag and pinch zoom parameters
I am very new to android .. I have an image view in my layout .. I want to enable the zoom in and zoom out for the image coming from this image view .. zoom should be done on two finger touch ..
can anyone tell me how to accomplish this,
Thanks ,
Raj
see this
private static final String TAG = "Touch";
//These matrices will be used to move and zoom image
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;
static final int DRAW =3;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
// Limit zoomable/pannable image
private float[] matrixValues = new float[9];
private float maxZoom;
private float minZoom;
private float height;
private float width;
private RectF viewRect;
/////////************ touch events functions **************////////////////////
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus){ init(); }
}
private void init() {
maxZoom = 4;
minZoom = 0.25f;
height = myimage.getDrawable().getIntrinsicHeight()+20;
width = myimage.getDrawable().getIntrinsicWidth()+20;
viewRect = new RectF(0, 0, myimage.getWidth()+20, myimage.getHeight()+20);
}
/////////************touch events for image Moving, panning and zooming ***********///
public boolean onTouch(View v, MotionEvent event) {
// Dump touch event to log
dumpEvent(event);
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
Log.d(TAG, "mode=DRAG");
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
Log.d(TAG, "oldDist=" + oldDist);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
Log.d(TAG, "mode=ZOOM");
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
Log.d(TAG, "mode=NONE");
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAW){ onTouchEvent(event);}
if (mode == DRAG) {
///code for draging..
}
else if (mode == ZOOM) {
float newDist = spacing(event);
Log.d(TAG, "newDist=" + newDist);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.getValues(matrixValues);
float currentScale = matrixValues[Matrix.MSCALE_X];
// limit zoom
if (scale * currentScale > maxZoom) {
scale = maxZoom / currentScale;
}else if(scale * currentScale < minZoom){
scale = minZoom / currentScale;
}
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
myimage.setImageMatrix(matrix);
return true; // indicate event was handled
}
//*******************Determine the space between the first two fingers
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);
}
//************* Calculate the mid point of the first two fingers
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);
}
}