package com.mcxiaoke.minicat.ui.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; public class ImageViewTouchBase extends ImageView implements IDisposable { public static final String LOG_TAG = "image"; ; protected final Matrix mDisplayMatrix = new Matrix(); ; protected final float[] mMatrixValues = new float[9]; final protected RotateBitmap mBitmapDisplayed = new RotateBitmap(null, 0); final protected float MAX_ZOOM = 2.0f; protected Matrix mBaseMatrix = new Matrix(); protected Matrix mSuppMatrix = new Matrix(); protected Handler mHandler = new Handler(); protected Runnable mOnLayoutRunnable = null; protected float mMaxZoom; protected int mThisWidth = -1, mThisHeight = -1; private OnBitmapChangedListener mListener; public ImageViewTouchBase(Context context) { super(context); init(); } public ImageViewTouchBase(Context context, AttributeSet attrs) { super(context, attrs); init(); } public static float easeOut(float time, float start, float end, float duration) { return end * ((time = time / duration - 1) * time * time + 1) + start; } public void setOnBitmapChangedListener(OnBitmapChangedListener listener) { mListener = listener; } protected void init() { setScaleType(ImageView.ScaleType.MATRIX); } public void clear() { setImageBitmapReset(null, true); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mThisWidth = right - left; mThisHeight = bottom - top; Runnable r = mOnLayoutRunnable; if (r != null) { mOnLayoutRunnable = null; r.run(); } if (mBitmapDisplayed.getBitmap() != null) { getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix); setImageMatrix(Command.Layout, getImageViewMatrix()); } } public void setImageBitmapReset(final Bitmap bitmap, final boolean reset) { setImageRotateBitmapReset(new RotateBitmap(bitmap, 0), reset); } public void setImageBitmapReset(final Bitmap bitmap, final int rotation, final boolean reset) { setImageRotateBitmapReset(new RotateBitmap(bitmap, rotation), reset); } public void setImageRotateBitmapReset(final RotateBitmap bitmap, final boolean reset) { Log.d(LOG_TAG, "setImageRotateBitmapReset"); final int viewWidth = getWidth(); if (viewWidth <= 0) { mOnLayoutRunnable = new Runnable() { @Override public void run() { setImageBitmapReset(bitmap.getBitmap(), bitmap.getRotation(), reset); } }; return; } if (bitmap.getBitmap() != null) { getProperBaseMatrix(bitmap, mBaseMatrix); setImageBitmap(bitmap.getBitmap(), bitmap.getRotation()); } else { mBaseMatrix.reset(); setImageBitmap(null); } if (reset) { mSuppMatrix.reset(); } setImageMatrix(Command.Reset, getImageViewMatrix()); mMaxZoom = maxZoom(); if (mListener != null) { mListener.onBitmapChanged(bitmap.getBitmap()); } } protected float maxZoom() { if (mBitmapDisplayed.getBitmap() == null) { return 1F; } float fw = (float) mBitmapDisplayed.getWidth() / (float) mThisWidth; float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight; float max = Math.max(fw, fh) * 4; return max; } public RotateBitmap getDisplayBitmap() { return mBitmapDisplayed; } public float getMaxZoom() { return mMaxZoom; } @Override public void setImageBitmap(Bitmap bitmap) { setImageBitmap(bitmap, 0); } /** * This is the ultimate method called when a new bitmap is set * * @param bitmap * @param rotation */ protected void setImageBitmap(Bitmap bitmap, int rotation) { super.setImageBitmap(bitmap); Drawable d = getDrawable(); if (d != null) { d.setDither(true); } mBitmapDisplayed.setBitmap(bitmap); mBitmapDisplayed.setRotation(rotation); } protected Matrix getImageViewMatrix() { mDisplayMatrix.set(mBaseMatrix); mDisplayMatrix.postConcat(mSuppMatrix); return mDisplayMatrix; } /** * Setup the base matrix so that the image is centered and scaled properly. * * @param bitmap * @param matrix */ protected void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) { float viewWidth = getWidth(); float viewHeight = getHeight(); float w = bitmap.getWidth(); float h = bitmap.getHeight(); matrix.reset(); float widthScale = Math.min(viewWidth / w, MAX_ZOOM); float heightScale = Math.min(viewHeight / h, MAX_ZOOM); float scale = Math.min(widthScale, heightScale); matrix.postConcat(bitmap.getRotateMatrix()); matrix.postScale(scale, scale); matrix.postTranslate((viewWidth - w * scale) / MAX_ZOOM, (viewHeight - h * scale) / MAX_ZOOM); } protected float getValue(Matrix matrix, int whichValue) { matrix.getValues(mMatrixValues); return mMatrixValues[whichValue]; } protected RectF getBitmapRect() { if (mBitmapDisplayed.getBitmap() == null) return null; Matrix m = getImageViewMatrix(); RectF rect = new RectF(0, 0, mBitmapDisplayed.getBitmap().getWidth(), mBitmapDisplayed.getBitmap().getHeight()); m.mapRect(rect); return rect; } protected float getScale(Matrix matrix) { return getValue(matrix, Matrix.MSCALE_X); } public float getScale() { return getScale(mSuppMatrix); } protected void center(boolean horizontal, boolean vertical) { if (mBitmapDisplayed.getBitmap() == null) return; RectF rect = getCenter(horizontal, vertical); if (rect.left != 0 || rect.top != 0) { postTranslate(rect.left, rect.top); } } protected void setImageMatrix(Command command, Matrix matrix) { setImageMatrix(matrix); } protected RectF getCenter(boolean horizontal, boolean vertical) { if (mBitmapDisplayed.getBitmap() == null) return new RectF(0, 0, 0, 0); RectF rect = getBitmapRect(); float height = rect.height(); float width = rect.width(); float deltaX = 0, deltaY = 0; if (vertical) { 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) { 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; } } return new RectF(deltaX, deltaY, 0, 0); } protected void postTranslate(float deltaX, float deltaY) { mSuppMatrix.postTranslate(deltaX, deltaY); setImageMatrix(Command.Move, getImageViewMatrix()); } protected void postScale(float scale, float centerX, float centerY) { mSuppMatrix.postScale(scale, scale, centerX, centerY); setImageMatrix(Command.Zoom, getImageViewMatrix()); } protected void zoomTo(float scale) { float cx = getWidth() / 2F; float cy = getHeight() / 2F; zoomTo(scale, cx, cy); } public void zoomTo(float scale, float durationMs) { float cx = getWidth() / 2F; float cy = getHeight() / 2F; zoomTo(scale, cx, cy, durationMs); } protected void zoomTo(float scale, float centerX, float centerY) { if (scale > mMaxZoom) scale = mMaxZoom; float oldScale = getScale(); float deltaScale = scale / oldScale; postScale(deltaScale, centerX, centerY); onZoom(getScale()); center(true, true); } protected void onZoom(float scale) { } public void scrollBy(float x, float y) { panBy(x, y); } protected void panBy(float dx, float dy) { RectF rect = getBitmapRect(); RectF srect = new RectF(dx, dy, 0, 0); updateRect(rect, srect); postTranslate(srect.left, srect.top); center(true, true); } protected void updateRect(RectF bitmapRect, RectF scrollRect) { float width = getWidth(); 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 scrollBy(float distanceX, float distanceY, final float durationMs) { final float dx = distanceX; final float dy = distanceY; final long startTime = System.currentTimeMillis(); mHandler.post(new Runnable() { float old_x = 0; float old_y = 0; @Override public void run() { long now = System.currentTimeMillis(); float currentMs = Math.min(durationMs, now - startTime); float x = easeOut(currentMs, 0, dx, durationMs); float y = 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(true, true); if (centerRect.left != 0 || centerRect.top != 0) scrollBy(centerRect.left, centerRect.top); } } }); } protected void zoomTo(float scale, final float centerX, final float centerY, final float durationMs) { // Log.d( 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() { long now = System.currentTimeMillis(); float currentMs = Math.min(durationMs, now - startTime); float target = oldScale + (incrementPerMs * currentMs); zoomTo(target, centerX, centerY); if (currentMs < durationMs) { mHandler.post(this); } else { // if ( getScale() < 1f ) {} } } }); } @Override public void dispose() { if (mBitmapDisplayed.getBitmap() != null) { if (!mBitmapDisplayed.getBitmap().isRecycled()) { mBitmapDisplayed.getBitmap().recycle(); } } clear(); } protected enum Command { Center, Move, Zoom, Layout, Reset, } public interface OnBitmapChangedListener { void onBitmapChanged(Bitmap bitmap); } }