package it.sephiroth.android.library.imagezoom;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
import it.sephiroth.android.library.easing.Cubic;
import it.sephiroth.android.library.easing.Easing;
import it.sephiroth.android.library.imagezoom.graphics.FastBitmapDrawable;
import it.sephiroth.android.library.imagezoom.utils.IDisposable;
/**
* Base View to manage image zoom/scroll/pinch operations
*
* @author alessandro
*/
public abstract class ImageViewTouchBase extends ImageView implements IDisposable {
public static final String LOG_TAG = "ImageViewTouchBase";
public static final float ZOOM_INVALID = -1f;
protected static final boolean LOG_ENABLED = false;
protected final Matrix mDisplayMatrix = new Matrix();
protected final float[] mMatrixValues = new float[9];
protected final int DEFAULT_ANIMATION_DURATION = 300;
protected Easing mEasing = new Cubic();
protected Matrix mBaseMatrix = new Matrix();
protected Matrix mSuppMatrix = new Matrix();
protected Matrix mNextMatrix;
protected Handler mHandler = new Handler();
protected Runnable mLayoutRunnable = null;
protected boolean mUserScaled = false;
protected DisplayType mScaleType = DisplayType.FIT_TO_WIDTH;
protected InitialPosition mInitialPosition = InitialPosition.LEFT_UP;
protected RectF mBitmapRect = new RectF();
protected RectF mCenterRect = new RectF();
protected RectF mScrollRect = new RectF();
private float mMaxZoom = ZOOM_INVALID;
private float mMinZoom = ZOOM_INVALID;
// true when min and max zoom are explicitly defined
private boolean mMaxZoomDefined;
private boolean mMinZoomDefined;
private int mThisWidth = -1;
private int mThisHeight = -1;
private PointF mCenter = new PointF();
private boolean mScaleTypeChanged;
private boolean mBitmapChanged;
private OnDrawableChangeListener mDrawableChangeListener;
private OnLayoutChangeListener mOnLayoutChangeListener;
private float scrollFactor = 1;
public ImageViewTouchBase(Context context) {
this(context, null);
}
public ImageViewTouchBase(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
public boolean getBitmapChanged() {
return mBitmapChanged;
}
public void setOnDrawableChangedListener(OnDrawableChangeListener listener) {
mDrawableChangeListener = listener;
}
public void setOnLayoutChangeListener(OnLayoutChangeListener listener) {
mOnLayoutChangeListener = listener;
}
protected void init(Context context, AttributeSet attrs, int defStyle) {
setScaleType(ImageView.ScaleType.MATRIX);
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType == ScaleType.MATRIX) {
super.setScaleType(scaleType);
} else {
Log.w(LOG_TAG, "Unsupported scale type. Only MATRIX can be used");
}
}
/**
* Clear the current drawable
*/
public void clear() {
setImageBitmap(null);
}
public DisplayType getDisplayType() {
return mScaleType;
}
/**
* Change the display type
*/
public void setDisplayType(DisplayType type) {
if (type != mScaleType) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "setDisplayType: " + type);
}
mUserScaled = false;
mScaleType = type;
mScaleTypeChanged = true;
requestLayout();
}
}
public InitialPosition getmInitialPosition() {
return mInitialPosition;
}
public void setInitialPosition(InitialPosition mInitialPosition) {
this.mInitialPosition = mInitialPosition;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (LOG_ENABLED) {
Log.e(LOG_TAG, "onLayout: " + changed + ", bitmapChanged: " +
mBitmapChanged + ", scaleChanged: " + mScaleTypeChanged);
}
super.onLayout(changed, left, top, right, bottom);
int deltaX = 0;
int deltaY = 0;
if (changed) {
int oldw = mThisWidth;
int oldh = mThisHeight;
mThisWidth = right - left;
mThisHeight = bottom - top;
deltaX = mThisWidth - oldw;
deltaY = mThisHeight - oldh;
// update center point
mCenter.x = mThisWidth / 2f;
mCenter.y = mThisHeight / 2f;
}
Runnable r = mLayoutRunnable;
if (r != null) {
mLayoutRunnable = null;
r.run();
}
final Drawable drawable = getDrawable();
if (drawable != null) {
if (changed || mScaleTypeChanged || mBitmapChanged) {
if (mBitmapChanged) {
mBaseMatrix.reset();
if (!mMinZoomDefined)
mMinZoom = ZOOM_INVALID;
if (!mMaxZoomDefined)
mMaxZoom = ZOOM_INVALID;
}
float scale = 1;
// retrieve the old values
float old_default_scale = getDefaultScale(mScaleType);
float old_matrix_scale = getScale(mBaseMatrix);
float old_scale = getScale();
float old_min_scale = Math.min(1f, 1f / old_matrix_scale);
getProperBaseMatrix(drawable, mBaseMatrix);
float new_matrix_scale = getScale(mBaseMatrix);
if (LOG_ENABLED) {
Log.d(LOG_TAG, "old matrix scale: " + old_matrix_scale);
Log.d(LOG_TAG, "new matrix scale: " + new_matrix_scale);
Log.d(LOG_TAG, "old min scale: " + old_min_scale);
Log.d(LOG_TAG, "old scale: " + old_scale);
}
// 1. bitmap changed or scale type changed
if (mBitmapChanged || mScaleTypeChanged) {
if (LOG_ENABLED) {
Log.d(LOG_TAG, "display type: " + mScaleType);
Log.d(LOG_TAG, "newMatrix: " + mNextMatrix);
}
if (mNextMatrix != null) {
mSuppMatrix.set(mNextMatrix);
mNextMatrix = null;
scale = getScale();
} else {
mSuppMatrix.reset();
scale = getDefaultScale(mScaleType);
}
setImageMatrix(getImageViewMatrix());
if (scale != getScale()) {
if (LOG_ENABLED) {
Log.v(LOG_TAG, "scale != getScale: " + scale + " != " + getScale());
}
zoomTo(scale);
}
} else if (changed) {
// 2. layout size changed
if (!mMinZoomDefined)
mMinZoom = ZOOM_INVALID;
if (!mMaxZoomDefined)
mMaxZoom = ZOOM_INVALID;
setImageMatrix(getImageViewMatrix());
postTranslate(-deltaX, -deltaY);
if (!mUserScaled) {
scale = getDefaultScale(mScaleType);
if (LOG_ENABLED) {
Log.v(LOG_TAG, "!userScaled. scale=" + scale);
}
zoomTo(scale);
} else {
if (Math.abs(old_scale - old_min_scale) > 0.001) {
scale = (old_matrix_scale / new_matrix_scale) * old_scale;
}
if (LOG_ENABLED) {
Log.v(LOG_TAG, "userScaled. scale=" + scale);
}
zoomTo(scale);
}
if (LOG_ENABLED) {
Log.d(LOG_TAG, "old min scale: " + old_default_scale);
Log.d(LOG_TAG, "old scale: " + old_scale);
Log.d(LOG_TAG, "new scale: " + scale);
}
}
mUserScaled = false;
if (scale > getMaxScale() || scale < getMinScale()) {
// if current scale if outside the min/max bounds
// then restore the correct scale
zoomTo(scale);
}
center(true, true);
// Set initial position
RectF bitmapRect = getBitmapRect();
Rect imageViewRect = new Rect();
getGlobalVisibleRect(imageViewRect);
switch (mInitialPosition) {
case CENTER_UP:
panBy(0, -bitmapRect.top);
break;
case RIGHT_UP:
panBy(imageViewRect.right - bitmapRect.right, -bitmapRect.top);
break;
case LEFT_UP:
panBy(-bitmapRect.right, -bitmapRect.top);
break;
case RIGHT_CENTER:
panBy(imageViewRect.right - bitmapRect.right, 0);
break;
case LEFT_CENTER:
panBy(-bitmapRect.right, 0);
break;
case LEFT_BOTTOM:
panBy(-bitmapRect.right, imageViewRect.bottom - bitmapRect.bottom);
break;
default:
break;
}
if (mBitmapChanged)
onDrawableChanged(drawable);
if (changed || mBitmapChanged || mScaleTypeChanged)
onLayoutChanged(left, top, right, bottom);
if (mScaleTypeChanged)
mScaleTypeChanged = false;
if (mBitmapChanged)
mBitmapChanged = false;
if (LOG_ENABLED) {
Log.d(LOG_TAG, "new scale: " + getScale());
}
}
} else {
// drawable is null
if (mBitmapChanged)
onDrawableChanged(null);
if (changed || mBitmapChanged || mScaleTypeChanged)
onLayoutChanged(left, top, right, bottom);
if (mBitmapChanged)
mBitmapChanged = false;
if (mScaleTypeChanged)
mScaleTypeChanged = false;
}
}
/**
* Restore the original display
*/
public void resetDisplay() {
mBitmapChanged = true;
requestLayout();
}
public void resetMatrix() {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "resetMatrix");
}
mSuppMatrix = new Matrix();
float scale = getDefaultScale(mScaleType);
setImageMatrix(getImageViewMatrix());
if (LOG_ENABLED) {
Log.d(LOG_TAG, "default scale: " + scale + ", scale: " + getScale());
}
if (scale != getScale()) {
zoomTo(scale);
}
postInvalidate();
}
protected float getDefaultScale(DisplayType type) {
if (type == DisplayType.FIT_TO_SCREEN) {
// always fit to screen
return 1f;
} /*
* else if (type == DisplayType.FIT_IF_BIGGER) { // normal scale if
* smaller, fit to screen otherwise return Math.min(1f, 1f /
* getScale(mBaseMatrix)); }/
*/ else if (type == DisplayType.FIT_TO_WIDTH) {
if (getWidth() != 0 && getHeight() != 0 && getDrawable() != null) {
float mImageOriginalWidth = getDrawable().getIntrinsicWidth();
float mImageOriginalHeight = getDrawable().getMinimumHeight();
if ((mImageOriginalWidth - getWidth()) * mImageOriginalWidth / getWidth() >
(mImageOriginalHeight - getHeight()) * mImageOriginalHeight / getHeight()) {
return 1f;
} else {
return 1f / ((mImageOriginalWidth * (((float) getHeight()) / mImageOriginalHeight)) / getWidth());
}
} else {
return 1f;
}
} else if (type == DisplayType.FIT_TO_HEIGHT) {
if (getWidth() != 0 && getHeight() != 0 && getDrawable() != null) {
float mImageOriginalWidth = getDrawable().getIntrinsicWidth();
float mImageOriginalHeight = getDrawable().getMinimumHeight();
if ((mImageOriginalWidth - getWidth()) * mImageOriginalWidth / getWidth() <
(mImageOriginalHeight - getHeight()) * mImageOriginalHeight / getHeight()) {
return 1f;
} else {
return 1f / ((mImageOriginalHeight * (((float) getWidth()) / mImageOriginalWidth)) / getHeight());
}
} else {
return 1f;
}
} else {
// no scale
return 1f / getScale(mBaseMatrix);
}
}
@Override
public void setImageResource(int resId) {
setImageDrawable(getContext().getResources().getDrawable(resId));
}
/**
* {@inheritDoc} Set the new image to display and reset the internal matrix.
*
* @param bitmap the {@link Bitmap} to display
* @see {@link ImageView#setImageBitmap(Bitmap)}
*/
@Override
public void setImageBitmap(final Bitmap bitmap) {
setImageBitmap(bitmap, null, ZOOM_INVALID, ZOOM_INVALID);
}
/**
* @see #setImageDrawable(Drawable, Matrix, float, float)
*/
public void setImageBitmap(final Bitmap bitmap, Matrix matrix, float min_zoom, float max_zoom) {
if (bitmap != null) {
setImageDrawable(new FastBitmapDrawable(bitmap), matrix, min_zoom, max_zoom);
} else
setImageDrawable(null, matrix, min_zoom, max_zoom);
}
@Override
public void setImageDrawable(Drawable drawable) {
setImageDrawable(drawable, null, ZOOM_INVALID, ZOOM_INVALID);
}
/**
* Note: if the scaleType is FitToScreen then min_zoom must be <= 1 and
* max_zoom must be >= 1
*
* @param drawable the new drawable
* @param initial_matrix the optional initial display matrix
* @param min_zoom the optional minimum scale, pass {@link #ZOOM_INVALID} to use
* the default min_zoom
* @param max_zoom the optional maximum scale, pass {@link #ZOOM_INVALID} to use
* the default max_zoom
*/
public void setImageDrawable(final Drawable drawable, final Matrix initial_matrix,
final float min_zoom, final float max_zoom) {
final int viewWidth = getWidth();
if (viewWidth <= 0) {
mLayoutRunnable = new Runnable() {
@Override
public void run() {
setImageDrawable(drawable, initial_matrix, min_zoom, max_zoom);
}
};
return;
}
_setImageDrawable(drawable, initial_matrix, min_zoom, max_zoom);
}
protected void _setImageDrawable(final Drawable drawable, final Matrix initial_matrix,
float min_zoom, float max_zoom) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "_setImageDrawable");
}
mBaseMatrix.reset();
if (drawable != null) {
if (LOG_ENABLED) {
Log.d(LOG_TAG, "size: " + drawable.getIntrinsicWidth() + "x" + drawable.getIntrinsicHeight());
}
super.setImageDrawable(drawable);
} else {
super.setImageDrawable(null);
}
if (min_zoom != ZOOM_INVALID && max_zoom != ZOOM_INVALID) {
min_zoom = Math.min(min_zoom, max_zoom);
max_zoom = Math.max(min_zoom, max_zoom);
mMinZoom = min_zoom;
mMaxZoom = max_zoom;
mMinZoomDefined = true;
mMaxZoomDefined = true;
if (mScaleType == DisplayType.FIT_TO_SCREEN || /*
* mScaleType ==
* DisplayType
* .FIT_IF_BIGGER
* ||/
*/mScaleType == DisplayType.FIT_TO_WIDTH || mScaleType == DisplayType.FIT_TO_HEIGHT) {
if (mMinZoom >= 1) {
mMinZoomDefined = false;
mMinZoom = ZOOM_INVALID;
}
if (mMaxZoom <= 1) {
mMaxZoomDefined = true;
mMaxZoom = ZOOM_INVALID;
}
}
} else {
mMinZoom = ZOOM_INVALID;
mMaxZoom = ZOOM_INVALID;
mMinZoomDefined = false;
mMaxZoomDefined = false;
}
if (initial_matrix != null) {
mNextMatrix = new Matrix(initial_matrix);
}
if (LOG_ENABLED) {
Log.v(LOG_TAG, "mMinZoom: " + mMinZoom + ", mMaxZoom: " + mMaxZoom);
}
mBitmapChanged = true;
requestLayout();
}
/**
* Fired as soon as a new Bitmap has been set
*/
protected void onDrawableChanged(final Drawable drawable) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "onDrawableChanged");
Log.v(LOG_TAG, "scale: " + getScale() + ", minScale: " + getMinScale());
}
fireOnDrawableChangeListener(drawable);
}
protected void fireOnLayoutChangeListener(int left, int top, int right, int bottom) {
if (null != mOnLayoutChangeListener) {
mOnLayoutChangeListener.onLayoutChanged(true, left, top, right, bottom);
}
}
protected void fireOnDrawableChangeListener(Drawable drawable) {
if (null != mDrawableChangeListener) {
mDrawableChangeListener.onDrawableChanged(drawable);
}
}
/**
* Called just after {@link #onLayout(boolean, int, int, int, int)} if the
* view's bounds has changed or a new Drawable has been set or the
* {@link DisplayType} has been modified
*/
protected void onLayoutChanged(int left, int top, int right, int bottom) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "onLayoutChanged");
}
fireOnLayoutChangeListener(left, top, right, bottom);
}
protected float computeMaxZoom() {
final Drawable drawable = getDrawable();
if (drawable == null) {
return 1F;
}
float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth;
float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight;
float scale = Math.max(fw, fh) * 8;
if (LOG_ENABLED) {
Log.i(LOG_TAG, "computeMaxZoom: " + scale);
}
return scale;
}
protected float computeMinZoom() {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "computeMinZoom");
}
final Drawable drawable = getDrawable();
if (drawable == null) {
return 1F;
}
float scale = getScale(mBaseMatrix);
scale = Math.min(1f, 1f / scale);
if (LOG_ENABLED) {
Log.i(LOG_TAG, "computeMinZoom: " + scale);
}
return scale;
}
/**
* Returns the current maximum allowed image scale
*/
public float getMaxScale() {
if (mMaxZoom == ZOOM_INVALID) {
mMaxZoom = computeMaxZoom();
}
return mMaxZoom;
}
protected void setMaxScale(float value) {
if (LOG_ENABLED) {
Log.d(LOG_TAG, "setMaxZoom: " + value);
}
mMaxZoom = value;
}
/**
* Returns the current minimum allowed image scale
*/
public float getMinScale() {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "getMinScale, mMinZoom: " + mMinZoom);
}
if (mMinZoom == ZOOM_INVALID) {
mMinZoom = computeMinZoom();
}
if (LOG_ENABLED) {
Log.v(LOG_TAG, "mMinZoom: " + mMinZoom);
}
return mMinZoom;
}
protected void setMinScale(float value) {
if (LOG_ENABLED) {
Log.d(LOG_TAG, "setMinZoom: " + value);
}
mMinZoom = value;
}
/**
* Returns the current view matrix
*/
public Matrix getImageViewMatrix() {
return getImageViewMatrix(mSuppMatrix);
}
public Matrix getImageViewMatrix(Matrix supportMatrix) {
mDisplayMatrix.set(mBaseMatrix);
mDisplayMatrix.postConcat(supportMatrix);
return mDisplayMatrix;
}
@Override
public void setImageMatrix(Matrix matrix) {
Matrix current = getImageMatrix();
boolean needUpdate = false;
if (matrix == null && !current.isIdentity() || matrix != null && !current.equals(matrix)) {
needUpdate = true;
}
super.setImageMatrix(matrix);
if (needUpdate)
onImageMatrixChanged();
}
/**
* Called just after a new Matrix has been assigned.
*
* @see {@link #setImageMatrix(Matrix)}
*/
protected void onImageMatrixChanged() {
}
/**
* Returns the current image display matrix.<br />
* This matrix can be used in the next call to the
* {@link #setImageDrawable(Drawable, Matrix, float, float)} to restore the
* same view state of the previous {@link Bitmap}.<br />
* Example:
* <p/>
* <p/>
* <pre>
* Matrix currentMatrix = mImageView.getDisplayMatrix();
* mImageView.setImageBitmap(newBitmap, currentMatrix, ZOOM_INVALID, ZOOM_INVALID);
* </pre>
*
* @return the current support matrix
*/
public Matrix getDisplayMatrix() {
return new Matrix(mSuppMatrix);
}
/**
* Setup the base matrix so that the image is centered and scaled properly.
*/
protected void getProperBaseMatrix(Drawable drawable, Matrix matrix) {
float viewWidth = mThisWidth;
float viewHeight = mThisHeight;
if (LOG_ENABLED) {
Log.d(LOG_TAG, "getProperBaseMatrix. view: " + viewWidth + "x" + viewHeight);
}
float w = drawable.getIntrinsicWidth();
float h = drawable.getIntrinsicHeight();
float widthScale, heightScale;
matrix.reset();
if (w > viewWidth || h > viewHeight) {
widthScale = viewWidth / w;
heightScale = viewHeight / h;
float scale = Math.min(widthScale, heightScale);
matrix.postScale(scale, scale);
float tw = (viewWidth - w * scale) / 2.0f;
float th = (viewHeight - h * scale) / 2.0f;
matrix.postTranslate(tw, th);
} else {
widthScale = viewWidth / w;
heightScale = viewHeight / h;
float scale = Math.min(widthScale, heightScale);
matrix.postScale(scale, scale);
float tw = (viewWidth - w * scale) / 2.0f;
float th = (viewHeight - h * scale) / 2.0f;
matrix.postTranslate(tw, th);
}
if (LOG_ENABLED) {
printMatrix(matrix);
}
}
/**
* Setup the base matrix so that the image is centered and scaled properly.
*/
protected void getProperBaseMatrix2(Drawable bitmap, Matrix matrix) {
float viewWidth = mThisWidth;
float viewHeight = mThisHeight;
float w = bitmap.getIntrinsicWidth();
float h = bitmap.getIntrinsicHeight();
matrix.reset();
float widthScale = viewWidth / w;
float heightScale = viewHeight / h;
float scale = Math.min(widthScale, heightScale);
matrix.postScale(scale, scale);
matrix.postTranslate((viewWidth - w * scale) / 2.0f, (viewHeight - h * scale) / 2.0f);
}
protected float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
public void printMatrix(Matrix matrix) {
float scalex = getValue(matrix, Matrix.MSCALE_X);
float scaley = getValue(matrix, Matrix.MSCALE_Y);
float tx = getValue(matrix, Matrix.MTRANS_X);
float ty = getValue(matrix, Matrix.MTRANS_Y);
Log.d(LOG_TAG, "matrix: { x: " + tx + ", y: " + ty + ", scalex: " + scalex + ", scaley: " + scaley + " }");
}
public RectF getBitmapRect() {
return getBitmapRect(mSuppMatrix);
}
protected RectF getBitmapRect(Matrix supportMatrix) {
final Drawable drawable = getDrawable();
if (drawable == null)
return null;
Matrix m = getImageViewMatrix(supportMatrix);
mBitmapRect.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
m.mapRect(mBitmapRect);
return mBitmapRect;
}
protected float getScale(Matrix matrix) {
return getValue(matrix, Matrix.MSCALE_X);
}
@SuppressLint("Override")
public float getRotation() {
return 0;
}
/**
* Returns the current image scale
*/
public float getScale() {
return getScale(mSuppMatrix);
}
public float getBaseScale() {
return getScale(mBaseMatrix);
}
protected void center(boolean horizontal, boolean vertical) {
final Drawable drawable = getDrawable();
if (drawable == null)
return;
RectF rect = getCenter(mSuppMatrix, horizontal, vertical);
if (rect.left != 0 || rect.top != 0) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "center");
}
postTranslate(rect.left, rect.top);
}
}
protected RectF getCenter(Matrix supportMatrix, boolean horizontal, boolean vertical) {
final Drawable drawable = getDrawable();
if (drawable == null)
return new RectF(0, 0, 0, 0);
mCenterRect.set(0, 0, 0, 0);
RectF rect = getBitmapRect(supportMatrix);
float height = rect.height();
float width = rect.width();
float deltaX = 0, deltaY = 0;
if (vertical) {
int viewHeight = mThisHeight;
if (height < viewHeight) {
deltaY = (viewHeight - height) / 2 - rect.top;
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = mThisHeight - rect.bottom;
}
}
if (horizontal) {
int viewWidth = mThisWidth;
if (width < viewWidth) {
deltaX = (viewWidth - width) / 2 - rect.left;
} else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
}
}
mCenterRect.set(deltaX, deltaY, 0, 0);
return mCenterRect;
}
protected void postTranslate(float deltaX, float deltaY) {
if (deltaX != 0 || deltaY != 0) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "postTranslate: " + deltaX + "x" + deltaY);
}
mSuppMatrix.postTranslate(deltaX, deltaY);
setImageMatrix(getImageViewMatrix());
}
}
protected void postScale(float scale, float centerX, float centerY) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "postScale: " + scale + ", center: " + centerX + "x" + centerY);
}
mSuppMatrix.postScale(scale, scale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
}
protected PointF getCenter() {
return mCenter;
}
protected void zoomTo(float scale) {
if (LOG_ENABLED) {
Log.i(LOG_TAG, "zoomTo: " + scale);
}
if (scale > getMaxScale())
scale = getMaxScale();
if (scale < getMinScale())
scale = getMinScale();
if (LOG_ENABLED) {
Log.d(LOG_TAG, "sanitized scale: " + scale);
}
PointF center = getCenter();
zoomTo(scale, center.x, center.y);
}
/**
* Scale to the target scale
*
* @param scale the target zoom
* @param durationMs the animation duration
*/
protected void zoomTo(float scale, float durationMs) {
PointF center = getCenter();
zoomTo(scale, center.x, center.y, durationMs);
}
protected void zoomTo(float scale, float centerX, float centerY) {
if (scale > getMaxScale())
scale = getMaxScale();
float oldScale = getScale();
float deltaScale = scale / oldScale;
postScale(deltaScale, centerX, centerY);
onZoom(getScale());
center(true, true);
}
protected void zoomTo(float scale, float centerX, float centerY, final float durationMs) {
if (scale > getMaxScale()) scale = getMaxScale();
final long startTime = System.currentTimeMillis();
final float oldScale = getScale();
final float deltaScale = scale - oldScale;
Matrix m = new Matrix(mSuppMatrix);
m.postScale(scale, scale, centerX, centerY);
RectF rect = getCenter(m, true, true);
final float destX = centerX + rect.left * scale;
final float destY = centerY + rect.top * scale;
mHandler.post(new Runnable() {
@Override
public void run() {
long now = System.currentTimeMillis();
float currentMs = Math.min(durationMs, now - startTime);
float newScale = (float) mEasing.easeInOut(currentMs, 0, deltaScale, durationMs);
zoomTo(oldScale + newScale, destX, destY);
if (currentMs < durationMs) {
mHandler.post(this);
} else {
onZoomAnimationCompleted(getScale());
center(true, true);
}
}
});
}
protected void onZoom(float scale) {
}
protected void onZoomAnimationCompleted(float scale) {
}
/**
* Scrolls the view by the x and y amount
*/
public void scrollBy(float x, float y) {
panBy(x, y);
}
protected void panBy(double dx, double dy) {
RectF rect = getBitmapRect();
mScrollRect.set((float) dx * scrollFactor, (float) dy * scrollFactor, 0, 0);
updateRect(rect, mScrollRect);
postTranslate(mScrollRect.left, mScrollRect.top);
center(true, true);
}
protected void updateRect(RectF bitmapRect, RectF scrollRect) {
if (bitmapRect == null)
return;
if (bitmapRect.top >= 0 && bitmapRect.bottom <= mThisHeight)
scrollRect.top = 0;
if (bitmapRect.left >= 0 && bitmapRect.right <= mThisWidth)
scrollRect.left = 0;
if (bitmapRect.top + scrollRect.top >= 0 && bitmapRect.bottom > mThisHeight)
scrollRect.top = (int) (0 - bitmapRect.top);
if (bitmapRect.bottom + scrollRect.top <= (mThisHeight - 0) && bitmapRect.top < 0)
scrollRect.top = (int) ((mThisHeight - 0) - bitmapRect.bottom);
if (bitmapRect.left + scrollRect.left >= 0)
scrollRect.left = (int) (0 - bitmapRect.left);
if (bitmapRect.right + scrollRect.left <= (mThisWidth - 0))
scrollRect.left = (int) ((mThisWidth - 0) - bitmapRect.right);
}
protected void scrollBy(float distanceX, float distanceY, final double durationMs) {
final double dx = distanceX;
final double dy = distanceY;
final long startTime = System.currentTimeMillis();
mHandler.post(new Runnable() {
double old_x = 0;
double old_y = 0;
@Override
public void run() {
long now = System.currentTimeMillis();
double currentMs = Math.min(durationMs, now - startTime);
double x = mEasing.easeOut(currentMs, 0, dx, durationMs);
double y = mEasing.easeOut(currentMs, 0, dy, durationMs);
panBy((x - old_x), (y - old_y));
old_x = x;
old_y = y;
if (currentMs < durationMs) {
mHandler.post(this);
} else {
RectF centerRect = getCenter(mSuppMatrix, true, true);
if (centerRect.left != 0 || centerRect.top != 0)
scrollBy(centerRect.left, centerRect.top);
}
}
});
}
@Override
public void dispose() {
clear();
}
public float getScrollFactor() {
return scrollFactor;
}
public void setScrollFactor(float scrollFactor) {
this.scrollFactor = scrollFactor;
}
/**
* Use this to change the
* {@link ImageViewTouchBase#setDisplayType(DisplayType)} of this View
*
* @author alessandro
*/
public enum DisplayType {
/**
* Image is not scaled by default
*/
NONE,
/**
* Image will be always presented using this view's bounds
*/
FIT_TO_SCREEN,
/*
* /** Image will be scaled only if bigger than the bounds of this view
* FIT_IF_BIGGER,/
*/
/**
* La imagen se escala al ancho, Image will be scaled to fit in width
*/
FIT_TO_WIDTH,
/**
* La imagen se escala al alto, Image will be scaled to fit in height
*/
FIT_TO_HEIGHT;
public DisplayType getNext() {
return values()[(ordinal() + 1) % values().length];
}
}
public enum InitialPosition {
/**
* Image on center
*/
CENTER,
/**
* Image right top corner
*/
RIGHT_UP,
/**
* Image left top corner
*/
LEFT_UP,
/**
* Image center on top
*/
CENTER_UP,
/**
* Image align right center vertical
*/
RIGHT_CENTER,
/**
* Image align right center vertical
*/
LEFT_CENTER,
/**
* Image align left bottom
*/
LEFT_BOTTOM
}
public interface OnDrawableChangeListener {
/**
* Callback invoked when a new drawable has been assigned to the view
*/
void onDrawableChanged(Drawable drawable);
}
public interface OnLayoutChangeListener {
/**
* Callback invoked when the layout bounds changed
*/
void onLayoutChanged(boolean changed, int left, int top, int right, int bottom);
}
}