505 lines
14 KiB
Java
505 lines
14 KiB
Java
package it.sephiroth.android.library.imagezoom;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Matrix;
|
|
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.imagezoom.easing.Cubic;
|
|
import it.sephiroth.android.library.imagezoom.easing.Easing;
|
|
import it.sephiroth.android.library.imagezoom.graphics.FastBitmapDrawable;
|
|
import it.sephiroth.android.library.imagezoom.utils.IDisposable;
|
|
|
|
/**
|
|
* Base View to manage image zoom/scrool/pinch operations
|
|
*
|
|
* @author alessandro
|
|
*/
|
|
public class ImageViewTouchBase extends ImageView implements IDisposable {
|
|
|
|
public static final String LOG_TAG = "image";
|
|
|
|
protected Easing mEasing = new Cubic();
|
|
|
|
protected Matrix mBaseMatrix = new Matrix();
|
|
protected Matrix mSuppMatrix = new Matrix();
|
|
protected Handler mHandler = new Handler();
|
|
protected Runnable mOnLayoutRunnable = null;
|
|
protected float mMaxZoom;
|
|
protected final Matrix mDisplayMatrix = new Matrix();
|
|
protected final float[] mMatrixValues = new float[9];
|
|
protected int mThisWidth = -1, mThisHeight = -1;
|
|
protected boolean mFitToScreen = false;
|
|
final protected float MAX_ZOOM = 2.0f;
|
|
protected RectF mBitmapRect = new RectF();
|
|
|
|
protected RectF mCenterRect = new RectF();
|
|
protected RectF mScrollRect = new RectF();
|
|
private OnBitmapChangedListener mListener;
|
|
|
|
public ImageViewTouchBase(final Context context) {
|
|
super(context);
|
|
init();
|
|
}
|
|
|
|
public ImageViewTouchBase(final Context context, final AttributeSet attrs) {
|
|
super(context, attrs);
|
|
init();
|
|
}
|
|
|
|
public void clear() {
|
|
setImageBitmap(null, true);
|
|
}
|
|
|
|
@Override
|
|
public void dispose() {
|
|
clear();
|
|
}
|
|
|
|
/**
|
|
* Returns the current image display matrix. This matrix can be used in the
|
|
* next call to the {@link #setImageBitmap(Bitmap, boolean, Matrix)} to
|
|
* restore the same view state of the previous {@link Bitmap}
|
|
*
|
|
* @return
|
|
*/
|
|
public Matrix getDisplayMatrix() {
|
|
return new Matrix(mSuppMatrix);
|
|
}
|
|
|
|
public Matrix getImageViewMatrix() {
|
|
mDisplayMatrix.set(mBaseMatrix);
|
|
mDisplayMatrix.postConcat(mSuppMatrix);
|
|
return mDisplayMatrix;
|
|
}
|
|
|
|
public float getMaxZoom() {
|
|
return mMaxZoom;
|
|
}
|
|
|
|
@Override
|
|
public float getRotation() {
|
|
return 0;
|
|
}
|
|
|
|
public float getScale() {
|
|
return getScale(mSuppMatrix);
|
|
}
|
|
|
|
public void scrollBy(final float x, final float y) {
|
|
panBy(x, y);
|
|
}
|
|
|
|
public void setFitToScreen(final boolean value) {
|
|
if (value != mFitToScreen) {
|
|
mFitToScreen = value;
|
|
requestLayout();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setImageBitmap(final Bitmap bm) {
|
|
setImageBitmap(bm, true);
|
|
}
|
|
|
|
/**
|
|
* Set the new image to display and reset the internal matrix.
|
|
*
|
|
* @param bitmap - the {@link Bitmap} to display
|
|
* @param reset - if true the image bounds will be recreated, otherwise the
|
|
* old {@link Matrix} is used to display the new bitmap
|
|
* @see #setImageBitmap(Bitmap)
|
|
*/
|
|
public void setImageBitmap(final Bitmap bitmap, final boolean reset) {
|
|
setImageBitmap(bitmap, reset, null);
|
|
}
|
|
|
|
/**
|
|
* Similar to {@link #setImageBitmap(Bitmap, boolean)} but an optional view
|
|
* {@link Matrix} can be passed to determine the new bitmap view matrix.<br />
|
|
* This method is useful if you need to restore a Bitmap with the same
|
|
* zoom/pan values from a previous state
|
|
*
|
|
* @param bitmap - the {@link Bitmap} to display
|
|
* @param reset
|
|
* @param matrix - the {@link Matrix} to be used to display the new bitmap
|
|
* @see #setImageBitmap(Bitmap, boolean)
|
|
* @see #setImageBitmap(Bitmap)
|
|
* @see #getImageViewMatrix()
|
|
* @see #getDisplayMatrix()
|
|
*/
|
|
public void setImageBitmap(final Bitmap bitmap, final boolean reset, final Matrix matrix) {
|
|
setImageBitmap(bitmap, reset, matrix, -1);
|
|
}
|
|
|
|
/**
|
|
* @param bitmap
|
|
* @param reset
|
|
* @param matrix
|
|
* @param maxZoom - maximum zoom allowd during zoom gestures
|
|
* @see #setImageBitmap(Bitmap, boolean, Matrix)
|
|
*/
|
|
public void setImageBitmap(final Bitmap bitmap, final boolean reset, final Matrix matrix, final float maxZoom) {
|
|
|
|
Log.i(LOG_TAG, "setImageBitmap: " + bitmap);
|
|
|
|
if (bitmap != null) {
|
|
setImageDrawable(new FastBitmapDrawable(bitmap), reset, matrix, maxZoom);
|
|
} else {
|
|
setImageDrawable(null, reset, matrix, maxZoom);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setImageDrawable(final Drawable drawable) {
|
|
setImageDrawable(drawable, true, null, -1);
|
|
}
|
|
|
|
public void setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix,
|
|
final float maxZoom) {
|
|
|
|
final int viewWidth = getWidth();
|
|
|
|
if (viewWidth <= 0) {
|
|
mOnLayoutRunnable = new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
setImageDrawable(drawable, reset, initial_matrix, maxZoom);
|
|
}
|
|
};
|
|
return;
|
|
}
|
|
|
|
_setImageDrawable(drawable, reset, initial_matrix, maxZoom);
|
|
}
|
|
|
|
@Override
|
|
public void setImageResource(final int resId) {
|
|
setImageDrawable(getContext().getResources().getDrawable(resId));
|
|
}
|
|
|
|
public void setOnBitmapChangedListener(final OnBitmapChangedListener listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
public void zoomTo(final float scale, final float durationMs) {
|
|
final float cx = getWidth() / 2F;
|
|
final float cy = getHeight() / 2F;
|
|
zoomTo(scale, cx, cy, durationMs);
|
|
}
|
|
|
|
protected void _setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix,
|
|
final float maxZoom) {
|
|
|
|
if (drawable != null) {
|
|
if (mFitToScreen) {
|
|
getProperBaseMatrix2(drawable, mBaseMatrix);
|
|
} else {
|
|
getProperBaseMatrix(drawable, mBaseMatrix);
|
|
}
|
|
super.setImageDrawable(drawable);
|
|
} else {
|
|
mBaseMatrix.reset();
|
|
super.setImageDrawable(null);
|
|
}
|
|
|
|
if (reset) {
|
|
mSuppMatrix.reset();
|
|
if (initial_matrix != null) {
|
|
mSuppMatrix = new Matrix(initial_matrix);
|
|
}
|
|
}
|
|
|
|
setImageMatrix(getImageViewMatrix());
|
|
|
|
if (maxZoom < 1) {
|
|
mMaxZoom = maxZoom();
|
|
} else {
|
|
mMaxZoom = maxZoom;
|
|
}
|
|
|
|
onBitmapChanged(drawable);
|
|
}
|
|
|
|
protected void center(final boolean horizontal, final boolean vertical) {
|
|
// Log.i(LOG_TAG, "center");
|
|
final Drawable drawable = getDrawable();
|
|
|
|
if (drawable == null) return;
|
|
final RectF rect = getCenter(horizontal, vertical);
|
|
if (rect.left != 0 || rect.top != 0) {
|
|
postTranslate(rect.left, rect.top);
|
|
}
|
|
}
|
|
|
|
protected RectF getBitmapRect() {
|
|
final Drawable drawable = getDrawable();
|
|
|
|
if (drawable == null) return null;
|
|
final Matrix m = getImageViewMatrix();
|
|
mBitmapRect.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
|
m.mapRect(mBitmapRect);
|
|
return mBitmapRect;
|
|
}
|
|
|
|
protected RectF getCenter(final boolean horizontal, final boolean vertical) {
|
|
final Drawable drawable = getDrawable();
|
|
|
|
if (drawable == null) return new RectF(0, 0, 0, 0);
|
|
|
|
final RectF rect = getBitmapRect();
|
|
final float height = rect.height();
|
|
final float width = rect.width();
|
|
float deltaX = 0, deltaY = 0;
|
|
if (vertical) {
|
|
final int viewHeight = getHeight();
|
|
if (height < viewHeight) {
|
|
deltaY = (viewHeight - height) / 2 - rect.top;
|
|
} else if (rect.top > 0) {
|
|
deltaY = -rect.top;
|
|
} else if (rect.bottom < viewHeight) {
|
|
deltaY = getHeight() - rect.bottom;
|
|
}
|
|
}
|
|
if (horizontal) {
|
|
final int viewWidth = getWidth();
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Setup the base matrix so that the image is centered and scaled properly.
|
|
*
|
|
* @param bitmap
|
|
* @param matrix
|
|
*/
|
|
protected void getProperBaseMatrix(final Drawable drawable, final Matrix matrix) {
|
|
final float viewWidth = getWidth();
|
|
final float viewHeight = getHeight();
|
|
final float w = drawable.getIntrinsicWidth();
|
|
final float h = drawable.getIntrinsicHeight();
|
|
matrix.reset();
|
|
|
|
if (w > viewWidth || h > viewHeight) {
|
|
final float widthScale = Math.min(viewWidth / w, 2.0f);
|
|
final float heightScale = Math.min(viewHeight / h, 2.0f);
|
|
final float scale = Math.min(widthScale, heightScale);
|
|
matrix.postScale(scale, scale);
|
|
final float tw = (viewWidth - w * scale) / 2.0f;
|
|
final float th = (viewHeight - h * scale) / 2.0f;
|
|
matrix.postTranslate(tw, th);
|
|
} else {
|
|
final float tw = (viewWidth - w) / 2.0f;
|
|
final float th = (viewHeight - h) / 2.0f;
|
|
matrix.postTranslate(tw, th);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup the base matrix so that the image is centered and scaled properly.
|
|
*
|
|
* @param bitmap
|
|
* @param matrix
|
|
*/
|
|
protected void getProperBaseMatrix2(final Drawable bitmap, final Matrix matrix) {
|
|
final float viewWidth = getWidth();
|
|
final float viewHeight = getHeight();
|
|
final float w = bitmap.getIntrinsicWidth();
|
|
final float h = bitmap.getIntrinsicHeight();
|
|
matrix.reset();
|
|
final float widthScale = Math.min(viewWidth / w, MAX_ZOOM);
|
|
final float heightScale = Math.min(viewHeight / h, MAX_ZOOM);
|
|
final float scale = Math.min(widthScale, heightScale);
|
|
matrix.postScale(scale, scale);
|
|
matrix.postTranslate((viewWidth - w * scale) / MAX_ZOOM, (viewHeight - h * scale) / MAX_ZOOM);
|
|
}
|
|
|
|
protected float getScale(final Matrix matrix) {
|
|
return getValue(matrix, Matrix.MSCALE_X);
|
|
}
|
|
|
|
protected float getValue(final Matrix matrix, final int whichValue) {
|
|
matrix.getValues(mMatrixValues);
|
|
return mMatrixValues[whichValue];
|
|
}
|
|
|
|
protected void init() {
|
|
setScaleType(ImageView.ScaleType.MATRIX);
|
|
}
|
|
|
|
protected float maxZoom() {
|
|
final Drawable drawable = getDrawable();
|
|
|
|
if (drawable == null) return 1F;
|
|
|
|
final float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth;
|
|
final float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight;
|
|
final float max = Math.max(fw, fh) * 4;
|
|
return max;
|
|
}
|
|
|
|
protected void onBitmapChanged(final Drawable bitmap) {
|
|
if (mListener != null) {
|
|
mListener.onBitmapChanged(bitmap);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
|
|
super.onLayout(changed, left, top, right, bottom);
|
|
mThisWidth = right - left;
|
|
mThisHeight = bottom - top;
|
|
final Runnable r = mOnLayoutRunnable;
|
|
if (r != null) {
|
|
mOnLayoutRunnable = null;
|
|
r.run();
|
|
}
|
|
if (getDrawable() != null) {
|
|
if (mFitToScreen) {
|
|
getProperBaseMatrix2(getDrawable(), mBaseMatrix);
|
|
} else {
|
|
getProperBaseMatrix(getDrawable(), mBaseMatrix);
|
|
}
|
|
setImageMatrix(getImageViewMatrix());
|
|
}
|
|
}
|
|
|
|
protected void onZoom(final float scale) {
|
|
}
|
|
|
|
protected void panBy(final double dx, final double dy) {
|
|
final RectF rect = getBitmapRect();
|
|
mScrollRect.set((float) dx, (float) dy, 0, 0);
|
|
updateRect(rect, mScrollRect);
|
|
postTranslate(mScrollRect.left, mScrollRect.top);
|
|
center(true, true);
|
|
}
|
|
|
|
protected void postScale(final float scale, final float centerX, final float centerY) {
|
|
mSuppMatrix.postScale(scale, scale, centerX, centerY);
|
|
setImageMatrix(getImageViewMatrix());
|
|
}
|
|
|
|
protected void postTranslate(final float deltaX, final float deltaY) {
|
|
mSuppMatrix.postTranslate(deltaX, deltaY);
|
|
setImageMatrix(getImageViewMatrix());
|
|
}
|
|
|
|
protected void scrollBy(final float distanceX, final 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() {
|
|
final long now = System.currentTimeMillis();
|
|
final double currentMs = Math.min(durationMs, now - startTime);
|
|
final double x = mEasing.easeOut(currentMs, 0, dx, durationMs);
|
|
final 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 {
|
|
final RectF centerRect = getCenter(true, true);
|
|
if (centerRect.left != 0 || centerRect.top != 0) {
|
|
scrollBy(centerRect.left, centerRect.top);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
protected void updateRect(final RectF bitmapRect, final RectF scrollRect) {
|
|
if (bitmapRect == null || scrollRect == null) return;
|
|
final float width = getWidth();
|
|
final float height = getHeight();
|
|
|
|
if (bitmapRect.top >= 0 && bitmapRect.bottom <= height) {
|
|
scrollRect.top = 0;
|
|
}
|
|
if (bitmapRect.left >= 0 && bitmapRect.right <= width) {
|
|
scrollRect.left = 0;
|
|
}
|
|
if (bitmapRect.top + scrollRect.top >= 0 && bitmapRect.bottom > height) {
|
|
scrollRect.top = (int) (0 - bitmapRect.top);
|
|
}
|
|
if (bitmapRect.bottom + scrollRect.top <= height - 0 && bitmapRect.top < 0) {
|
|
scrollRect.top = (int) (height - 0 - bitmapRect.bottom);
|
|
}
|
|
if (bitmapRect.left + scrollRect.left >= 0) {
|
|
scrollRect.left = (int) (0 - bitmapRect.left);
|
|
}
|
|
if (bitmapRect.right + scrollRect.left <= width - 0) {
|
|
scrollRect.left = (int) (width - 0 - bitmapRect.right);
|
|
// Log.d( LOG_TAG, "scrollRect(2): " + scrollRect.toString() );
|
|
}
|
|
}
|
|
|
|
protected void zoomTo(final float scale) {
|
|
final float cx = getWidth() / 2F;
|
|
final float cy = getHeight() / 2F;
|
|
zoomTo(scale, cx, cy);
|
|
}
|
|
|
|
protected void zoomTo(float scale, final float centerX, final float centerY) {
|
|
// Log.i(LOG_TAG, "zoomTo");
|
|
|
|
if (scale > mMaxZoom) {
|
|
scale = mMaxZoom;
|
|
}
|
|
final float oldScale = getScale();
|
|
final float deltaScale = scale / oldScale;
|
|
postScale(deltaScale, centerX, centerY);
|
|
onZoom(getScale());
|
|
center(true, true);
|
|
}
|
|
|
|
protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
|
|
// Log.i( LOG_TAG, "zoomTo: " + scale + ", " + centerX + ": " + centerY
|
|
// );
|
|
final long startTime = System.currentTimeMillis();
|
|
final float incrementPerMs = (scale - getScale()) / durationMs;
|
|
final float oldScale = getScale();
|
|
mHandler.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
final long now = System.currentTimeMillis();
|
|
final float currentMs = Math.min(durationMs, now - startTime);
|
|
final float target = oldScale + incrementPerMs * currentMs;
|
|
zoomTo(target, centerX, centerY);
|
|
if (currentMs < durationMs) {
|
|
mHandler.post(this);
|
|
} else {
|
|
// if ( getScale() < 1f ) {}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public interface OnBitmapChangedListener {
|
|
|
|
void onBitmapChanged(Drawable drawable);
|
|
}
|
|
}
|