android-how to move image to center of this CustomImageVIew - android

I needed to able zoom images inside an imageView.
I've searched and used this CustomImageVIew and it works fine.
The only problem is this, it move the image to the top left corner of the screen.
Sorry, I can't post screenshot, consider this, it's in 1/4 of the screen at top left corner.
I want to make the image to come to center of the screen
this is the class:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class CustomImageVIew extends ImageView implements OnTouchListener {
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
private int mode = NONE;
private PointF mStartPoint = new PointF();
private PointF mMiddlePoint = new PointF();
private Point mBitmapMiddlePoint = new Point();
private float oldDist = 1f;
private float matrixValues[] = {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f};
private float scale;
private float oldEventX = 0;
private float oldEventY = 0;
private float oldStartPointX = 0;
private float oldStartPointY = 0;
private int mViewWidth = -1;
private int mViewHeight = -1;
private int mBitmapWidth = -1;
private int mBitmapHeight = -1;
private boolean mDraggable = false;
public CustomImageVIew(Context context) {
this(context, null, 0);
}
public CustomImageVIew(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomImageVIew(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setOnTouchListener(this);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
public void setBitmap(Bitmap bitmap){
if(bitmap != null){
setImageBitmap(bitmap);
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mBitmapMiddlePoint.x = (mViewWidth / 2) - (mBitmapWidth / 2);
mBitmapMiddlePoint.y = (mViewHeight / 2) - (mBitmapHeight / 2);
matrix.postTranslate(mBitmapMiddlePoint.x, mBitmapMiddlePoint.y);
this.setImageMatrix(matrix);
}
}
#Override
public boolean onTouch(View v, MotionEvent event){
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
mStartPoint.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if(oldDist > 10f){
savedMatrix.set(matrix);
midPoint(mMiddlePoint, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if(mode == DRAG){
drag(event);
} else if(mode == ZOOM){
zoom(event);
}
break;
}
return true;
}
public void drag(MotionEvent event){
matrix.getValues(matrixValues);
float left = matrixValues[2];
float top = matrixValues[5];
float bottom = (top + (matrixValues[0] * mBitmapHeight)) - mViewHeight;
float right = (left + (matrixValues[0] * mBitmapWidth)) -mViewWidth;
float eventX = event.getX();
float eventY = event.getY();
float spacingX = eventX - mStartPoint.x;
float spacingY = eventY - mStartPoint.y;
float newPositionLeft = (left < 0 ? spacingX : spacingX * -1) + left;
float newPositionRight = (spacingX) + right;
float newPositionTop = (top < 0 ? spacingY : spacingY * -1) + top;
float newPositionBottom = (spacingY) + bottom;
boolean x = true;
boolean y = true;
if(newPositionRight < 0.0f || newPositionLeft > 0.0f){
if(newPositionRight < 0.0f && newPositionLeft > 0.0f){
x = false;
} else{
eventX = oldEventX;
mStartPoint.x = oldStartPointX;
}
}
if(newPositionBottom < 0.0f || newPositionTop > 0.0f){
if(newPositionBottom < 0.0f && newPositionTop > 0.0f){
y = false;
} else{
eventY = oldEventY;
mStartPoint.y = oldStartPointY;
}
}
if(mDraggable){
matrix.set(savedMatrix);
matrix.postTranslate(x? eventX - mStartPoint.x : 0, y? eventY - mStartPoint.y : 0);
this.setImageMatrix(matrix);
if(x)oldEventX = eventX;
if(y)oldEventY = eventY;
if(x)oldStartPointX = mStartPoint.x;
if(y)oldStartPointY = mStartPoint.y;
}
}
public void zoom(MotionEvent event){
matrix.getValues(matrixValues);
float newDist = spacing(event);
float bitmapWidth = matrixValues[0] * mBitmapWidth;
float bimtapHeight = matrixValues[0] * mBitmapHeight;
boolean in = newDist > oldDist;
if(!in && matrixValues[0] < 1){
return;
}
if(bitmapWidth > mViewWidth || bimtapHeight > mViewHeight){
mDraggable = true;
} else{
mDraggable = false;
}
float midX = (mViewWidth / 2);
float midY = (mViewHeight / 2);
matrix.set(savedMatrix);
scale = newDist / oldDist;
matrix.postScale(scale, scale, bitmapWidth > mViewWidth ? mMiddlePoint.x : midX, bimtapHeight > mViewHeight ? mMiddlePoint.y : midY);
this.setImageMatrix(matrix);
}
/** 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 (float)Math.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);
}
}
this is layout xml :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:background="#000" >
<mypackagename.CustomImageVIew
android:id="#+id/imageView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:scaleType="matrix"
/>

My guess is that you're calling setBitmap before the view is visible on screen, while it still has a width and height of zero.
There are a few options for fixing this, but generally speaking you could change the image view so it does the calculation that happens in setBitmap either the first time onSizeChanged is called with a width and height greater than zero, or the first time onDraw is called and both getWidth() and getHeight() return greater than zero. When setBitmap is called and the view already has a width and a height, do the calculation immediately.
Here's a rough (untested) illustration:
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
if (w > 0 && h > 0 && mBitmapWidth > 0 && mBitmapHeight > 0) {
centerBitmap();
}
}
public void setBitmap(Bitmap bitmap){
if(bitmap != null){
setImageBitmap(bitmap);
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
if (getWidth() > 0 && getHeight() > 0) {
centerBitmap();
}
}
}
private void centerBitmap() {
mBitmapMiddlePoint.x = (mViewWidth / 2) - (mBitmapWidth / 2);
mBitmapMiddlePoint.y = (mViewHeight / 2) - (mBitmapHeight / 2);
matrix.postTranslate(mBitmapMiddlePoint.x, mBitmapMiddlePoint.y);
this.setImageMatrix(matrix);
}

Take Framelayout instead of Relative layout and try..

Related

Make zoom in/out using Matrix and x,y of ImageView

I have an ImageView:
<ImageView
android:id="#+id/ivHand"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
And in Activity I do the following:
ImageView ivBgHand = (ImageView) findViewById(R.id.ivHand);
Bitmap bitmap = BitmapFactory.decodeFile(photoPath); // Path from storage
ivBgHand.setImageBitmap(bitmap);
I want to make zoom in and out using Matrix and x, y coordinates where it should be zoomed (aka pivotX, pivotY). Can anyone help me with this?
(I don't want to use ScaleAnimation, because the next step will be a handling changed Matrix)
If you are looking for a simple library which handles the zoom in an imageview with no extra code, then I can recommend to use PhotoView library. It´s pretty simple.
I've found a fantastic solution! Source link.
Custom layout view:
package <your package>;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
/**
* Zooming view.
*/
public class ZoomView extends FrameLayout {
public ZoomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public ZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public ZoomView(final Context context) {
super(context);
}
/**
* Zooming view listener interface.
*
* #author karooolek
*
*/
public interface ZoomViewListener {
void onZoomStarted(float zoom, float zoomx, float zoomy);
void onZooming(float zoom, float zoomx, float zoomy);
void onZoomEnded(float zoom, float zoomx, float zoomy);
}
// zooming
float zoom = 1.0f;
float maxZoom = 2.0f;
float smoothZoom = 1.0f;
float zoomX, zoomY;
float smoothZoomX, smoothZoomY;
private boolean scrolling; // NOPMD by karooolek on 29.06.11 11:45
// minimap variables
private boolean showMinimap = false;
private int miniMapColor = Color.WHITE;
private int miniMapHeight = -1;
private String miniMapCaption;
private float miniMapCaptionSize = 10.0f;
private int miniMapCaptionColor = Color.WHITE;
// touching variables
private long lastTapTime;
private float touchStartX, touchStartY;
private float touchLastX, touchLastY;
private float startd;
private boolean pinching;
private float lastd;
private float lastdx1, lastdy1;
private float lastdx2, lastdy2;
// drawing
private final Matrix m = new Matrix();
private final Paint p = new Paint();
// listener
ZoomViewListener listener;
private Bitmap ch;
public float getZoom() {
return zoom;
}
public float getMaxZoom() {
return maxZoom;
}
public void setMaxZoom(final float maxZoom) {
if (maxZoom < 1.0f) {
return;
}
this.maxZoom = maxZoom;
}
public void setMiniMapEnabled(final boolean showMiniMap) {
this.showMinimap = showMiniMap;
}
public boolean isMiniMapEnabled() {
return showMinimap;
}
public void setMiniMapHeight(final int miniMapHeight) {
if (miniMapHeight < 0) {
return;
}
this.miniMapHeight = miniMapHeight;
}
public int getMiniMapHeight() {
return miniMapHeight;
}
public void setMiniMapColor(final int color) {
miniMapColor = color;
}
public int getMiniMapColor() {
return miniMapColor;
}
public String getMiniMapCaption() {
return miniMapCaption;
}
public void setMiniMapCaption(final String miniMapCaption) {
this.miniMapCaption = miniMapCaption;
}
public float getMiniMapCaptionSize() {
return miniMapCaptionSize;
}
public void setMiniMapCaptionSize(final float size) {
miniMapCaptionSize = size;
}
public int getMiniMapCaptionColor() {
return miniMapCaptionColor;
}
public void setMiniMapCaptionColor(final int color) {
miniMapCaptionColor = color;
}
public void zoomTo(final float zoom, final float x, final float y) {
this.zoom = Math.min(zoom, maxZoom);
zoomX = x;
zoomY = y;
smoothZoomTo(this.zoom, x, y);
}
public void smoothZoomTo(final float zoom, final float x, final float y) {
smoothZoom = clamp(1.0f, zoom, maxZoom);
smoothZoomX = x;
smoothZoomY = y;
if (listener != null) {
listener.onZoomStarted(smoothZoom, x, y);
}
}
public ZoomViewListener getListener() {
return listener;
}
public void setListner(final ZoomViewListener listener) {
this.listener = listener;
}
public float getZoomFocusX() {
return zoomX * zoom;
}
public float getZoomFocusY() {
return zoomY * zoom;
}
#Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
// single touch
if (ev.getPointerCount() == 1) {
processSingleTouchEvent(ev);
}
// // double touch
if (ev.getPointerCount() == 2) {
processDoubleTouchEvent(ev);
}
// redraw
getRootView().invalidate();
invalidate();
return true;
}
private void processSingleTouchEvent(final MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final float w = miniMapHeight * (float) getWidth() / getHeight();
final float h = miniMapHeight;
final boolean touchingMiniMap = x >= 10.0f && x <= 10.0f + w
&& y >= 10.0f && y <= 10.0f + h;
if (showMinimap && smoothZoom > 1.0f && touchingMiniMap) {
processSingleTouchOnMinimap(ev);
} else {
processSingleTouchOutsideMinimap(ev);
}
}
private void processSingleTouchOnMinimap(final MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final float w = miniMapHeight * (float) getWidth() / getHeight();
final float h = miniMapHeight;
final float zx = (x - 10.0f) / w * getWidth();
final float zy = (y - 10.0f) / h * getHeight();
smoothZoomTo(smoothZoom, zx, zy);
}
private void processSingleTouchOutsideMinimap(final MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
float lx = x - touchStartX;
float ly = y - touchStartY;
final float l = (float) Math.hypot(lx, ly);
float dx = x - touchLastX;
float dy = y - touchLastY;
touchLastX = x;
touchLastY = y;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStartX = x;
touchStartY = y;
touchLastX = x;
touchLastY = y;
dx = 0;
dy = 0;
lx = 0;
ly = 0;
scrolling = false;
break;
case MotionEvent.ACTION_MOVE:
if (scrolling || (smoothZoom > 1.0f && l > 30.0f)) {
if (!scrolling) {
scrolling = true;
ev.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(ev);
}
smoothZoomX -= dx / zoom;
smoothZoomY -= dy / zoom;
return;
}
break;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_UP:
// tap
if (l < 30.0f) {
// check double tap
if (System.currentTimeMillis() - lastTapTime < 500) {
if (smoothZoom == 1.0f) {
smoothZoomTo(maxZoom, x, y);
} else {
smoothZoomTo(1.0f, getWidth() / 2.0f,
getHeight() / 2.0f);
}
lastTapTime = 0;
ev.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(ev);
return;
}
lastTapTime = System.currentTimeMillis();
performClick();
}
break;
default:
break;
}
ev.setLocation(zoomX + (x - 0.5f * getWidth()) / zoom, zoomY
+ (y - 0.5f * getHeight()) / zoom);
ev.getX();
ev.getY();
super.dispatchTouchEvent(ev);
}
private void processDoubleTouchEvent(final MotionEvent ev) {
final float x1 = ev.getX(0);
final float dx1 = x1 - lastdx1;
lastdx1 = x1;
final float y1 = ev.getY(0);
final float dy1 = y1 - lastdy1;
lastdy1 = y1;
final float x2 = ev.getX(1);
final float dx2 = x2 - lastdx2;
lastdx2 = x2;
final float y2 = ev.getY(1);
final float dy2 = y2 - lastdy2;
lastdy2 = y2;
// pointers distance
final float d = (float) Math.hypot(x2 - x1, y2 - y1);
final float dd = d - lastd;
lastd = d;
final float ld = Math.abs(d - startd);
Math.atan2(y2 - y1, x2 - x1);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startd = d;
pinching = false;
break;
case MotionEvent.ACTION_MOVE:
if (pinching || ld > 30.0f) {
pinching = true;
final float dxk = 0.5f * (dx1 + dx2);
final float dyk = 0.5f * (dy1 + dy2);
smoothZoomTo(Math.max(1.0f, zoom * d / (d - dd)), zoomX - dxk
/ zoom, zoomY - dyk / zoom);
}
break;
case MotionEvent.ACTION_UP:
default:
pinching = false;
break;
}
ev.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(ev);
}
private float clamp(final float min, final float value, final float max) {
return Math.max(min, Math.min(value, max));
}
private float lerp(final float a, final float b, final float k) {
return a + (b - a) * k;
}
private float bias(final float a, final float b, final float k) {
return Math.abs(b - a) >= k ? a + k * Math.signum(b - a) : b;
}
#Override
protected void dispatchDraw(final Canvas canvas) {
// do zoom
zoom = lerp(bias(zoom, smoothZoom, 0.05f), smoothZoom, 0.2f);
smoothZoomX = clamp(0.5f * getWidth() / smoothZoom, smoothZoomX,
getWidth() - 0.5f * getWidth() / smoothZoom);
smoothZoomY = clamp(0.5f * getHeight() / smoothZoom, smoothZoomY,
getHeight() - 0.5f * getHeight() / smoothZoom);
zoomX = lerp(bias(zoomX, smoothZoomX, 0.1f), smoothZoomX, 0.35f);
zoomY = lerp(bias(zoomY, smoothZoomY, 0.1f), smoothZoomY, 0.35f);
if (zoom != smoothZoom && listener != null) {
listener.onZooming(zoom, zoomX, zoomY);
}
final boolean animating = Math.abs(zoom - smoothZoom) > 0.0000001f
|| Math.abs(zoomX - smoothZoomX) > 0.0000001f
|| Math.abs(zoomY - smoothZoomY) > 0.0000001f;
// nothing to draw
if (getChildCount() == 0) {
return;
}
// prepare matrix
m.setTranslate(0.5f * getWidth(), 0.5f * getHeight());
m.preScale(zoom, zoom);
m.preTranslate(
-clamp(0.5f * getWidth() / zoom, zoomX, getWidth() - 0.5f
* getWidth() / zoom),
-clamp(0.5f * getHeight() / zoom, zoomY, getHeight() - 0.5f
* getHeight() / zoom));
// get view
final View v = getChildAt(0);
m.preTranslate(v.getLeft(), v.getTop());
// get drawing cache if available
if (animating && ch == null && isAnimationCacheEnabled()) {
v.setDrawingCacheEnabled(true);
ch = v.getDrawingCache();
}
// draw using cache while animating
if (animating && isAnimationCacheEnabled() && ch != null) {
p.setColor(0xffffffff);
canvas.drawBitmap(ch, m, p);
} else { // zoomed or cache unavailable
ch = null;
canvas.save();
canvas.concat(m);
v.draw(canvas);
canvas.restore();
}
// draw minimap
if (showMinimap) {
if (miniMapHeight < 0) {
miniMapHeight = getHeight() / 4;
}
canvas.translate(10.0f, 10.0f);
p.setColor(0x80000000 | 0x00ffffff & miniMapColor);
final float w = miniMapHeight * (float) getWidth() / getHeight();
final float h = miniMapHeight;
canvas.drawRect(0.0f, 0.0f, w, h, p);
if (miniMapCaption != null && miniMapCaption.length() > 0) {
p.setTextSize(miniMapCaptionSize);
p.setColor(miniMapCaptionColor);
p.setAntiAlias(true);
canvas.drawText(miniMapCaption, 10.0f,
10.0f + miniMapCaptionSize, p);
p.setAntiAlias(false);
}
p.setColor(0x80000000 | 0x00ffffff & miniMapColor);
final float dx = w * zoomX / getWidth();
final float dy = h * zoomY / getHeight();
canvas.drawRect(dx - 0.5f * w / zoom, dy - 0.5f * h / zoom, dx
+ 0.5f * w / zoom, dy + 0.5f * h / zoom, p);
canvas.translate(-10.0f, -10.0f);
}
// redraw
// if (animating) {
getRootView().invalidate();
invalidate();
// }
}
}
Just keep in mind that custom view should have only one child that's why these views were wrapped in RelativeLayout
<!-- View which can change matrix of multiple views -->
<com.vitalii.semeruk.model.ZoomView
android:id="#+id/zoomView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Note: ZoomView should have only one child
that's why were wrapped these views in RelativeLayout -->
<RelativeLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Captured photo -->
<com.vitalii.semeruk.model.HandView
android:id="#+id/ivBgHand"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:scaleType="fitXY" />
<!-- Ring -->
<com.vitalii.semeruk.model.RingPinky
android:id="#+id/ivRingPinky"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</com.vitalii.semeruk.model.ZoomView>
Methods to use in Activity are:
zoomTo() and smoothZoomTo()
Example:
ZoomView zoomView = (ZoomView) findViewById(R.id.zoomView);
zoomView.smoothZoomTo(3f, 86, 908);
To change maximum zoom find and replace: float maxZoom = 2.0f; with value which will fit your needs.

Android : Zoom by center pinch

So, I have ScrollView, HorizantalScrollView and BoardView for TicTacToe game.
When user zooms, while i redraw scale into categories of cells through ScalegestureDetector zoom pinch is allocated on top, left of the screen, not at the centre of pinches, how can I allocate it to the center?
here is my project in github: https://github.com/boyfox/TestTicTac.git
Project used com.android.support:appcompat-v7
Anybody have a solution to this problem?
I think that you will want to move to an implementation closer to the Android Dragging and Scaling examples (and to the similar question here).
This is the start of what you need. You can pan the view as before and now scale the view at the center of the pinch gesture. It is your base BoardView with logic to draw points on clicks from here. It should be pretty easy to draw your custom X and O icons instead of circles.
BoardView.java
public class BoardView extends View {
private static final int JUST_SCALED_DURATION = 100;
private static final int MAX_TOUCH_DURATION = 1000;
private static final int MAX_TOUCH_DISTANCE = 10;
private boolean stayedWithinTouchDistance;
private float firstTouchX, firstTouchY;
private long firstTouchTime, lastScaleTime;
private float posX, posY, lastTouchX, lastTouchY;
private ScaleGestureDetector scaleDetector;
private float minScaleFactor = 0.1f;
private float maxScaleFactor = 5.0f;
private float scaleFactor = 1.0f;
private float cellSize = 50.0f;
private int numCells = 50;
private ArrayList<Point> points;
private Paint linePaint, pointPaint;
public BoardView(Context context) {
this(context, null, 0);
}
public BoardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BoardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
points = new ArrayList<>();
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setColor(-65536);
linePaint.setStrokeWidth(1.0f);
pointPaint = new Paint();
pointPaint.setAntiAlias(true);
pointPaint.setColor(-65536);
pointPaint.setStrokeWidth(1.0f);
posX = linePaint.getStrokeWidth();
posY = linePaint.getStrokeWidth();
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
private static float distancePx(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
return (float) Math.sqrt(dx * dx + dy * dy);
}
private float distanceDp(float distancePx) {
return distancePx / getResources().getDisplayMetrics().density;
}
private Point coerceToGrid(Point point) {
point.x = (int) ((((int) (point.x / cellSize)) * cellSize) + (cellSize / 2));
point.y = (int) ((((int) (point.y / cellSize)) * cellSize) + (cellSize / 2));
return point;
}
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
scaleDetector.onTouchEvent(event);
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_DOWN) {
stayedWithinTouchDistance = true;
firstTouchX = lastTouchX = event.getRawX();
firstTouchY = lastTouchY = event.getRawY();
firstTouchTime = System.currentTimeMillis();
} else if (action == MotionEvent.ACTION_MOVE) {
float thisTouchX = event.getRawX();
float thisTouchY = event.getRawY();
boolean justScaled = System.currentTimeMillis() - lastScaleTime < JUST_SCALED_DURATION;
float distancePx = distancePx(firstTouchX, firstTouchY, thisTouchX, thisTouchY);
stayedWithinTouchDistance = stayedWithinTouchDistance &&
distanceDp(distancePx) < MAX_TOUCH_DISTANCE;
if (!stayedWithinTouchDistance && !scaleDetector.isInProgress() && !justScaled) {
posX += thisTouchX - lastTouchX;
posY += thisTouchY - lastTouchY;
invalidate();
}
lastTouchX = thisTouchX;
lastTouchY = thisTouchY;
} else if (action == MotionEvent.ACTION_UP) {
long touchDuration = System.currentTimeMillis() - firstTouchTime;
if (touchDuration < MAX_TOUCH_DURATION && stayedWithinTouchDistance) {
int[] location = {0, 0};
getLocationOnScreen(location);
float x = ((lastTouchX - posX - location[0]) / scaleFactor);
float y = ((lastTouchY - posY - location[1]) / scaleFactor);
points.add(coerceToGrid(new Point((int) x, (int) y)));
invalidate();
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
lastScaleTime = System.currentTimeMillis();
float scale = detector.getScaleFactor();
scaleFactor = Math.max(minScaleFactor, Math.min(scaleFactor * scale, maxScaleFactor));
if (scaleFactor > minScaleFactor && scaleFactor < maxScaleFactor) {
float centerX = detector.getFocusX();
float centerY = detector.getFocusY();
float diffX = centerX - posX;
float diffY = centerY - posY;
diffX = diffX * scale - diffX;
diffY = diffY * scale - diffY;
posX -= diffX;
posY -= diffY;
invalidate();
return true;
}
return false;
}
}
private float getScaledCellSize() {
return scaleFactor * cellSize;
}
private float getScaledBoardSize() {
return numCells * getScaledCellSize();
}
private void drawBoard(Canvas canvas) {
for (int i = 0; i <= numCells; i++) {
float total = getScaledBoardSize();
float offset = getScaledCellSize() * i;
canvas.drawLine(offset, 0, offset, total, linePaint);
canvas.drawLine(0, offset, total, offset, linePaint);
}
}
private void drawPoints(Canvas canvas) {
for (Point point : points) {
float x = point.x * scaleFactor;
float y = point.y * scaleFactor;
float r = getScaledCellSize() / 4;
canvas.drawCircle(x, y, r, pointPaint);
}
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
float total = getScaledBoardSize();
float edge = linePaint.getStrokeWidth();
posX = Math.max(Math.min(edge, getWidth() - total - edge), Math.min(edge, posX));
posY = Math.max(Math.min(edge, getHeight() - total - edge), Math.min(edge, posY));
canvas.save();
canvas.translate(posX, posY);
drawBoard(canvas);
drawPoints(canvas);
canvas.restore();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.client.BoardView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

Android. Canvas scale and translate

I created custom view where you can touch and scale it.
Most of work was created with the help of this post
Next i observe that if i want to zoom in image it is always zooming to top left corner.
Here is my onDraw() method:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
// actual drawing
canvas.getClipBounds(mRect);
canvas.restore();
}
So this is normal behaviour for canvas.scale(px, py); I want from my view be scaling to center point(later i will take focalX, focalY coordinates, but now just to center). I decided to replace
canvas.scale(mScaleFactor, mScaleFactor); to
canvas.scale(mScaleFactor, mScaleFactor, getWidth()/2-mPosX, getHeight()/2-mPosY);
And now it is scaling perfectly at the mid of the image. But it somehow affects my mPosX, mPosY coordinates. I attach an image for better understanding.
Top left corner is eqivalent mPosX = 0, mPosY=0, and that's OK.
Next if i am zooming out next happens:
Now it decides that top left corner is eqivalent to mPosX=0,mPosY=0(top left corner). But in my logic it should be somehow like (mPosX=100, mPosY=130) or something like that.
So my problem is that i am sliding out from my actual content view. What should i do to prevent such behaviour?
I had same question regarding touch points calculation and referring this answer, I solved it my own way using metrics. Above answer provides better pinch zooming and dragging functionality but not correct touch points. Here is the full code and ignore my own variables.
package axis.nbapp;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import java.io.IOException;
public class NewView extends ViewGroup {
float dx;
float dy;
float scaleFac;
Region r;
private Path mPath;
private Paint mPaint;
private Path mTransformedPath;
Stencil stencil;
private Paint cColor;
boolean scaling;
Canvas bmcanvas;
SVG svg = null;
int viewWidth;
int viewHeight;
float svgWidth;
float svgHeight;
// States.
private static final byte NONE = 0;
private static final byte DRAG = 1;
private static final byte ZOOM = 2;
private byte mode = NONE;
// Matrices used to move and zoom image.
private Matrix matrix = new Matrix();
private Matrix matrixInverse = new Matrix();
private Matrix savedMatrix = new Matrix();
// Parameters for zooming.
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
private float[] lastEvent = null;
private long lastDownTime = 0l;
private float[] mDispatchTouchEventWorkingArray = new float[2];
private float[] mOnTouchEventWorkingArray = new float[2];
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
mDispatchTouchEventWorkingArray[0] = ev.getX();
mDispatchTouchEventWorkingArray[1] = ev.getY();
mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
ev.setLocation(mDispatchTouchEventWorkingArray[0], mDispatchTouchEventWorkingArray[1]);
return super.dispatchTouchEvent(ev);
}
public NewView(Context context) {
super(context);
init(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
public NewView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public NewView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
}
/**
* 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 (float) Math.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);
}
private float[] scaledPointsToScreenPoints(float[] a) {
matrix.mapPoints(a);
return a;
}
private float[] screenPointsToScaledPoints(float[] a) {
matrixInverse.mapPoints(a);
return a;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
float[] values = new float[9];
matrix.getValues(values);
canvas.save();
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// handle touch events here
mOnTouchEventWorkingArray[0] = event.getX();
mOnTouchEventWorkingArray[1] = event.getY();
mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
mode = DRAG;
lastEvent = null;
long downTime = event.getDownTime();
if (downTime - lastDownTime < 300l) {
float density = getResources().getDisplayMetrics().density;
if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) < 40.f * density) {
savedMatrix.set(matrix);
mid.set(event.getX(), event.getY());
mode = ZOOM;
lastEvent = new float[4];
lastEvent[0] = lastEvent[1] = event.getX();
lastEvent[2] = lastEvent[3] = event.getY();
}
lastDownTime = 0l;
} else {
lastDownTime = downTime;
}
start.set(event.getX(), event.getY());
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);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
final float density = getResources().getDisplayMetrics().density;
if (mode == DRAG) {
matrix.set(savedMatrix);
dx = event.getX() - start.x;
dy = event.getY() - start.y;
matrix.postTranslate(dx, dy);
matrix.invert(matrixInverse);
if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) > 20.f * density) {
lastDownTime = 0l;
}
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
scaleFac = newDist;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
}
} else {
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
scaleFac = scale;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
}
}
break;
}
float[] values = new float[9];
matrix.getValues(values);
//int x = ((int)(event.getX() - values[2]*values[Matrix.MSCALE_X]))/(int)values[0];
//int y = ((int)(event.getY() - values[5]*values[Matrix.MSCALE_Y]))/(int)values[4];
int x = ((int)(event.getX() / values[Matrix.MSCALE_X] - (values[Matrix.MTRANS_X]/values[Matrix.MSCALE_X])));
int y = ((int)(event.getY() / values[Matrix.MSCALE_Y] - (values[Matrix.MTRANS_Y]/values[Matrix.MSCALE_Y])));
invalidate();
return true;
}
}
Check onTouchEvent you can find transformed x, y touch points from following calculation.
int x = ((int)(event.getX() / values[Matrix.MSCALE_X] - (values[Matrix.MTRANS_X]/values[Matrix.MSCALE_X])));
int y = ((int)(event.getY() / values[Matrix.MSCALE_Y] - (values[Matrix.MTRANS_Y]/values[Matrix.MSCALE_Y])));
Did you try just using getWidth()/2?
As I remember the pivot point parameter always ask for a point from 0 to width. And 0 is always on top left of the image.
So I would try just:
canvas.scale(mScaleFactor, mScaleFactor, getWidth()/2, getHeight()/2);

Zoomable View in Android? [duplicate]

Is there a common way to show a big image and enable the user to zoom in and out and pan the image?
Until now I found two ways:
overwriting ImageView, that seems a little bit too much for such a common problem.
using a webview but with less control over the overall layout etc.
UPDATE
I've just given TouchImageView a new update. It now includes Double Tap Zoom and Fling in addition to Panning and Pinch Zoom. The code below is very dated. You can check out the github project to get the latest code.
USAGE
Place TouchImageView.java in your project. It can then be used the same as
ImageView. Example:
TouchImageView img = (TouchImageView) findViewById(R.id.img);
If you are using TouchImageView in xml, then you must provide the full package
name, because it is a custom view. Example:
<com.example.touch.TouchImageView
android:id="#+id/img”
android:layout_width="match_parent"
android:layout_height="match_parent" />
Note: I've removed my prior answer, which included some very old code and now link straight to the most updated code on github.
ViewPager
If you are interested in putting TouchImageView in a ViewPager, refer to this answer.
I adapted some code to create a TouchImageView that supports multitouch (>2.1).
It is inspired by the book Hello, Android! (3rd edition)
It is contained within the following 3 files
TouchImageView.java
WrapMotionEvent.java
EclairMotionEvent.java
TouchImageView.java
import se.robertfoss.ChanImageBrowser.Viewer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
public class TouchImageView extends ImageView {
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;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
Context context;
public TouchImageView(Context context) {
super(context);
super.setClickable(true);
this.context = context;
matrix.setTranslate(1f, 1f);
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent rawEvent) {
WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent);
// Dump touch event to log
if (Viewer.isDebug == true){
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:
int xDiff = (int) Math.abs(event.getX() - start.x);
int yDiff = (int) Math.abs(event.getY() - start.y);
if (xDiff < 8 && yDiff < 8){
performClick();
}
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
Log.d(TAG, "mode=NONE");
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
// ...
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
} else if (mode == ZOOM) {
float newDist = spacing(event);
Log.d(TAG, "newDist=" + newDist);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
setImageMatrix(matrix);
return true; // indicate event was handled
}
});
}
public void setImage(Bitmap bm, int displayWidth, int displayHeight) {
super.setImageBitmap(bm);
//Fit to screen.
float scale;
if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth())){
scale = (float)displayWidth / (float)bm.getWidth();
} else {
scale = (float)displayHeight / (float)bm.getHeight();
}
savedMatrix.set(matrix);
matrix.set(savedMatrix);
matrix.postScale(scale, scale, mid.x, mid.y);
setImageMatrix(matrix);
// Center the image
float redundantYSpace = (float)displayHeight - (scale * (float)bm.getHeight()) ;
float redundantXSpace = (float)displayWidth - (scale * (float)bm.getWidth());
redundantYSpace /= (float)2;
redundantXSpace /= (float)2;
savedMatrix.set(matrix);
matrix.set(savedMatrix);
matrix.postTranslate(redundantXSpace, redundantYSpace);
setImageMatrix(matrix);
}
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(WrapMotionEvent event) {
// ...
String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE",
"POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid ").append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";");
}
sb.append("]");
Log.d(TAG, sb.toString());
}
/** Determine the space between the first two fingers */
private float spacing(WrapMotionEvent 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, WrapMotionEvent event) {
// ...
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
}
WrapMotionEvent.java
import android.view.MotionEvent;
public class WrapMotionEvent {
protected MotionEvent event;
protected WrapMotionEvent(MotionEvent event) {
this.event = event;
}
static public WrapMotionEvent wrap(MotionEvent event) {
try {
return new EclairMotionEvent(event);
} catch (VerifyError e) {
return new WrapMotionEvent(event);
}
}
public int getAction() {
return event.getAction();
}
public float getX() {
return event.getX();
}
public float getX(int pointerIndex) {
verifyPointerIndex(pointerIndex);
return getX();
}
public float getY() {
return event.getY();
}
public float getY(int pointerIndex) {
verifyPointerIndex(pointerIndex);
return getY();
}
public int getPointerCount() {
return 1;
}
public int getPointerId(int pointerIndex) {
verifyPointerIndex(pointerIndex);
return 0;
}
private void verifyPointerIndex(int pointerIndex) {
if (pointerIndex > 0) {
throw new IllegalArgumentException(
"Invalid pointer index for Donut/Cupcake");
}
}
}
EclairMotionEvent.java
import android.view.MotionEvent;
public class EclairMotionEvent extends WrapMotionEvent {
protected EclairMotionEvent(MotionEvent event) {
super(event);
}
public float getX(int pointerIndex) {
return event.getX(pointerIndex);
}
public float getY(int pointerIndex) {
return event.getY(pointerIndex);
}
public int getPointerCount() {
return event.getPointerCount();
}
public int getPointerId(int pointerIndex) {
return event.getPointerId(pointerIndex);
}
}
I used a WebView and loaded the image from the memory via
webview.loadUrl("file://...")
The WebView handles all the panning zooming and scrolling. If you use wrap_content the webview won't be bigger then the image and no white areas are shown.
The WebView is the better ImageView ;)
In Response to Janusz original question, there are several ways to achieve this all of which vary in their difficulty level and have been stated below. Using a web view is good, but it is very limited in terms of look and feel and controllability. If you are drawing a bitmap from a canvas, the most versatile solutions that have been proposed seems to be MikeOrtiz's, Robert Foss's and/or what Jacob Nordfalk suggested. There is a great example for incorporating the android-multitouch-controller by PaulBourke, and is great for having the multi-touch support and alltypes of custom views.
Personally, if you are simply drawing a canvas to a bitmap and then displaying it inside and ImageView and want to be able to zoom into and move around using multi touch, I find MikeOrtiz's solution as the easiest. However, for my purposes the code from the Git that he has provided seems to only work when his TouchImageView custom ImageView class is the only child or provide the layout params as:
android:layout_height="match_parent"
android:layout_height="match_parent"
Unfortunately due to my layout design, I needed "wrap_content" for "layout_height". When I changed it to this the image was cropped at the bottom and I couldn't scroll or zoom to the cropped region.
So I took a look at the Source for ImageView just to see how Android implemented "onMeasure" and changed MikeOrtiz's to suit.
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//**** ADDED THIS ********/////
int w = (int) bmWidth;
int h = (int) bmHeight;
width = resolveSize(w, widthMeasureSpec);
height = resolveSize(h, heightMeasureSpec);
//**** END ********///
// width = MeasureSpec.getSize(widthMeasureSpec); // REMOVED
// height = MeasureSpec.getSize(heightMeasureSpec); // REMOVED
//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 = 1f;
// Center the image
redundantYSpace = (float)height - (scale * (float)bmHeight) ;
redundantXSpace = (float)width - (scale * (float)bmWidth);
redundantYSpace /= (float)2;
redundantXSpace /= (float)2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
// origHeight = bmHeight;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);
}
Here resolveSize(int,int) is a "Utility to reconcile a desired size with constraints imposed by a MeasureSpec, where :
Parameters:
- size How big the view wants to be
- MeasureSpec Constraints imposed by the parent
Returns:
- The size this view should be."
So essentially providing a behaviour a little more similar to the original ImageView class when the image is loaded. Some more changes could be made to support a greater variety of screens which modify the aspect ratio. But for now I Hope this helps. Thanks to MikeOrtiz for his original code, great work.
You could also try out http://code.google.com/p/android-multitouch-controller/
The library is really great, although initially a little hard to grasp.
I just integrated Robert Foss's TouchImageView: it worked perfectly out of the box! Thanks!
I just modified a bit the code so I could be able to instantiate it from my layout.xml.
Just add two constructors
public TouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TouchImageView(Context context) {
super(context);
init(context);
}
and transform the old constructor into an init method:
private void init(Context context){
//...old code ofconstructor of Robert Moss's code
}
#Robert Foss, #Mike Ortiz, thank you very much for your work. I merged your work, and completed Robert classes for android > 2.0 with Mike additional work.
As result of my work I present Android Touch Gallery, based on ViewPager and used modificated TouchImageView. Images loading by URL and you can zoom and drag them. You can find it here https://github.com/Dreddik/AndroidTouchGallery
Try using ZoomView for zooming any other view.
http://code.google.com/p/android-zoom-view/ it's easy, free and fun to use!
Adding to #Mike's answer. I also needed double tap to restore the image to the original dimensions when first viewed. So I added a whole heap of "orig..." instance variables and added the SimpleOnGestureListener which did the trick.
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);
}
}
This is a very late addition to this thread but I've been working on an image view that supports zoom and pan and has a couple of features I haven't found elsewhere. This started out as a way of displaying very large images without causing OutOfMemoryErrors, by subsampling the image when zoomed out and loading higher resolution tiles when zoomed in. It now supports use in a ViewPager, rotation manually or using EXIF information (90° stops), override of selected touch events using OnClickListener or your own GestureDetector or OnTouchListener, subclassing to add overlays, pan while zooming, and fling momentum.
It's not intended as a general use replacement for ImageView so doesn't extend it, and doesn't support display of images from resources, only assets and external files. It requires SDK 10.
Source is on GitHub, and there's a sample that illustrates use in a ViewPager.
https://github.com/davemorrissey/subsampling-scale-image-view
You can try using the LayoutParams for this
public void zoom(boolean flag){
if(flag){
int width=40;
int height=40;
}
else{
int width=20;
int height=20;
}
RelativeLayout.LayoutParams param=new RelativeLayout.LayoutParams(width,height); //use the parent layout of the ImageView;
imageView.setLayoutParams(param); //imageView is the view which needs zooming.
}
ZoomIn = zoom(true);
ZoomOut = zoom(false);
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageDetail = (ImageView) findViewById(R.id.imageView1);
imageDetail.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
System.out.println("matrix=" + savedMatrix.toString());
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
startPoint.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(midPoint, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y);
} else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, midPoint.x, midPoint.y);
}
}
break;
}
view.setImageMatrix(matrix);
return true;
}
#SuppressLint("FloatMath")
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);
}
});
}
and drawable folder should have bticn image file.
perfectly works :)
Something like below will do it.
#Override public boolean onTouch(View v,MotionEvent e)
{
tap=tap2=drag=pinch=none;
int mask=e.getActionMasked();
posx=e.getX();posy=e.getY();
float midx= img.getWidth()/2f;
float midy=img.getHeight()/2f;
int fingers=e.getPointerCount();
switch(mask)
{
case MotionEvent.ACTION_POINTER_UP:
tap2=1;break;
case MotionEvent.ACTION_UP:
tap=1;break;
case MotionEvent.ACTION_MOVE:
drag=1;
}
if(fingers==2){nowsp=Math.abs(e.getX(0)-e.getX(1));}
if((fingers==2)&&(drag==0)){ tap2=1;tap=0;drag=0;}
if((fingers==2)&&(drag==1)){ tap2=0;tap=0;drag=0;pinch=1;}
if(pinch==1)
{
if(nowsp>oldsp)scale+=0.1;
if(nowsp<oldsp)scale-=0.1;
tap2=tap=drag=0;
}
if(tap2==1)
{
scale-=0.1;
tap=0;drag=0;
}
if(tap==1)
{
tap2=0;drag=0;
scale+=0.1;
}
if(drag==1)
{
movx=posx-oldx;
movy=posy-oldy;
x+=movx;
y+=movy;
tap=0;tap2=0;
}
m.setTranslate(x,y);
m.postScale(scale,scale,midx,midy);
img.setImageMatrix(m);img.invalidate();
tap=tap2=drag=none;
oldx=posx;oldy=posy;
oldsp=nowsp;
return true;
}
public void onCreate(Bundle b)
{
super.onCreate(b);
img=new ImageView(this);
img.setScaleType(ImageView.ScaleType.MATRIX);
img.setOnTouchListener(this);
path=Environment.getExternalStorageDirectory().getPath();
path=path+"/DCIM"+"/behala.jpg";
byte[] bytes;
bytes=null;
try{
FileInputStream fis;
fis=new FileInputStream(path);
BufferedInputStream bis;
bis=new BufferedInputStream(fis);
bytes=new byte[bis.available()];
bis.read(bytes);
if(bis!=null)bis.close();
if(fis!=null)fis.close();
}
catch(Exception e)
{
ret="Nothing";
}
Bitmap bmp=BitmapFactory.decodeByteArray(bytes,0,bytes.length);
img.setImageBitmap(bmp);
setContentView(img);
}
For viewing complete program see here: Program to zoom image in android

Android ImageView Zoom-in and Zoom-Out

I want to Zoom-in and Zoom-out an Android ImageView. I tried most of the samples but in all of them the image in the ImageView itself is getting Zoomed-in and Zoomed-out, while I want to Zoom-in and Zoom-out the ImageView. I want to increase the ImageView width and height while Zooming-in and reduce the ImageView width and height while Zooming-out. How do I achieve that?
Please follow the below class, that is used for Zoom in and Zoom Out for ImageView.
import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class ZoomInZoomOut extends Activity implements OnTouchListener
{
private static final String TAG = "Touch";
#SuppressWarnings("unused")
private static final float MIN_ZOOM = 1f,MAX_ZOOM = 1f;
// 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;
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;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView view = (ImageView) findViewById(R.id.imageView);
view.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event)
{
ImageView view = (ImageView) v;
view.setScaleType(ImageView.ScaleType.MATRIX);
float scale;
dumpEvent(event);
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN: // first finger down only
matrix.set(view.getImageMatrix());
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
Log.d(TAG, "mode=DRAG"); // write to LogCat
mode = DRAG;
break;
case MotionEvent.ACTION_UP: // first finger lifted
case MotionEvent.ACTION_POINTER_UP: // second finger lifted
mode = NONE;
Log.d(TAG, "mode=NONE");
break;
case MotionEvent.ACTION_POINTER_DOWN: // first and second finger down
oldDist = spacing(event);
Log.d(TAG, "oldDist=" + oldDist);
if (oldDist > 5f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
Log.d(TAG, "mode=ZOOM");
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG)
{
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x, event.getY() - start.y); // create the transformation in the matrix of points
}
else if (mode == ZOOM)
{
// pinch zooming
float newDist = spacing(event);
Log.d(TAG, "newDist=" + newDist);
if (newDist > 5f)
{
matrix.set(savedMatrix);
scale = newDist / oldDist; // setting the scaling of the
// matrix...if scale > 1 means
// zoom in...if scale < 1 means
// zoom out
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
view.setImageMatrix(matrix); // display the transformation on screen
return true; // indicate event was handled
}
/*
* --------------------------------------------------------------------------
* 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 (float) Math.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);
}
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(MotionEvent event)
{
String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE","POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN || actionCode == MotionEvent.ACTION_POINTER_UP)
{
sb.append("(pid ").append(action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++)
{
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";");
}
sb.append("]");
Log.d("Touch Events ---------", sb.toString());
}
}
The other implementations here all have some kind of a flaw. so i basically mixed them up and came up with this.
Create a custom view like this:
ZoomableImageView.java:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;
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.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);
}
}
Then add the image like this:
ZoomableImageView touch = (ZoomableImageView)findViewById(R.id.IMAGEID);
touch.setImageBitmap(bitmap);
Add the view like this in XML:
<PACKAGE.ZoomableImageView
android:id="#+id/IMAGEID"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Simple way:
PhotoViewAttacher pAttacher;
pAttacher = new PhotoViewAttacher(Your_Image_View);
pAttacher.update();
Add below line in build.gradle:
compile 'com.commit451:PhotoView:1.2.4'
Make two java classes
Zoom class
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
public class Zoom extends View {
private Drawable image;
ImageButton img,img1;
private int zoomControler=20;
public Zoom(Context context){
super(context);
image=context.getResources().getDrawable(R.drawable.j);
//image=context.getResources().getDrawable(R.drawable.icon);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//here u can control the width and height of the images........ this line is very important
image.setBounds((getWidth()/2)-zoomControler, (getHeight()/2)-zoomControler, (getWidth()/2)+zoomControler, (getHeight()/2)+zoomControler);
image.draw(canvas);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode==KeyEvent.KEYCODE_DPAD_UP){
// zoom in
zoomControler+=10;
}
if(keyCode==KeyEvent.KEYCODE_DPAD_DOWN){
// zoom out
zoomControler-=10;
}
if(zoomControler<10){
zoomControler=10;
}
invalidate();
return true;
}
}
make second class
import android.app.Activity;
import android.os.Bundle;
public class Zoomexample extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(new Zoom(this));
}
}
I have improved the answer I got from stack for flawless ZOOM (two finger) / ROTATION (two finger) / DRAG (Single finger).
//============================XML code==================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.flochat.imageviewzoomforstack.MainActivity">
<ImageView
android:id="#+id/imageview_trash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/trash" />
</LinearLayout>
//============================Java code==========================
public class MainActivity extends AppCompatActivity {
ImageView photoview2;
float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
private boolean isZoomAndRotate;
private boolean isOutSide;
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
float oldDist = 1f;
private float xCoOrdinate, yCoOrdinate;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
photoview2 = findViewById(R.id.imageview_trash);
photoview2.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
view.bringToFront();
viewTransformation(view, event);
return true;
}
});
}
private void viewTransformation(View view, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
xCoOrdinate = view.getX() - event.getRawX();
yCoOrdinate = view.getY() - event.getRawY();
start.set(event.getX(), event.getY());
isOutSide = false;
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
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:
isZoomAndRotate = false;
if (mode == DRAG) {
float x = event.getX();
float y = event.getY();
}
case MotionEvent.ACTION_OUTSIDE:
isOutSide = true;
mode = NONE;
lastEvent = null;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if (!isOutSide) {
if (mode == DRAG) {
isZoomAndRotate = false;
view.animate().x(event.getRawX() + xCoOrdinate).y(event.getRawY() + yCoOrdinate).setDuration(0).start();
}
if (mode == ZOOM && event.getPointerCount() == 2) {
float newDist1 = spacing(event);
if (newDist1 > 10f) {
float scale = newDist1 / oldDist * view.getScaleX();
view.setScaleX(scale);
view.setScaleY(scale);
}
if (lastEvent != null) {
newRot = rotation(event);
view.setRotation((float) (view.getRotation() + (newRot - d)));
}
}
}
break;
}
}
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 (int) Math.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);
}
}
//========================Just pass any view which you want to zoom/rotate/drag to viewTransformation() method. Very applicable for textview zoom. It will not pixelate text.
It's old, but this may help someone else.
Below TouchImageView class supports both zooming in/out on either pinch or double tap
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
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 implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
Matrix 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;
int viewWidth, viewHeight;
static final int CLICK = 3;
float saveScale = 1f;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
ScaleGestureDetector mScaleDetector;
Context context;
public TouchImageView(Context context) {
super(context);
sharedConstructing(context);
}
public TouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
sharedConstructing(context);
}
GestureDetector mGestureDetector;
private void sharedConstructing(Context context) {
super.setClickable(true);
this.context = context;
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setOnDoubleTapListener(this);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix = new Matrix();
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(curr);
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 fixTransX = getFixDragTrans(deltaX, viewWidth,
origWidth * saveScale);
float fixTransY = getFixDragTrans(deltaY, viewHeight,
origHeight * saveScale);
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
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
}
});
}
public void setMaxZoom(float x) {
maxScale = x;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
#Override
public boolean onDoubleTap(MotionEvent e) {
// Double tap is detected
Log.i("MAIN_TAG", "Double tap detected");
float origScale = saveScale;
float mScaleFactor;
if (saveScale == maxScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
} else {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
}
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2,
viewHeight / 2);
fixTrans();
return false;
}
#Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
#Override
public boolean onDown(MotionEvent e) {
return false;
}
#Override
public void onShowPress(MotionEvent e) {
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
#Override
public void onLongPress(MotionEvent e) {
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
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;
}
if (origWidth * saveScale <= viewWidth
|| origHeight * saveScale <= viewHeight)
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2,
viewHeight / 2);
else
matrix.postScale(mScaleFactor, mScaleFactor,
detector.getFocusX(), detector.getFocusY());
fixTrans();
return true;
}
}
void fixTrans() {
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X];
float transY = m[Matrix.MTRANS_Y];
float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
float fixTransY = getFixTrans(transY, viewHeight, origHeight
* saveScale);
if (fixTransX != 0 || fixTransY != 0)
matrix.postTranslate(fixTransX, fixTransY);
}
float getFixTrans(float trans, float viewSize, float contentSize) {
float minTrans, maxTrans;
if (contentSize <= viewSize) {
minTrans = 0;
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
maxTrans = 0;
}
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
}
float getFixDragTrans(float delta, float viewSize, float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
|| viewWidth == 0 || viewHeight == 0)
return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1) {
// Fit to screen.
float scale;
Drawable drawable = getDrawable();
if (drawable == null || drawable.getIntrinsicWidth() == 0
|| drawable.getIntrinsicHeight() == 0)
return;
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
// Center the image
float redundantYSpace = (float) viewHeight
- (scale * (float) bmHeight);
float redundantXSpace = (float) viewWidth
- (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
setImageMatrix(matrix);
}
fixTrans();
}
}
Usage:
You can replace your ImageView with TouchImageView in both XML & java
1. For XML
<?xml version="1.0" encoding="utf-8"?>
<com.example.android.myapp.TouchImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/imViewedImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true" />
2. For Java
TouchImageView imViewedImage = findViewById(R.id.imViewedImage);
This is yet another implementation based on the code posted by Nicolas Tyler.
The following bugs are fixed:
Setting minScale to a number less than 1 now works
You don't need to use setImageBitmap() to set the image (you can use, for example setImageResource()
All constructors now work properly
The following things, amongst others are tidied up:
An OnTouchListener is not used, it's not necessary because the the class can just implement the onTouchEvent() method.
The assignment right = width * saveScale - width - (2 * redundantXSpace * saveScale); has been simplified to right = (originalBitmapWidth * saveScale) - width Which, in my option, is much less confusing.
Some member variables are removed (more state can probably be removed from this class)
It's not perfect but here you go:
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
/**
* Created by alex on 23/02/16.
* Based on code posted by Nicolas Tyler here:
* https://stackoverflow.com/questions/6650398/android-imageview-zoom-in-and-zoom-out
*/
public class ZoomableImageView extends ImageView {
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
float newScale = saveScale * scaleFactor;
if (newScale < maxScale && newScale > minScale) {
saveScale = newScale;
float width = getWidth();
float height = getHeight();
right = (originalBitmapWidth * saveScale) - width;
bottom = (originalBitmapHeight * saveScale) - height;
float scaledBitmapWidth = originalBitmapWidth * saveScale;
float scaledBitmapHeight = originalBitmapHeight * saveScale;
if (scaledBitmapWidth <= width || scaledBitmapHeight <= height) {
matrix.postScale(scaleFactor, scaleFactor, width / 2, height / 2);
} else {
matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
}
}
return true;
}
}
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
static final int CLICK = 3;
private int mode = NONE;
private Matrix matrix = new Matrix();
private PointF last = new PointF();
private PointF start = new PointF();
private float minScale = 0.5f;
private float maxScale = 4f;
private float[] m;
private float redundantXSpace, redundantYSpace;
private float saveScale = 1f;
private float right, bottom, originalBitmapWidth, originalBitmapHeight;
private ScaleGestureDetector mScaleDetector;
public ZoomableImageView(Context context) {
super(context);
init(context);
}
public ZoomableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
super.setClickable(true);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int bmHeight = getBmHeight();
int bmWidth = getBmWidth();
float width = getMeasuredWidth();
float height = getMeasuredHeight();
//Fit to screen.
float scale = width > height ? height / bmHeight : width / bmWidth;
matrix.setScale(scale, scale);
saveScale = 1f;
originalBitmapWidth = scale * bmWidth;
originalBitmapHeight = scale * bmHeight;
// Center the image
redundantYSpace = (height - originalBitmapHeight);
redundantXSpace = (width - originalBitmapWidth);
matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2);
setImageMatrix(matrix);
}
#Override
public boolean onTouchEvent(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(originalBitmapWidth * saveScale);// width after applying current scale
float scaleHeight = Math.round(originalBitmapHeight * saveScale);// height after applying current scale
boolean limitX = false;
boolean limitY = false;
//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 < getWidth() && scaleHeight < getHeight()) {
// don't do anything
}
else if (scaleWidth < getWidth()) {
deltaX = 0;
limitY = true;
}
//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 < getHeight()) {
deltaY = 0;
limitX = true;
}
//if the image doesnt fit in the width or height
//limit both up and down and left and right
else {
limitX = true;
limitY = true;
}
if (limitY) {
if (y + deltaY > 0) {
deltaY = -y;
} else if (y + deltaY < -bottom) {
deltaY = -(y + bottom);
}
}
if (limitX) {
if (x + deltaX > 0) {
deltaX = -x;
} else if (x + deltaX < -right) {
deltaX = -(x + right);
}
}
//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;
}
public void setMaxZoom(float x) {
maxScale = x;
}
private int getBmWidth() {
Drawable drawable = getDrawable();
if (drawable != null) {
return drawable.getIntrinsicWidth();
}
return 0;
}
private int getBmHeight() {
Drawable drawable = getDrawable();
if (drawable != null) {
return drawable.getIntrinsicHeight();
}
return 0;
}
}
just use this class : TouchImageView
I made my own custom imageview with pinch to zoom. There is no limits/borders on Chirag Ravals code, so user can drag the image off the screen.
Here is the CustomImageView class:
public class CustomImageVIew extends ImageView implements OnTouchListener {
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
private int mode = NONE;
private PointF mStartPoint = new PointF();
private PointF mMiddlePoint = new PointF();
private Point mBitmapMiddlePoint = new Point();
private float oldDist = 1f;
private float matrixValues[] = {0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f};
private float scale;
private float oldEventX = 0;
private float oldEventY = 0;
private float oldStartPointX = 0;
private float oldStartPointY = 0;
private int mViewWidth = -1;
private int mViewHeight = -1;
private int mBitmapWidth = -1;
private int mBitmapHeight = -1;
private boolean mDraggable = false;
public CustomImageVIew(Context context) {
this(context, null, 0);
}
public CustomImageVIew(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomImageVIew(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setOnTouchListener(this);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
public void setBitmap(Bitmap bitmap){
if(bitmap != null){
setImageBitmap(bitmap);
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mBitmapMiddlePoint.x = (mViewWidth / 2) - (mBitmapWidth / 2);
mBitmapMiddlePoint.y = (mViewHeight / 2) - (mBitmapHeight / 2);
matrix.postTranslate(mBitmapMiddlePoint.x, mBitmapMiddlePoint.y);
this.setImageMatrix(matrix);
}
}
#Override
public boolean onTouch(View v, MotionEvent event){
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
mStartPoint.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if(oldDist > 10f){
savedMatrix.set(matrix);
midPoint(mMiddlePoint, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if(mode == DRAG){
drag(event);
} else if(mode == ZOOM){
zoom(event);
}
break;
}
return true;
}
public void drag(MotionEvent event){
matrix.getValues(matrixValues);
float left = matrixValues[2];
float top = matrixValues[5];
float bottom = (top + (matrixValues[0] * mBitmapHeight)) - mViewHeight;
float right = (left + (matrixValues[0] * mBitmapWidth)) -mViewWidth;
float eventX = event.getX();
float eventY = event.getY();
float spacingX = eventX - mStartPoint.x;
float spacingY = eventY - mStartPoint.y;
float newPositionLeft = (left < 0 ? spacingX : spacingX * -1) + left;
float newPositionRight = (spacingX) + right;
float newPositionTop = (top < 0 ? spacingY : spacingY * -1) + top;
float newPositionBottom = (spacingY) + bottom;
boolean x = true;
boolean y = true;
if(newPositionRight < 0.0f || newPositionLeft > 0.0f){
if(newPositionRight < 0.0f && newPositionLeft > 0.0f){
x = false;
} else{
eventX = oldEventX;
mStartPoint.x = oldStartPointX;
}
}
if(newPositionBottom < 0.0f || newPositionTop > 0.0f){
if(newPositionBottom < 0.0f && newPositionTop > 0.0f){
y = false;
} else{
eventY = oldEventY;
mStartPoint.y = oldStartPointY;
}
}
if(mDraggable){
matrix.set(savedMatrix);
matrix.postTranslate(x? eventX - mStartPoint.x : 0, y? eventY - mStartPoint.y : 0);
this.setImageMatrix(matrix);
if(x)oldEventX = eventX;
if(y)oldEventY = eventY;
if(x)oldStartPointX = mStartPoint.x;
if(y)oldStartPointY = mStartPoint.y;
}
}
public void zoom(MotionEvent event){
matrix.getValues(matrixValues);
float newDist = spacing(event);
float bitmapWidth = matrixValues[0] * mBitmapWidth;
float bimtapHeight = matrixValues[0] * mBitmapHeight;
boolean in = newDist > oldDist;
if(!in && matrixValues[0] < 1){
return;
}
if(bitmapWidth > mViewWidth || bimtapHeight > mViewHeight){
mDraggable = true;
} else{
mDraggable = false;
}
float midX = (mViewWidth / 2);
float midY = (mViewHeight / 2);
matrix.set(savedMatrix);
scale = newDist / oldDist;
matrix.postScale(scale, scale, bitmapWidth > mViewWidth ? mMiddlePoint.x : midX, bimtapHeight > mViewHeight ? mMiddlePoint.y : midY);
this.setImageMatrix(matrix);
}
/** 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 (float)Math.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);
}
}
This is how you can use it in your activity:
CustomImageVIew mImageView = (CustomImageVIew)findViewById(R.id.customImageVIew1);
mImage.setBitmap(your bitmap);
And layout:
<your.package.name.CustomImageVIew
android:id="#+id/customImageVIew1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="15dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:scaleType="matrix"/> // important
I think Chirag Ravals' answer is great!
The only thing it could be improved is moving all this code inside some class like:
PinchZoomImageView extends ImageView {...
and adding there initial Image Matrix initialization to prevent zooming after the first tap:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
matrix = new Matrix(this.getImageMatrix());
}
BTW, this will fix a bug mentioned by Muhammad Umar and Baz
P.S. Having Max and Min zoom limits could be also useful.
E.g Max zoom is 2X and min zoom is the original scale when the image is fitted to screen:
static final int MAX_SCALE_FACTOR = 2;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Getting initial Image matrix
mViewMatrix = new Matrix(this.getImageMatrix());
mMinScaleMatrix = new Matrix(mViewMatrix);
float initialScale = getMatrixScale(mViewMatrix);
if (initialScale < 1.0f) // Image is bigger than screen
mMaxScale = MAX_SCALE_FACTOR;
else
mMaxScale = MAX_SCALE_FACTOR * initialScale;
mMinScale = getMatrixScale(mMinScaleMatrix);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
// We set scale only after onMeasure was called and automatically fit image to screen
if(!mWasScaleTypeSet) {
view.setScaleType(ImageView.ScaleType.MATRIX);
mWasScaleTypeSet = true;
}
float scale;
dumpEvent(event);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // first finger down only
mCurSavedMatrix.set(mViewMatrix);
start.set(event.getX(), event.getY());
mCurrentMode = DRAG;
break;
case MotionEvent.ACTION_UP: // first finger lifted
case MotionEvent.ACTION_POINTER_UP: // second finger lifted
mCurrentMode = NONE;
float resScale = getMatrixScale(mViewMatrix);
if (resScale > mMaxScale) {
downscaleMatrix(resScale, mViewMatrix);
} else if (resScale < mMinScale)
mViewMatrix = new Matrix(mMinScaleMatrix);
else if ((resScale - mMinScale) < 0.1f) // Don't allow user to drag picture outside in case of FIT TO WINDOW zoom
mViewMatrix = new Matrix(mMinScaleMatrix);
else
break;
break;
case MotionEvent.ACTION_POINTER_DOWN: // first and second finger down
mOldDist = spacing(event);
Helper.LOGD(TAG, "oldDist=" + mOldDist);
if (mOldDist > 5f) {
mCurSavedMatrix.set(mViewMatrix);
midPoint(mCurMidPoint, event);
mCurrentMode = ZOOM;
Helper.LOGD(TAG, "mode=ZOOM");
}
break;
case MotionEvent.ACTION_MOVE:
if (mCurrentMode == DRAG) {
mViewMatrix.set(mCurSavedMatrix);
mViewMatrix.postTranslate(event.getX() - start.x, event.getY() - start.y); // create the transformation in the matrix of points
} else if (mCurrentMode == ZOOM) {
// pinch zooming
float newDist = spacing(event);
Helper.LOGD(TAG, "newDist=" + newDist);
if (newDist > 1.f) {
mViewMatrix.set(mCurSavedMatrix);
scale = newDist / mOldDist; // setting the scaling of the
// matrix...if scale > 1 means
// zoom in...if scale < 1 means
// zoom out
mViewMatrix.postScale(scale, scale, mCurMidPoint.x, mCurMidPoint.y);
}
}
break;
}
view.setImageMatrix(mViewMatrix); // display the transformation on screen
return true; // indicate event was handled
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////// PRIVATE SECTION ///////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// These matrices will be used to scale points of the image
private Matrix mViewMatrix = new Matrix();
private Matrix mCurSavedMatrix = new Matrix();
// These PointF objects are used to record the point(s) the user is touching
private PointF start = new PointF();
private PointF mCurMidPoint = new PointF();
private float mOldDist = 1f;
private Matrix mMinScaleMatrix;
private float mMinScale;
private float mMaxScale;
float[] mTmpValues = new float[9];
private boolean mWasScaleTypeSet;
/**
* Returns scale factor of the Matrix
* #param matrix
* #return
*/
private float getMatrixScale(Matrix matrix) {
matrix.getValues(mTmpValues);
return mTmpValues[Matrix.MSCALE_X];
}
/**
* Downscales matrix with the scale to maximum allowed scale factor, but the same translations
* #param scale
* #param dist
*/
private void downscaleMatrix(float scale, Matrix dist) {
float resScale = mMaxScale / scale;
dist.postScale(resScale, resScale, mCurMidPoint.x, mCurMidPoint.y);
}
I got the most helpful answer from #Nicolas Tyler but I had issues with how the syntax and logic worked. I also did not want any Alpha space and my Algebra was rusty! After 3 days I have put together my own version of this.
My answer differs from #Nicolas Tyler with the following:
Different variable names which I found made more sense in underlying contextual uses
This Pinch-Zoom image class does NOT show alpha space and will let you zoom in and out and still not let you over/under pan an image and reveal alpha space
Added deep comments to the matrix section to explain what was going on with the math involved
This image class will also let you pass in a resourceId and it will create a bitmap from it
Much simpler algorithms for both scaling and translation and few variables
Changing the image within this will cause it to zoom in/out such that the new image takes up the view container
A great resource refresher on Algebra can be found here:
https://youtu.be/IiXB6tYtY4w?t=4m12s
That video covers the core of Scalar and Translation matrixes (and will help you make sense of the MTRANS_X and MTRANS_Y stuff). If you have questions ask and I will do my best to answer (but I am NO Algebra expert).
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;
public class iImage extends ImageView
{
static final int NONE_MODE = 0;
static final int DRAG_MODE = 1;
static final int ZOOM_MODE = 2;
int _mode = NONE_MODE;
Matrix _matrix = new Matrix();
PointF _previousPoint = new PointF();
PointF _startPoint = new PointF();
float _currentScale = 1f;
float _minScale = 1f;
float _maxScale = 3f;
float[] _arrayOf9Floats;
float _bitmapWidth, _bitmapHeight,_displayWidth, _displayHeight;
ScaleGestureDetector _scaleDetector;
Context _context;
public iImage(Context context)
{
super(context);
super.setClickable(true);
_context = context;
_scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
_arrayOf9Floats = new float[9];
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return handleTouch(v, event);
}
});
}
private boolean handleTouch(View v, MotionEvent event)
{
_scaleDetector.onTouchEvent(event);
//Contrary to how this line looks, the matrix is not setting the values from the arrayOf9Floats, but rather taking the
//matrix values and assigning them into the arrayOf9Floats. I extremely dislike this syntax and I think
//it should have been written as _arrayOf9Floats = _matrix.getValues() but that's Android for you!!!
_matrix.getValues(_arrayOf9Floats);
//Look at https://youtu.be/IiXB6tYtY4w?t=4m12s , it shows scale, rotate, and translate matrices
//If you look at the translate matrix, you'll see that the 3rd and 6th values are the values which represent x and y translations respectively
//this corresponds to the 2nd and 5th values in the array and hence why the MTRANS_X and MTRANS_Y have the constants 2 and 5 respectively
float xTranslate = _arrayOf9Floats[Matrix.MTRANS_X];
float yTranslate = _arrayOf9Floats[Matrix.MTRANS_Y];
PointF currentEventPoint = new PointF(event.getX(), event.getY());
switch (event.getAction())
{
//First finger down only
case MotionEvent.ACTION_DOWN:
_previousPoint.set(event.getX(), event.getY());
_startPoint.set(_previousPoint);
_mode = DRAG_MODE;
break;
//Second finger down
case MotionEvent.ACTION_POINTER_DOWN:
_previousPoint.set(event.getX(), event.getY());
_startPoint.set(_previousPoint);
_mode = ZOOM_MODE;
break;
case MotionEvent.ACTION_MOVE:
if (_mode == ZOOM_MODE || _mode == DRAG_MODE )
{
float deltaX = currentEventPoint.x - _previousPoint.x;
float deltaY = currentEventPoint.y - _previousPoint.y;
//In matrix terms, going right is + and going left is +
//Moving the image right past 0 means it will show alpha space on the left so we dont want that
//Keep in mind this is a TOP LEFT pivot point, so we dont want the top left to be past 0 lest we have alpha space
if(xTranslate + deltaX > 0)
{
//get absolute of how much into the negative we would have gone
float excessDeltaX = Math.abs(xTranslate + deltaX);
//take that excess away from deltaX so X wont got less than 0 after the translation
deltaX = deltaX - excessDeltaX;
}
//Going left we dont want the negative value to be less than the negative width of the sprite, lest we get alpha space on the right
//The width is going to be the width of the bitmap * scale and we want the - of it because we are checking for left movement
//We also need to account for the width of the DISPLAY CONTAINER (i.e. _displayWidth) so that gets subtracted
//i.e. we want the max scroll width value
float maxScrollableWidth = _bitmapWidth * _currentScale - _displayWidth;
if(xTranslate + deltaX < -maxScrollableWidth)
{
//this forces the max possible translate to always match the - of maxScrollableWidth
deltaX = -maxScrollableWidth - xTranslate;
}
//repeat for Y
if(yTranslate + deltaY > 0)
{
float excessDeltaY = Math.abs(yTranslate + deltaY);
deltaY = deltaY - excessDeltaY;
}
float maxScrollableHeight = _bitmapHeight * _currentScale - _displayWidth;
if(yTranslate + deltaY < -maxScrollableHeight)
{
//this forces the max possible translate to always match the - of maxScrollableWidth
deltaY = -maxScrollableHeight - yTranslate;
}
_matrix.postTranslate(deltaX, deltaY);
_matrix.getValues(_arrayOf9Floats);
//System.out.println(_matrix);
_previousPoint.set(currentEventPoint.x, currentEventPoint.y);
}
break;
case MotionEvent.ACTION_POINTER_UP:
_mode = NONE_MODE;
break;
}
setImageMatrix(_matrix);
invalidate();
return true;
}
#Override
public void setImageBitmap(Bitmap bm)
{
super.setImageBitmap(bm);
_bitmapWidth = bm.getWidth();
_bitmapHeight = bm.getHeight();
invalidate();
}
#Override
public void setImageResource(int resid)
{
Bitmap bitmapImage = BitmapFactory.decodeResource(_context.getResources(), resid);
setImageBitmap(bitmapImage);
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
#Override
public boolean onScaleBegin(ScaleGestureDetector detector)
{
_mode = ZOOM_MODE;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector)
{
float scaleFactor = detector.getScaleFactor();
float originalScale = _currentScale;
_currentScale *= scaleFactor;
//Zoom in too much
if (_currentScale > _maxScale) {
_currentScale = _maxScale;
scaleFactor = _maxScale / originalScale;
}//Zoom out too much
else if (_currentScale < _minScale) {
_currentScale = _minScale;
scaleFactor = _minScale / originalScale;
}
_matrix.postScale(scaleFactor,scaleFactor);
return true;
}
}
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
_displayWidth = MeasureSpec.getSize(widthMeasureSpec);
_displayHeight = MeasureSpec.getSize(heightMeasureSpec);
adjustScale();
}
private void adjustScale()
{
//Fit to display bounds with NO alpha space
float scale;
float scaleX = _displayWidth / _bitmapWidth;
float scaleY = _displayHeight / _bitmapHeight;
scale = Math.max(scaleX, scaleY);
_matrix.setScale(scale, scale);
setImageMatrix(_matrix);
_currentScale = scale;
_minScale = scale;
}
public void setMaxZoom(float maxZoom){_maxScale = maxZoom;}
public void setMinZoom(float minZoom) {_minScale = minZoom;}
}
This code works and implement the double tap to return to original image size.
1st step - In your xml layout put this:
<com.****.*****.TouchImageView
android:id="#+id/action_infolinks_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#mipmap/myinfolinks_splash"
android:layout_gravity="center"
android:gravity="center"
android:scaleType="fitCenter"
android:contentDescription="#string/aboutSupport_description_image"/>
2nd step- Create a file (TouchImageView.java) with the TouchImageView class:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
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;
// 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;
int viewWidth, viewHeight;
static final int CLICK = 3;
static final float SAVE_SCALE = 1f;
float saveScale = SAVE_SCALE;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
float origScale, bottom, origBottom, right, origRight;
ScaleGestureDetector mScaleDetector;
GestureDetector mGestureDetector;
Context context;
public TouchImageView(Context context) {
super(context);
sharedConstructing(context);
}
public TouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
sharedConstructing(context);
}
private void sharedConstructing(Context context) {
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix = new Matrix();
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);
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(curr);
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 fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);
float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
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;
}
});
}
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 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 = viewWidth * saveScale - viewWidth - (2 * redundantXSpace * saveScale);
bottom = viewHeight * saveScale - viewHeight - (2 * redundantYSpace * saveScale);
if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);
else
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
fixTrans();
return true;
}
}
void fixTrans() {
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X];
float transY = m[Matrix.MTRANS_Y];
float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);
if (fixTransX != 0 || fixTransY != 0)
matrix.postTranslate(fixTransX, fixTransY);
}
float getFixTrans(float trans, float viewSize, float contentSize) {
float minTrans, maxTrans;
if (contentSize <= viewSize) {
minTrans = 0;
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
maxTrans = 0;
}
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
}
float getFixDragTrans(float delta, float viewSize, float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight || viewWidth == 0 || viewHeight == 0) return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1) {
// Fit to screen.
float scale;
int bmWidth,bmHeight;
Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.mipmap.myinfolinks_splash);
bmWidth = bm.getWidth();
bmHeight = bm.getHeight();
int w = bmWidth;
int h = bmHeight;
viewWidth = resolveSize(w, widthMeasureSpec);
viewHeight = resolveSize(h, heightMeasureSpec);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
saveScale = SAVE_SCALE;
origScale = scale;
// Center the image
redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
origRedundantXSpace = redundantXSpace;
origRedundantYSpace = redundantYSpace;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
right = viewWidth * saveScale - viewWidth - (2 * redundantXSpace * saveScale);
bottom = viewHeight * saveScale - viewHeight - (2 * redundantYSpace * saveScale);
origRight = right;
origBottom = bottom;
setImageMatrix(matrix);
}
fixTrans();
}
}
And finally, make the call in your main activity:
TouchImageView imgDisplay = (TouchImageView) messageView.findViewById(R.id.id_myImage);
imgDisplay.setMaxZoom(2f);
imgDisplay.setImageResource(R.drawable.myImage);
I saw lots of code and after my adjustments it's working. Enjoy!
Step 1:
First you add dependencies in build.gradle(Module:app) file.
dependencies {
implementation 'com.jsibbold:zoomage:1.2.0'
}
Step 2:
Next create a class
ImageFullScreenFragment.java
public class ImageFullScreenFragment{
private ZoomageView ImageZoomageView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = null;
try {
view = inflater.inflate(R.layout.fragment_image_full_screen, container, false);
ImageZoomageView = view.findViewById(R.id.imageViewImageFullScreen);
ImageZoomageView.setImageResource(R.drawable.image);
} catch (Exception e) {
e.printStackTrace();
}
return view;
}
Step 3 :
Next create a layout xml file
fragment_image_full_screen.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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.jsibbold.zoomage.ZoomageView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:zoomage_restrictBounds="false"
app:zoomage_animateOnReset="true"
app:zoomage_autoResetMode="UNDER"
app:zoomage_autoCenter="true"
app:zoomage_zoomable="true"
app:zoomage_translatable="true"
app:zoomage_minScale="0.6"
app:zoomage_maxScale="8"
android:id="#+id/imageViewImageFullScreen"
/>
</RelativeLayout>
OutPut:-
You should put the image in webview and work with that. Zoom in / out controls are available in webview.
http://xjaphx.wordpress.com/2011/09/18/using-webview-as-a-image-zoom-view-control/
http://androidforums.com/android-lounge/121522-how-display-image-sdcard-webview.html
Try the following:
package com.example.nwssugeoinformationmobileapplication;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.util.FloatMath;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View.OnTouchListener;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends Activity implements OnTouchListener {
private static final String TAG = "Touch";
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
private ImageView view;
private float[] matrixValues = new float[9];
private float maxZoom;
private float minZoom;
private float height;
private float width;
private RectF viewRect;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TabHost th = (TabHost) findViewById (R.id.tabhost);
th.setup();
TabSpec specs = th.newTabSpec("tag1");
specs.setContent(R.id.tab1);
specs.setIndicator("Map");
th.addTab(specs);
specs = th.newTabSpec("tag2");
specs.setContent(R.id.tab2);
specs.setIndicator("Search");
th.addTab(specs);
view = (ImageView) findViewById(R.id.imageView1);
Drawable bitmap = getResources().getDrawable(R.drawable.map);
view.setImageDrawable(bitmap);
view.setOnTouchListener(this);
matrix.setTranslate(1f, 1f);
view.setImageMatrix(matrix);
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
init();
}
}
private void init() {
maxZoom = 2;
minZoom = 1f;
height = view.getDrawable().getIntrinsicHeight();
width = view.getDrawable().getIntrinsicWidth();
viewRect = new RectF(0, 0, view.getWidth(), view.getHeight());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menus, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId()== R.id.item1){
Log.d("Tracks", "Track Us was Clicked");
startActivity(new Intent (MainActivity.this, Tracklocation.class ));
}
if(item.getItemId()== R.id.item2){
Log.d("Updates", "Updates was Clicked");
startActivity(new Intent (MainActivity.this, Updates.class ));
}
if(item.getItemId()== R.id.item3){
Log.d("About Us", "About Us was Clicked");
startActivity(new Intent (MainActivity.this, Horoscope.class ));
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onTouch(View v, MotionEvent rawEvent) {
ImageView view = (ImageView) v;
view.setScaleType(ImageView.ScaleType.MATRIX);
dumpEvent(rawEvent);
// Handle touch events here...
switch (rawEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(rawEvent.getX(), rawEvent.getY());
Log.d(TAG, "mode=DRAG");
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(rawEvent);
Log.d(TAG, "oldDist=" + oldDist);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, rawEvent);
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 == DRAG) {
matrix.set(savedMatrix);
// 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 = rawEvent.getX() - start.x;
float dy = rawEvent.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);
} else if (mode == ZOOM) {
float newDist = spacing(rawEvent);
Log.d(TAG, "newDist=" + newDist);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale1 = newDist / oldDist;
matrix.getValues(matrixValues);
float currentScale = matrixValues[Matrix.MSCALE_X];
// limit zoom
if (scale1 * currentScale > maxZoom) {
scale1 = maxZoom / currentScale;
} else if (scale1 * currentScale < minZoom) {
scale1 = minZoom / currentScale;
}
matrix.postScale(scale1, scale1, mid.x, mid.y);
}
}
break;
}
view.setImageMatrix(matrix);
return true;
}
#SuppressWarnings("deprecation")
private void dumpEvent(MotionEvent event) {
String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE",
"POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid ").append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";");
}
sb.append("]");
Log.d(TAG, sb.toString());
}
/** 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 */
#SuppressLint("FloatMath")
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 needed something similar, but needed the ability to get the dimensions easily and also drag/drop. I based this off of the answer #Nicolas Tyler gave and modified it from there.
The features are pinch zoom in/out, long press to vibration/highlighted drag/drop.
To use it add this CustomZoomView class to your project.
public class CustomZoomView extends View implements View.OnTouchListener, View.OnLongClickListener{
private Paint mPaint;
Vibrator v;
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
static final int MOVE = 3;
private int mode = NONE;
Rect src;
Rect mTempDst = new Rect();
Rect dst = new Rect();
Bitmap mBitmap;
private int mBitmapWidth = -1;
private int mBitmapHeight = -1;
private PointF mStartPoint = new PointF();
private PointF mMiddlePoint = new PointF();
private PointF mStartDragPoint = new PointF();
private PointF mMovePoint = new PointF();
private float oldDist = 1f;
private float scale;
private float oldEventX = 0;
private float oldEventY = 0;
private float oldStartPointX = 0;
private float oldStartPointY = 0;
private int mViewWidth = -1;
private int mViewHeight = -1;
private boolean mDraggable = false;
public CustomZoomView(Context context) {
this(context, null, 0);
}
public CustomZoomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomZoomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setOnTouchListener(this);
this.setOnLongClickListener(this);
v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mPaint = new Paint();
mPaint.setColorFilter(new PorterDuffColorFilter(Color.argb(100,255,255,255), PorterDuff.Mode.SRC_IN));
}
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
public void setBitmap(Bitmap bitmap) {
if (bitmap != null) {
src = new Rect();
src.left = 0;
src.top = 0;
src.right = bitmap.getWidth();
src.bottom = bitmap.getHeight();
mBitmap = bitmap;
mBitmapWidth = bitmap.getWidth() * 1;
mBitmapHeight = bitmap.getHeight() * 1;
dst = new Rect();
dst.left = (mViewWidth / 2) - (mBitmapWidth / 2);
dst.top = (mViewHeight / 2) - (mBitmapHeight / 2);
dst.right = (mViewWidth / 2) + (mBitmapWidth / 2);
dst.bottom = (mViewHeight / 2) + (mBitmapHeight / 2);
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mStartPoint.set(event.getX(), event.getY());
mStartDragPoint.set(event.getX(), event.getY());
mTempDst.set(dst.left, dst.top, dst.right, dst.bottom);
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
midPoint(mMiddlePoint, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mode == ZOOM) {
mBitmapWidth = dst.right - dst.left;
mBitmapHeight = dst.bottom - dst.top;
}
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
mMovePoint.x = event.getX();
mMovePoint.y = event.getY();
drag(event);
} else if (mode == ZOOM) {
zoom(event);
} else if (mode == MOVE) {
move(event);
}
break;
}
return false;
}
public void move(MotionEvent event) {
int xChange = (int) (event.getX() - mStartPoint.x);
int yChange = (int) (event.getY() - mStartPoint.y);
dst.left = mTempDst.left + (xChange);
dst.top = mTempDst.top + (yChange);
dst.right = mTempDst.right + (xChange);
dst.bottom = mTempDst.bottom + (yChange);
invalidate();
}
public void drag(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
float spacingX = eventX - mStartDragPoint.x;
float spacingY = eventY - mStartDragPoint.y;
float newPositionLeft = (dst.left < 0 ? spacingX : spacingX * -1) + dst.left;
float newPositionRight = (spacingX) + dst.right;
float newPositionTop = (dst.top < 0 ? spacingY : spacingY * -1) + dst.top;
float newPositionBottom = (spacingY) + dst.bottom;
boolean x = true;
boolean y = true;
if (newPositionRight < 0.0f || newPositionLeft > 0.0f) {
if (newPositionRight < 0.0f && newPositionLeft > 0.0f) {
x = false;
} else {
eventX = oldEventX;
mStartDragPoint.x = oldStartPointX;
}
}
if (newPositionBottom < 0.0f || newPositionTop > 0.0f) {
if (newPositionBottom < 0.0f && newPositionTop > 0.0f) {
y = false;
} else {
eventY = oldEventY;
mStartDragPoint.y = oldStartPointY;
}
}
if (mDraggable) {
if (x) oldEventX = eventX;
if (y) oldEventY = eventY;
if (x) oldStartPointX = mStartDragPoint.x;
if (y) oldStartPointY = mStartDragPoint.y;
}
}
public void zoom(MotionEvent event) {
float newDist = spacing(event);
boolean in = newDist > oldDist;
if (!in && scale < .01f) {
return;
}
scale = newDist / oldDist;
int xChange = (int) ((mBitmapWidth * scale) / 2);
int yChange = (int) ((mBitmapHeight * scale) / 2);
if (xChange > 10 && yChange > 10) { //ADDED THIS TO KEEP IT FROM GOING INVERSE
int xMidPoint = ((dst.right - dst.left) / 2) + dst.left;
int yMidPoint = ((dst.bottom - dst.top) / 2) + dst.top;
dst.left = (int) (float) (xMidPoint - xChange);
dst.top = (int) (float) (yMidPoint - yChange);
dst.right = (int) (float) (xMidPoint + xChange);
dst.bottom = (int) (float) (yMidPoint + yChange);
}
invalidate();
}
/**
* 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 (float) Math.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);
}
#Override
public boolean onLongClick(View view) {
if (mode == DRAG) {
if ((mStartPoint.x > dst.left && mStartPoint.x < dst.right) && (mStartPoint.y < dst.bottom && mStartPoint.y > dst.top)
&& (mMovePoint.x > dst.left && mMovePoint.x < dst.right) && (mMovePoint.y < dst.bottom && mMovePoint.y > dst.top)) {
mode = MOVE;
v.vibrate(500);
}
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mode == MOVE) {
canvas.drawBitmap(mBitmap, src, dst, null);
canvas.drawBitmap(mBitmap, src, dst, mPaint);
} else {
canvas.drawBitmap(mBitmap, src, dst, null);
}
}
}
...then add this to your activity
CustomZoomView customImageView = (CustomZoomView) findViewById(R.id.customZoomView);
customImageView.setBitmap(yourBitmap);
...and this in your view in xml.
<your.package.name.CustomZoomView
android:id="#+id/customZoomView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:longClickable="true"/>
...and add this to your manifest
<uses-permission android:name="android.permission.VIBRATE"/>
Here is my solution, it is based on #alexbirkett's solution.
public class ZoomImageView extends ImageView {
// region . Static fields .
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
static final int CLICK = 3;
// endregion . Static fields .
// region . Fields .
private int mode = NONE;
private Matrix mMatrix = new Matrix();
private PointF mLastTouch = new PointF();
private PointF mStartTouch = new PointF();
private float minScale = 0.5f;
private float maxScale = 4f;
private float[] mCriticPoints;
private float mScale = 1f;
private float mRight;
private float mBottom;
private float mOriginalBitmapWidth;
private float mOriginalBitmapHeight;
private ScaleGestureDetector mScaleDetector;
//endregion . Fields .
// region . Ctor .
public ZoomImageView(Context context) {
super(context);
init(context);
}
public ZoomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
// endregion . Ctor .
// region . Overrider .
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int bmHeight = getBmHeight();
int bmWidth = getBmWidth();
float width = getMeasuredWidth();
float height = getMeasuredHeight();
float scale = 1;
// If image is bigger then display fit it to screen.
if (width < bmWidth || height < bmHeight) {
scale = width > height ? height / bmHeight : width / bmWidth;
}
mMatrix.setScale(scale, scale);
mScale = 1f;
mOriginalBitmapWidth = scale * bmWidth;
mOriginalBitmapHeight = scale * bmHeight;
// Center the image
float redundantYSpace = (height - mOriginalBitmapHeight);
float redundantXSpace = (width - mOriginalBitmapWidth);
mMatrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2);
setImageMatrix(mMatrix);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mMatrix.getValues(mCriticPoints);
float translateX = mCriticPoints[Matrix.MTRANS_X];
float trnslateY = mCriticPoints[Matrix.MTRANS_Y];
PointF currentPoint = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
//when one finger is touching
//set the mode to DRAG
case MotionEvent.ACTION_DOWN:
mLastTouch.set(event.getX(), event.getY());
mStartTouch.set(mLastTouch);
mode = DRAG;
break;
//when two fingers are touching
//set the mode to ZOOM
case MotionEvent.ACTION_POINTER_DOWN:
mLastTouch.set(event.getX(), event.getY());
mStartTouch.set(mLastTouch);
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 && mScale > minScale)) {
// region . Move image.
float deltaX = currentPoint.x - mLastTouch.x;// x difference
float deltaY = currentPoint.y - mLastTouch.y;// y difference
float scaleWidth = Math.round(mOriginalBitmapWidth * mScale);// width after applying current scale
float scaleHeight = Math.round(mOriginalBitmapHeight * mScale);// height after applying current scale
// Move image to lef or right if its width is bigger than display width
if (scaleWidth > getWidth()) {
if (translateX + deltaX > 0) {
deltaX = -translateX;
} else if (translateX + deltaX < -mRight) {
deltaX = -(translateX + mRight);
}
} else {
deltaX = 0;
}
// Move image to up or bottom if its height is bigger than display height
if (scaleHeight > getHeight()) {
if (trnslateY + deltaY > 0) {
deltaY = -trnslateY;
} else if (trnslateY + deltaY < -mBottom) {
deltaY = -(trnslateY + mBottom);
}
} else {
deltaY = 0;
}
//move the image with the matrix
mMatrix.postTranslate(deltaX, deltaY);
//set the last touch location to the current
mLastTouch.set(currentPoint.x, currentPoint.y);
// endregion . Move image .
}
break;
//first finger is lifted
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(currentPoint.x - mStartTouch.x);
int yDiff = (int) Math.abs(currentPoint.y - mStartTouch.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
// second finger is lifted
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(mMatrix);
invalidate();
return true;
}
//endregion . Overrides .
// region . Privates .
private void init(Context context) {
super.setClickable(true);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mCriticPoints = new float[9];
setImageMatrix(mMatrix);
setScaleType(ScaleType.MATRIX);
}
private int getBmWidth() {
Drawable drawable = getDrawable();
if (drawable != null) {
return drawable.getIntrinsicWidth();
}
return 0;
}
private int getBmHeight() {
Drawable drawable = getDrawable();
if (drawable != null) {
return drawable.getIntrinsicHeight();
}
return 0;
}
//endregion . Privates .
// region . Internal classes .
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
float newScale = mScale * scaleFactor;
if (newScale < maxScale && newScale > minScale) {
mScale = newScale;
float width = getWidth();
float height = getHeight();
mRight = (mOriginalBitmapWidth * mScale) - width;
mBottom = (mOriginalBitmapHeight * mScale) - height;
float scaledBitmapWidth = mOriginalBitmapWidth * mScale;
float scaledBitmapHeight = mOriginalBitmapHeight * mScale;
if (scaledBitmapWidth <= width || scaledBitmapHeight <= height) {
mMatrix.postScale(scaleFactor, scaleFactor, width / 2, height / 2);
} else {
mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
}
}
return true;
}
}
// endregion . Internal classes .
}
Method to call the About&support dialog
public void setupAboutSupport() {
try {
// The About&Support AlertDialog is active
activeAboutSupport=true;
View messageView;
int orientation=this.getResources().getConfiguration().orientation;
// Inflate the about message contents
messageView = getLayoutInflater().inflate(R.layout.about_support, null, false);
ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.MyCustomTheme_AlertDialog1);
AlertDialog.Builder builder = new AlertDialog.Builder(ctw);
builder.setIcon(R.mipmap.ic_launcher);
builder.setTitle(R.string.action_aboutSupport);
builder.setView(messageView);
TouchImageView imgDisplay = (TouchImageView) messageView.findViewById(R.id.action_infolinks_about_support);
imgDisplay.setMaxZoom(3f);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.myinfolinks_about_support);
int imageWidth = bitmap.getWidth();
int imageHeight = bitmap.getHeight();
int newWidth;
// Calculate the new About_Support image width
if(orientation==Configuration.ORIENTATION_PORTRAIT ) {
// For 7" up to 10" tablets
//if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
if (SingletonMyInfoLinks.isTablet) {
// newWidth = widthScreen - (two borders of about_support layout and 20% of width Screen)
newWidth = widthScreen - ((2 * toPixels(8)) + (int)(widthScreen*0.2));
} else newWidth = widthScreen - ((2 * toPixels(8)) + (int)(widthScreen*0.1));
} else {
// For 7" up to 10" tablets
//if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
if (SingletonMyInfoLinks.isTablet) {
newWidth = widthScreen - ((2 * toPixels(8)) + (int)(widthScreen*0.5));
} else newWidth = widthScreen - ((2 * toPixels(8)) + (int)(widthScreen*0.3));
}
// Get the scale factor
float scaleFactor = (float)newWidth/(float)imageWidth;
// Calculate the new About_Support image height
int newHeight = (int)(imageHeight * scaleFactor);
// Set the new bitmap corresponding the adjusted About_Support image
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
// Rescale the image
imgDisplay.setImageBitmap(bitmap);
dialogAboutSupport = builder.show();
TextView textViewVersion = (TextView) dialogAboutSupport.findViewById(R.id.action_strVersion);
textViewVersion.setText(Html.fromHtml(getString(R.string.aboutSupport_text1)+" <b>"+versionName+"</b>"));
TextView textViewDeveloperName = (TextView) dialogAboutSupport.findViewById(R.id.action_strDeveloperName);
textViewDeveloperName.setText(Html.fromHtml(getString(R.string.aboutSupport_text2)+" <b>"+SingletonMyInfoLinks.developerName+"</b>"));
TextView textViewSupportEmail = (TextView) dialogAboutSupport.findViewById(R.id.action_strSupportEmail);
textViewSupportEmail.setText(Html.fromHtml(getString(R.string.aboutSupport_text3)+" "+SingletonMyInfoLinks.developerEmail));
TextView textViewCompanyName = (TextView) dialogAboutSupport.findViewById(R.id.action_strCompanyName);
textViewCompanyName.setText(Html.fromHtml(getString(R.string.aboutSupport_text4)+" "+SingletonMyInfoLinks.companyName));
Button btnOk = (Button) dialogAboutSupport.findViewById(R.id.btnOK);
btnOk.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialogAboutSupport.dismiss();
}
});
dialogAboutSupport.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(final DialogInterface dialog) {
// the About & Support AlertDialog is closed
activeAboutSupport=false;
}
});
dialogAboutSupport.getWindow().setBackgroundDrawable(new ColorDrawable(SingletonMyInfoLinks.atualBackgroundColor));
/* Effect that image appear slower */
// Only the fade_in matters
AlphaAnimation fade_out = new AlphaAnimation(1.0f, 0.0f);
AlphaAnimation fade_in = new AlphaAnimation(0.0f, 1.0f);
AlphaAnimation a = false ? fade_out : fade_in;
a.setDuration(2000); // 2 sec
a.setFillAfter(true); // Maintain the visibility at the end of animation
// Animation start
ImageView img = (ImageView) messageView.findViewById(R.id.action_infolinks_about_support);
img.startAnimation(a);
} catch (Exception e) {
//Log.e(SingletonMyInfoLinks.appNameText +"-" + getLocalClassName() + ": ", e.getMessage());
}
}
Just change ACTION_MOVE_EVENT in Chirag Raval Answer to set ZOOM_IN LIMIT
float[] values = new float[9]; matrix.getValues(values);
//0.37047964 is limit for zoom in
if(values[Matrix.MSCALE_X]>0.37047964) {
matrix.set(savedMatrix);
matrix.postScale(scale, scale, mid.x, mid.y);
view.setImageMatrix(matrix);
}else if (scale>1){
matrix.set(savedMatrix);
matrix.postScale(scale, scale, mid.x, mid.y);
view.setImageMatrix(matrix);
}
I know it is a bit late for this answer, but I hope it helps someone.
I was looking for the same thing (Zoom using pinch, and dragging image) and I found this Android Developers Blog link.
Works perfect. No artifacts or what so ever. It uses ScaleGestureDetector.
ZoomLib Link: https://drive.google.com/uc?export=download&id=0B34PUThnUsjVaHpkaGk0Z1hSRU0
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dummy_layout_for_zooming);// Activity layout
mZoomLinearLayout = (LinearLayout) findViewById(R.id.mZoomLinearLayout);// LinearLayout inside Activity layout
View v = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.layout_for_zoom, null, false);// View wants to zoom
v.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
ZoomView zoomView = new ZoomView(this);// intialize lib
zoomView.addView(v);
mZoomLinearLayout.addView(zoomView);
}
I think why you want to zoom-in and zoom-out the image view is because your image view is small and zooming in the image there will not let the image come out of the image view. Instead, image starts disappearing from the boundaries while zooming in. So letting the image come out of the image while we zoom in is what you want I guess.
So there are two tricks (according to me) to achieve this-
Change the layout params of the image view to cover the whole outermost view or view group at runtime while we are about to zoom-in the image.
Make an expanded Image view or Full Image View (height and width = Outermost View or View Group) in xml file and set it's visibility to gone. When we are about to zoom-in the image, load the image from small Image View into full Img View, map the image in fullImgView to image in smallImgView and make it's view visible.
Trick no.1 does not work when the image View is nested very much inside other views (like in nested recycler view)
Trick no.2 works always :-)
Here is the implementation of trick no.2 -
public class ZoomOnTouchListener extends AppCompatActivity implements View.OnTouchListener {
private Matrix matrix = new Matrix();
private boolean isfullImgViewActive = false;
// above boolean gets true when when we firstly move 2 fingers on the smallImgView and in this case smallImgView gets invisible and fullImgView gets visible
// and false if smallImgView is visible and fullImgView is gone
private float[] matrixArray = new float[9];
private float orgScale;
private PointF start = new PointF();
private PointF prevP1 = new PointF();
private PointF prevP2 = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
private ImageView mfullImgView;
private ImageView smallImgView;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mfullImgView = (ImageView)findViewById(R.id.imgView2);
mfullImgView.setVisibility(View.GONE);
smallImgView = (ImageView)findViewById(R.id.imgView);
smallImgView.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
//Log.i("0", "OnTouch()");
if(v instanceof ImageView) {
ImageView imgView = (ImageView) v;
boolean isImgViewSmall = (imgView == smallImgView);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // first finger down only
//Log.i("T", "Motion Event: ACTION_DOWN");
if(isImgViewSmall)
start.set(event.getX(), event.getY());
prevP1.set(event.getX(), event.getY());
return true;
case MotionEvent.ACTION_POINTER_DOWN: //second finger and other fingers down
prevP1.set(event.getX(0), event.getY(0));
prevP2.set(event.getX(1), event.getY(1));
oldDist = spacing(event);
midPoint(mid, event);
break;
case MotionEvent.ACTION_MOVE: //it doesn't mean fingers are moved. In this case, all pointers which are active are batched together
// this case occurs after action_down or action_pointer_down
//Log.i("Tag", event.getX()+","+event.getY());
if(event.getPointerCount() == 2) {
PointF newMid = new PointF();
midPoint(newMid,event);
float newDist = spacing(event);
float scale = newDist/oldDist;
if( !isfullImgViewActive){ // is smallImgView is visible and mfullImgView is gone
Log.i("tag", "true");
isfullImgViewActive = true;
matrix.set(imgView.getImageMatrix()); //note:- do not write matrix = imgView.getImageMatrix() because it gives the smallImgView's matrix reference to the matrix variable and changes on matrix will reflect on smallImgView
matrix.getValues(matrixArray);
orgScale = matrixArray[0];
smallImgView.setVisibility(View.INVISIBLE);
mfullImgView.setImageDrawable(smallImgView.getDrawable());
mfullImgView.setScaleType(ImageView.ScaleType.MATRIX);
mfullImgView.setVisibility(View.VISIBLE);
//To map the image of mFullImgView to that of smallImgView we have to
// translate the mFullImgView's image
matrix.postTranslate(tx, ty);
/////////////NOTE///////////////
//here (tx,ty) are coordinates of top-left corner of smallimgView and
// they MUST be relative to the origin of Outermost view or view group
// where fullImgView is placed. So find tx,ty in your case by yourself
mfullImgView.setImageMatrix(matrix);
}
if(isImgViewSmall) {
matrix.postScale(scale, scale, mid.x + tx, mid.y + ty);
}
else{
matrix.postScale(scale, scale, mid.x, mid.y);
}
oldDist = newDist;
matrix.postTranslate(newMid.x - mid.x, newMid.y - mid.y);
matrix.getValues(matrixArray);
mid.set(newMid);
prevP1.set(event.getX(0), event.getY(0));
prevP2.set(event.getX(1), event.getY(1));
}
else if(event.getPointerCount() == 1 ){
if(isfullImgViewActive) {
matrix.postTranslate(event.getX() - prevP1.x, event.getY() - prevP1.y);
matrix.getValues(matrixArray);
}
prevP1.set(event.getX(0), event.getY(0));
}
break;
case MotionEvent.ACTION_POINTER_UP: // second finger lifted
//Now if pointer of index 0 is lifted then pointer of index 1 will get index 0;
if(event.getActionIndex() == 0 && isfullImgViewActive){
Log.i("TAg", event.getActionIndex()+"");
prevP1.set(prevP2);
}
break;
case MotionEvent.ACTION_UP: // first finger lifted or all fingers are lifted
if(isImgViewSmall && !isfullImgViewActive) {
imgView.setScaleType(ImageView.ScaleType.FIT_CENTER);
int xDiff = (int) Math.abs(event.getX() - start.x);
int yDiff = (int) Math.abs(event.getY() - start.y);
if (xDiff == 0 && yDiff == 0) {
imgView.performClick();
return true;
}
}
if(isfullImgViewActive){
if(matrixArray[0] <= orgScale){ //matrixArray[0] is Scale.X value
mfullImgView.setOnTouchListener(null);
mfullImgView.setImageDrawable(null);
mfullImgView.setVisibility(View.GONE);
smallImgView.setOnTouchListener(this);
smallImgView.setScaleType(ImageView.ScaleType.FIT_CENTER);
smallImgView.setVisibility(View.VISIBLE);
isfullImgViewActive = false;
}
else if(matrixArray[0] > orgScale && isImgViewSmall){ //if the imgView was smallImgView
smallImgView.setOnTouchListener(null);
smallImgView.setScaleType(ImageView.ScaleType.FIT_CENTER);
smallImgView.setVisibility(View.GONE); // or View.INVISIBLE
mfullImgView.setOnTouchListener(this);
}
}
return true;
}
//end of Switch statement
if(isfullImgViewActive) { //active means visible
mfullImgView.setImageMatrix(matrix); // display the transformation on screen
}
return true; // indicate event was handled
}
else
return false;
}
private float spacing(MotionEvent event)
{
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.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);
}
}
NOTE:- Don't set OnClickListener to mfullImgView before setting onTouchListener to it. Setting that will let the fullImageView(if it is visible) steal the touch event of putting second finger down after lifting it for the first time because we want to let the smallImgView take all the touch events until all the fingers are lifted up for the first time.
I am Using this one it is working perfectly.
<your.packagename.MyZoomableImageViewTouch
android:id="#+id/mediaImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="matrix"/>
My a MyZoomableImageViewTouch class is below:
public class MyZoomableImageViewTouch extends ImageViewTouch
{
static final float SCROLL_DELTA_THRESHOLD = 1.0f;
public MyZoomableImageViewTouch(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init();
}
public MyZoomableImageViewTouch(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyZoomableImageViewTouch(Context context)
{
super(context);
init();
}
private void init() {
View.OnTouchListener listener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getScale() > 1f) {
getParent().requestDisallowInterceptTouchEvent(true);
} else {
getParent().requestDisallowInterceptTouchEvent(false);
}
return false;
}
};
setOnTouchListener(listener);
setDisplayType(DisplayType.FIT_TO_SCREEN);
}
#Override
protected float onDoubleTapPost(float scale, float maxZoom) {
if (scale != 1f) {
mDoubleTapDirection = 1;
return 1f;
}
if (mDoubleTapDirection == 1) {
mDoubleTapDirection = -1;
if ((scale + (mScaleFactor * 2)) <= maxZoom) {
return scale + mScaleFactor;
} else {
mDoubleTapDirection = -1;
return maxZoom;
}
} else {
mDoubleTapDirection = 1;
return 1f;
}
}
#Override
public boolean canScroll(int direction) {
RectF bitmapRect = getBitmapRect();
updateRect(bitmapRect, mScrollRect);
Rect imageViewRect = new Rect();
getGlobalVisibleRect(imageViewRect);
if (null == bitmapRect) {
return false;
}
if (Math.abs(bitmapRect.right - imageViewRect.right) < SCROLL_DELTA_THRESHOLD) {
if (direction < 0) {
return false;
}
}
if (Math.abs(bitmapRect.left - mScrollRect.left) < SCROLL_DELTA_THRESHOLD) {
if (direction > 0) {
return false;
}
}
return true;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
if (getScale() == 1f) return false;
if (distanceX != 0 && !canScroll((int) -distanceX)) {
getParent().requestDisallowInterceptTouchEvent(false);
return false;
} else {
getParent().requestDisallowInterceptTouchEvent(true);
mUserScaled = true;
scrollBy(-distanceX, -distanceY);
invalidate();
return true;
}
}
}

Categories

Resources