I have an imageView with multitouch roughly based on this tutorial. One of the commenters there put together a semi-dirty method of limiting the image drag to the boundaries of the image, so that the image edge cannot be dragged beyond its edge. This method sorta works, but not entirely. It only limits drag of two edges.
Does anyone know a less messy and actually functional method for limiting image drag?
This is a highly important concept for android app development that is not adequately addressed....
I was thinking of the following ideas:
1) setScaleType(scaleType.fitXY) when zoom = 1.0F (i.e. min zoom), and drag only enabled when zoom > 1.0f
2) when zoom > 1.0f, setScaleType(scaleType.MATRIX), then you determine image bounds and screen dimensions, and in some way that is too smart for me, using an if statement you only allow drag when the image edge is not on the screen. I don't know how to declare that, is the thing.
anyways, for completeness, here is the limit pan code from that link. This seems to be the most popular suggestion on stackoverflow, but I think we can do better:
// limit pan
matrix.getValues(matrixValues);
float currentY = matrixValues[Matrix.MTRANS_Y];
float currentX = matrixValues[Matrix.MTRANS_X];
float currentScale = matrixValues[Matrix.MSCALE_X];
float currentHeight = height * currentScale;
float currentWidth = width * currentScale;
float dx = event.getX() - start.x;
float dy = event.getY() - start.y;
float newX = currentX+dx;
float newY = currentY+dy;
RectF drawingRect = new RectF(newX, newY, newX+currentWidth, newY+currentHeight);
float diffUp = Math.min(viewRect.bottom-drawingRect.bottom, viewRect.top-drawingRect.top);
float diffDown = Math.max(viewRect.bottom-drawingRect.bottom, viewRect.top-drawingRect.top);
float diffLeft = Math.min(viewRect.left-drawingRect.left, viewRect.right-drawingRect.right);
float diffRight = Math.max(viewRect.left-drawingRect.left, viewRect.right-drawingRect.right);
if(diffUp > 0 ){
dy +=diffUp;
}
if(diffDown < 0){
dy +=diffDown;
}
if( diffLeft> 0){
dx += diffLeft;
}
if(diffRight < 0){
dx += diffRight;
}
matrix.postTranslate(dx, dy);
private void limitDrag(Matrix m, ImageView view) {
float[] values = new float[9];
m.getValues(values);
float transX = values[Matrix.MTRANS_X];
float transY = values[Matrix.MTRANS_Y];
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
Rect bounds = view.getDrawable().getBounds();
int viewWidth = getResources().getDisplayMetrics().widthPixels;
int viewHeight = getResources().getDisplayMetrics().heightPixels;
if(viewHeight<=480)
{
_y_up=0;
}
if(viewHeight>480&&viewHeight<980)
{
_y_up=140;
}
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
int __width=width;
int __height=height;
width = viewWidth / 2;
height = viewHeight / 2;
//height = 200 ;
float minX = (-width) ;//* scaleX;
float minY = (-height) ;//* scaleY;
if ((transX) > (viewWidth)) {
//_x_left
transX = viewWidth;
} else if (transX < minX) {
transX = minX;
}
if ((-transX) > (viewWidth)) {
// _x_right
transX = -(viewWidth);
} else if (-transX < minX) {
transX = -(minX+30);
}
if ((transY) > (viewHeight)) {
// _y_up
transY =( viewHeight);
} else if (transY < minY) {
transY = (minY+_y_up);
}
if ((-transY) > (viewHeight)) {
// _y_down
transY = -(viewHeight);
} else if (-transY < minY) {
transY = -(minY+170);
}
values[Matrix.MTRANS_X] = transX;
values[Matrix.MTRANS_Y] = transY;
m.setValues(values);
}
call this above your view.setImageMatrix(matrix) ;
I realize this is rather old now, but try this. imageWidth and imageHeight are unscaled values.
private void limitDrag(Matrix m, ImageView view, int imageWidth, int imageHeight) {
float[] values = new float[9];
m.getValues(values);
float[] orig = new float[] {0,0, imageWidth, imageHeight};
float[] trans = new float[4];
m.mapPoints(trans, orig);
float transLeft = trans[0];
float transTop = trans[1];
float transRight = trans[2];
float transBottom = trans[3];
float transWidth = transRight - transLeft;
float transHeight = transBottom - transTop;
float xOffset = 0;
if (transWidth > view.getWidth()) {
if (transLeft > 0) {
xOffset = -transLeft;
} else if (transRight < view.getWidth()) {
xOffset = view.getWidth() - transRight;
}
} else {
if (transLeft < 0) {
xOffset = -transLeft;
} else if (transRight > view.getWidth()) {
xOffset = -(transRight - view.getWidth());
}
}
float yOffset = 0;
if (transHeight > view.getHeight()) {
if (transTop > 0) {
yOffset = -transTop;
} else if (transBottom < view.getHeight()) {
yOffset = view.getHeight() - transBottom;
}
} else {
if (transTop < 0) {
yOffset = -transTop;
} else if (transBottom > view.getHeight()) {
yOffset = -(transBottom - view.getHeight());
}
}
float transX = values[Matrix.MTRANS_X];
float transY = values[Matrix.MTRANS_Y];
values[Matrix.MTRANS_X] = transX + xOffset;
values[Matrix.MTRANS_Y] = transY + yOffset;
m.setValues(values);
}
Related
How do I map some areas in an image to be clickable. I have been looking for solutions and got the following solutions:
Using a percentRelativeLayout
Mask the image and get the pixel color of that underneath image to know which area has been clicked
Using AndroidMap
ClickableAreasImages
None of these really gives a solution to my problem. Isn't there any way in Android to map images like html? How do I map an area of any shape so that it becomes clickable?
The area needs to be adjusted even if i zoom the image.
Also I want to change that clicked area to a specific color.
===== Edit ========
Below is the code I am using to zoom the image.
public class ZoomableImageView extends android.support.v7.widget.AppCompatImageView
{
/* public ZoomableImageView(Context context) {
this(context, (AttributeSet)null);
}*/
public OnCustomEventListener mListener; //listener field
//setting the listener
public void setCustomEventListener(OnCustomEventListener eventListener) {
this.mListener=eventListener;
}
Matrix matrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
static final int CLICK = 3;
int mode = NONE;
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 4f;
float[] m;
float redundantXSpace, redundantYSpace;
float width, height;
float saveScale = 1f;
float right, bottom, origWidth, origHeight, bmWidth, bmHeight;
ScaleGestureDetector mScaleDetector;
Context context;
public ZoomableImageView(Context context, AttributeSet attr)
{
super(context, attr);
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix.setTranslate(1f, 1f);
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
mScaleDetector.onTouchEvent(event);
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction())
{
//when one finger is touching
//set the mode to DRAG
case MotionEvent.ACTION_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
mode = DRAG;
break;
//when two fingers are touching
//set the mode to ZOOM
case MotionEvent.ACTION_POINTER_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
mode = ZOOM;
break;
//when a finger moves
//If mode is applicable move image
case MotionEvent.ACTION_MOVE:
//if the mode is ZOOM or
//if the mode is DRAG and already zoomed
if (mode == ZOOM || (mode == DRAG && saveScale > minScale))
{
float deltaX = curr.x - last.x;// x difference
float deltaY = curr.y - last.y;// y difference
float scaleWidth = Math.round(origWidth * saveScale);// width after applying current scale
float scaleHeight = Math.round(origHeight * saveScale);// height after applying current scale
//if scaleWidth is smaller than the views width
//in other words if the image width fits in the view
//limit left and right movement
if (scaleWidth < width)
{
deltaX = 0;
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
}
//if scaleHeight is smaller than the views height
//in other words if the image height fits in the view
//limit up and down movement
else if (scaleHeight < height)
{
deltaY = 0;
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
}
//if the image doesnt fit in the width or height
//limit both up and down and left and right
else
{
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
}
//move the image with the matrix
matrix.postTranslate(deltaX, deltaY);
//set the last touch location to the current
last.set(curr.x, curr.y);
}
break;
//first finger is lifted
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK) {
performClick();
// Toast.makeText(getContext(), "X:: " + String.valueOf(event.getX()) + " Y:: " + String.valueOf(event.getY()), Toast.LENGTH_SHORT).show();
if(mListener!=null){
int x_move_diff = (int) (curr.x - start.x);
int y_move_diff = (int) (curr.y - start.y);
mListener.onEvent((int)curr.x ,(int) curr.y , x_move_diff, y_move_diff);
}
}
break;
// second finger is lifted
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(matrix);
invalidate();
return true;
}
});
}
#Override
public void setImageBitmap(Bitmap bm)
{
super.setImageBitmap(bm);
bmWidth = bm.getWidth();
bmHeight = bm.getHeight();
}
public void setMaxZoom(float x)
{
maxScale = x;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
#Override
public boolean onScaleBegin(ScaleGestureDetector detector)
{
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector)
{
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale)
{
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
}
else if (saveScale < minScale)
{
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
if (origWidth * saveScale <= width || origHeight * saveScale <= height)
{
matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
if (mScaleFactor < 1)
{
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1)
{
if (Math.round(origWidth * saveScale) < width)
{
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
}
else
{
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
}
}
}
}
else
{
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
}
}
return true;
}
}
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
//Fit to screen.
float scale;
float scaleX = width / bmWidth;
float scaleY = height / bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
setImageMatrix(matrix);
saveScale = 1f;
// Center the image
redundantYSpace = height - (scale * bmHeight) ;
redundantXSpace = width - (scale * bmWidth);
redundantYSpace /= 2;
redundantXSpace /= 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);
}
}
Why not just use the Image "setOnTouchListener()" and check if the current X/Y coords are inside an array of Rects?
ArrayList<Rect> mAreas = new ArrayList<>();
mAreas.add(new Rect(0,0,200,200));
mAreas.add(new Rect(300,0, 500, 300));
mImageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
for (final Rect cRect : mAreas) {
if (cRect.contains(event.getX(), event.getY())) {
Toast.makeText(getContext(), "AREA TOUCHED!!", Toast.LENGTH_SHORT).show();
return true;
}
}
return false;
}
});
Recently I used an implementation for a pinch to zoom in my TextureView using a SurfaceView. The zoom works perfectly but the view does not grow to the dimensions of the screen. In my case the TextureView is always in the dimensions 1080, 612. I would like to modify this zoom so that the view is filling the screen as I do the pinch.
Here is the code for my class that receives TextureView:
public class ScaleManager extends BaseInputManager {
private final TextureView mTargetView;
private final ScaleGestureDetector mScaleDetector;
private static final float MIN_SCALE = 1.0f;
private static final float MAX_SCALE = 5.0f;
private float mCurrentScale = MIN_SCALE;
private Matrix mMatrix = new Matrix();
private float[] mMatrixValues = new float[9];
private float mPreviousX;
private float mPreviousY;
private boolean mIsDragInProgress = false;
public ScaleManager(TextureView view) {
mTargetView = view;
mScaleDetector = new ScaleGestureDetector(view.getContext(), new ScaleListener());
mScaleDetector.setQuickScaleEnabled(false);
}
#Override
public boolean handle(KeyEvent event) {
return false;
}
#Override
public boolean handle(MotionEvent event) {
final int index = event.getActionIndex();
final int type = event.getToolType(index);
if (type == MotionEvent.TOOL_TYPE_FINGER || type == MotionEvent.TOOL_TYPE_STYLUS) {
mScaleDetector.onTouchEvent(event);
mMatrix.getValues(mMatrixValues);
boolean isScaling = (mMatrixValues[Matrix.MSCALE_X] != 1.0f && mMatrixValues[Matrix.MSCALE_Y] != 1.0f);
if (isScaling || mScaleDetector.isInProgress()) {
switch (event.getAction()) {
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
if (event.getPointerCount() <= 2) {
mPreviousX = event.getX();
mPreviousY = event.getY();
}
if (event.getPointerCount() == 2) {
mIsDragInProgress = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() == 2) {
float x = mMatrixValues[Matrix.MTRANS_X];
float y = mMatrixValues[Matrix.MTRANS_Y];
float width = mTargetView.getWidth();
float height = mTargetView.getHeight();
float right = width * mCurrentScale - width;
float bottom = height * mCurrentScale - height;
float deltaX = event.getX() - mPreviousX;// x difference
float deltaY = event.getY() - mPreviousY;// y difference
float scaledWidth = Math.round(width * mCurrentScale);// width after applying current scale
float scaledHeight = Math.round(height * mCurrentScale);// height after applying current scale
//if scaledWidth is smaller than the views width
//in other words if the image width fits in the view
//limit left and right movement
if (scaledWidth < width) {
deltaX = 0;
if (y + deltaY > 0) {
deltaY = -y;
} else if (y + deltaY < -bottom) {
deltaY = -(y + bottom);
}
//if scaledHeight is smaller than the views height
//in other words if the image height fits in the view
//limit up and down movement
} else if (scaledHeight < height) {
deltaY = 0;
if (x + deltaX > 0) {
deltaX = -x;
} else if (x + deltaX < -right) {
deltaX = -(x + right);
}
//if the image doesn't fit in the width or height
//limit both up and down and left and right
} else {
if (x + deltaX > 0) {
deltaX = -x;
} else if (x + deltaX < -right) {
deltaX = -(x + right);
}
if (y + deltaY > 0) {
deltaY = -y;
} else if (y + deltaY < -bottom) {
deltaY = -(y + bottom);
}
}
//move the image with the matrix
mMatrix.postTranslate(deltaX, deltaY);
mTargetView.setTransform(mMatrix);
mTargetView.invalidate();
mPreviousX = event.getX();
mPreviousY = event.getY();
}
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
if (event.getPointerCount() == 1) {
mIsDragInProgress = false;
}
break;
}
}
return mScaleDetector.isInProgress() || mIsDragInProgress;
}
return false;
}
private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float width = mTargetView.getWidth();
float height = mTargetView.getHeight();
float factor = detector.getScaleFactor();
float scale = mCurrentScale * factor;
if (scale > MAX_SCALE) {
factor = MAX_SCALE / mCurrentScale;
mCurrentScale = MAX_SCALE;
} else if (scale < MIN_SCALE) {
factor = MIN_SCALE / mCurrentScale;
mCurrentScale = MIN_SCALE;
} else {
mCurrentScale = scale;
}
if (width * scale <= width || height * scale <= height) {
mMatrix.postScale(factor, factor, width / 2, height / 2);
} else {
mMatrix.postScale(factor, factor, detector.getFocusX(), detector.getFocusY());
}
if (factor < 1) {
float right = width * mCurrentScale - width;
float bottom = height * mCurrentScale - height;
mMatrix.getValues(mMatrixValues);
float x = mMatrixValues[Matrix.MTRANS_X];
float y = mMatrixValues[Matrix.MTRANS_Y];
if (x < -right) {
mMatrix.postTranslate(-(x + right), 0);
} else if (x > 0) {
mMatrix.postTranslate(-x, 0);
}
if (y < -bottom) {
mMatrix.postTranslate(0, -(y + bottom));
} else if (y > 0) {
mMatrix.postTranslate(0, -y);
}
}
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
}
When I start the view I use a class called AspectFrameLayout to determine the initial ratio of the view. And this AspectFrameLayout is called each time the view is being modified to maintain the aspect ratio. Follow the code:
<com.examples.views.AspectRatioFrameLayout
android:id="#+id/player_video"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
android:focusableInTouchMode="true" />
mSurfaceView = layoutInflater.inflate(R.layout.texture_view,
player_video_frame, false)
This is the AspectFrameLayout class that I use to keep the aspect ratio:
public final class AspectRatioFrameLayout extends FrameLayout {
private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f;
private float mVideoAspectRatio;
public AspectRatioFrameLayout(Context context) {
super(context);
}
public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAspectRatio(float ratio) {
if (this.mVideoAspectRatio != ratio) {
this.mVideoAspectRatio = ratio;
requestLayout();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mVideoAspectRatio == 0) {
// Aspect ratio not set.
return;
}
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float viewAspectRatio = (float) width / height;
float aspectDeformation = mVideoAspectRatio / viewAspectRatio - 1;
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
// We're within the allowed tolerance.
return;
}
if (aspectDeformation > 0) {
height = (int) (width / mVideoAspectRatio);
} else {
width = (int) (height * mVideoAspectRatio);
}
super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
);
}
}
I would like to zoom the image to particular portion(to specified coordinates) of the image.
I have an image, i displayed it in android image view with full screen. when i click on a button i would like to zoom the image to specified coordinates of the image.lets say coordinates i have is left:500,top:50,width:60 and height:20. I want the full image zoom to specified coordinates and fit this sub image in center of the image view.
Currently i am doing it by cropping the image to specified coordinates. I will get small image. I am displaying it in image view. But i think it is not as good solution.
Can some one help me to find the way to implement the zoom functionality.
Use this Custom ImageView class for you Image View. Then If you want to keep Pinch Zoom then you can use that as well otherwise remove all gestures from it. You can see a "Double Tap method" in the class given below. Just call this method code on the click of your button.
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;
public class TouchImageView extends ImageView {
Matrix matrix = 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 last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;
float redundantXSpace, redundantYSpace, origRedundantXSpace, origRedundantYSpace;;
float width, height;
static final int CLICK = 3;
static final float SAVE_SCALE = 1f;
float saveScale = SAVE_SCALE;
float right, bottom, origWidth, origHeight, bmWidth, bmHeight, origScale, origBottom,origRight;
ScaleGestureDetector mScaleDetector;
GestureDetector mGestureDetector;
Context context;
public TouchImageView(Context context) {
super(context);
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix.setTranslate(1f, 1f);
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
boolean onDoubleTapEvent = mGestureDetector.onTouchEvent(event);
if (onDoubleTapEvent) {
// Reset Image to original scale values
mode = NONE;
bottom = origBottom;
right = origRight;
last = new PointF();
start = new PointF();
m = new float[9];
saveScale = SAVE_SCALE;
matrix = new Matrix();
matrix.setScale(origScale, origScale);
matrix.postTranslate(origRedundantXSpace, origRedundantYSpace);
setImageMatrix(matrix);
invalidate();
return true;
}
mScaleDetector.onTouchEvent(event);
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float scaleWidth = Math.round(origWidth * saveScale);
float scaleHeight = Math.round(origHeight * saveScale);
if (scaleWidth < width) {
deltaX = 0;
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
} else if (scaleHeight < height) {
deltaY = 0;
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
} else {
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
}
matrix.postTranslate(deltaX, deltaY);
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(matrix);
invalidate();
return true; // indicate event was handled
}
});
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onDoubleTapEvent(MotionEvent e) {
return true;
}
});
}
#Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
bmWidth = bm.getWidth();
bmHeight = bm.getHeight();
}
public void setMaxZoom(float x) {
maxScale = x;
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = (float) Math.min(
Math.max(.95f, detector.getScaleFactor()), 1.05);
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
right = width * saveScale - width
- (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height
- (2 * redundantYSpace * saveScale);
if (origWidth * saveScale <= width
|| origHeight * saveScale <= height) {
matrix.postScale(mScaleFactor, mScaleFactor, width / 2,
height / 2);
if (mScaleFactor < 1) {
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (Math.round(origWidth * saveScale) < width) {
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
} else {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
}
}
}
} else {
matrix.postScale(mScaleFactor, mScaleFactor,
detector.getFocusX(), detector.getFocusY());
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
}
}
return true;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
// Fit to screen.
float scale;
float scaleX = (float) width / (float) bmWidth;
float scaleY = (float) height / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
setImageMatrix(matrix);
saveScale = SAVE_SCALE;
origScale = scale;
// Center the image
redundantYSpace = (float) height - (scale * (float) bmHeight);
redundantXSpace = (float) width - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
origRedundantXSpace = redundantXSpace;
origRedundantYSpace = redundantYSpace;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height
- (2 * redundantYSpace * saveScale);
origRight = right;
origBottom = bottom;
setImageMatrix(matrix);
}
}
Hope this will help!
May be this is what you looking for :
/**
* Set zoom to the specified scale. Image will be centered around the point
* (focusX, focusY). These floats range from 0 to 1 and denote the focus point
* as a fraction from the left and top of the view. For example, the top left
* corner of the image would be (0, 0). And the bottom right corner would be (1, 1).
* #param scale
* #param focusX
* #param focusY
* #param scaleType
*/
public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) {
//
// setZoom can be called before the image is on the screen, but at this point,
// image and view sizes have not yet been calculated in onMeasure. Thus, we should
// delay calling setZoom until the view has been measured.
//
if (!onDrawReady) {
delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType);
return;
}
if (scaleType != mScaleType) {
setScaleType(scaleType);
}
resetZoom();
scaleImage(scale, viewWidth / 2, viewHeight / 2, true);
matrix.getValues(m);
m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f));
m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f));
matrix.setValues(m);
fixTrans();
setImageMatrix(matrix);
}
for more info you can refer :
This link Github can help you more
enter link description here
Edit :
please refer this youll get your answer :
The Answer for your question
I am working with the canvas, taking an image as the background and trying to implement the zoom feature into it.
I want to Zoom in at the point where fingers are pinched and zoom out will work through the center coordinates as that of Image gallery feature. I went through many tutorials and read many threads about it.
Whatever I implemented till now is either supporting zoom in and out at same point or if I am applying it to the image then whatever I draw on canvas is not getting zoomed, only the background image is zooming in and out.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
onDrawReady = true;
// for eraser
canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), 0xff, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
imageRenderedAtLeastOnce = true;
if (delayedZoomVariables != null) {
setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX,
delayedZoomVariables.focusY, delayedZoomVariables.scaleType);
delayedZoomVariables = null;
}
canvas.concat(matrix);
for (DrawObject d : paths) {
if (d.getType() == MODE_DRAWING) {
canvas.drawPath(d.getPair().first, d.getPair().second);
} else if (d.getType() == MODE_TEXT) {
canvas.drawText(d.getText(), d.getX(), d.getY(),
d.getPair().second);
} else if (d.getType() == MODE_ARROW) {
canvas.drawLine(d.getStartX(), d.getStartY(), d.getX(),
d.getY(), d.getPair().second);
fillArrow(canvas, d.getStartX(), d.getStartY(), d.getX(),
d.getY(), d.getPair().second);
} else if (d.getType() == MODE_CIRCLE) {
RectF oval2 = new RectF(d.getStartX(), d.getStartY(), d.getX(),
d.getY());
canvas.drawOval(oval2, d.getPair().second);
} else if (d.getType() == MODE_RECTANGLE) {
canvas.drawRect(d.getStartX(), d.getStartY(), d.getX(),
d.getY(), d.getPair().second);
} else if (d.getType() == MODE_ERASE) {
canvas.drawPath(d.getPair().first, d.getPair().second);
}
}
canvas.save();
}
This is the Issue
I dont know how did you implement it but the following code works how you mean:
ZoomActivity:
public class ZoomActivity extends Activity {
Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.zoom_view);
ZoomableImageView mIV = (ZoomableImageView)findViewById(R.id.image_view);
context = this;
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.background);
mIV.setImageBitmap(bm);
}
}
zoom_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.se.test.ZoomableImageView
android:id="#+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
ZoomableImageView:
public class ZoomableImageView extends ImageView {
Matrix matrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
static final int CLICK = 3;
int mode = NONE;
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 4f;
float[] m;
float redundantXSpace, redundantYSpace;
float width, height;
float saveScale = 1f;
float right, bottom, origWidth, origHeight, bmWidth, bmHeight;
ScaleGestureDetector mScaleDetector;
Context context;
public ZoomableImageView(Context context, AttributeSet attr) {
// super(context, attr);
super(context, attr);
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix.setTranslate(1f, 1f);
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
//when one finger is touching
//set the mode to DRAG
case MotionEvent.ACTION_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
mode = DRAG;
break;
//when two fingers are touching
//set the mode to ZOOM
case MotionEvent.ACTION_POINTER_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
mode = ZOOM;
break;
//when a finger moves
//If mode is applicable move image
case MotionEvent.ACTION_MOVE:
//if the mode is ZOOM or
//if the mode is DRAG and already zoomed
if (mode == ZOOM || (mode == DRAG && saveScale > minScale)) {
float deltaX = curr.x - last.x;// x difference
float deltaY = curr.y - last.y;// y difference
float scaleWidth = Math.round(origWidth * saveScale);// width after applying current scale
float scaleHeight = Math.round(origHeight * saveScale);// height after applying current scale
//if scaleWidth is smaller than the views width
//in other words if the image width fits in the view
//limit left and right movement
if (scaleWidth < width) {
deltaX = 0;
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
}
//if scaleHeight is smaller than the views height
//in other words if the image height fits in the view
//limit up and down movement
else if (scaleHeight < height) {
deltaY = 0;
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
}
//if the image doesnt fit in the width or height
//limit both up and down and left and right
else {
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
}
//move the image with the matrix
matrix.postTranslate(deltaX, deltaY);
//set the last touch location to the current
last.set(curr.x, curr.y);
}
break;
//first finger is lifted
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
// second finger is lifted
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(matrix);
invalidate();
return true;
}
});
}
#Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
bmWidth = bm.getWidth();
bmHeight = bm.getHeight();
}
public void setMaxZoom(float x) {
maxScale = x;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
if (mScaleFactor < 1) {
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (Math.round(origWidth * saveScale) < width) {
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
} else {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
}
}
}
} else {
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
}
}
return true;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
//Fit to screen.
float scale;
float scaleX = width / bmWidth;
float scaleY = height / bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
setImageMatrix(matrix);
saveScale = 1f;
// Center the image
redundantYSpace = height - (scale * bmHeight);
redundantXSpace = width - (scale * bmWidth);
redundantYSpace /= 2;
redundantXSpace /= 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);
}
}
Hope it helps
I have done this code. when I zoom hen it should not go out of boundry. its working fine.But when it touches cordinate of x=0 and y=0 it do not minimize. I have used FLAG here to control it may we use some another things.
int FLAG=0;
else if (mode == ZOOM) {
float newDistance = spacing(event);
Log.i("new distance ", newDistance+"");
matrix.getValues(matrixValues);
matrixX = matrixValues[2];
matrixY = matrixValues[5];
width = matrixValues[0]
* (((ImageView)view).getDrawable().getIntrinsicWidth());
height = matrixValues[4]
* (((ImageView) view).getDrawable()
.getIntrinsicHeight());
dx = event.getX() - start.x;
dy = event.getY() - start.y;
// if image will go outside left bound
if (matrixX + dx < 0) {
FLAG=1;
}
if (matrixX + dx + width > view.getWidth()) {
FLAG=1;
//dx = view.getWidth() - matrixX - width;
}
// if image will go oustside top bound
if (matrixY + dy < 0) {
FLAG=1;
//dy = -matrixY;
}
// if image will go outside bottom bound
if (matrixY + dy + height > view.getHeight()) {
FLAG=1;
//dy = view.getHeight() - matrixY - height;
}
if (matrixX + dx ==0 || matrixY + dy==0){
FLAG=0;
}
if (newDistance > 10f && FLAG==0) {
matrix.set(savedMatrix);
float scale = newDistance / oldDistance;
float[] values = new float[9];
matrix.getValues(values);
float currentScale = values[Matrix.MSCALE_X];
if (scale * currentScale > MAX_ZOOM)
scale = MAX_ZOOM / currentScale;
else if (scale * currentScale < MIN_ZOOM)
scale = MIN_ZOOM / currentScale;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;