/******************************************************************************* * Copyright 2011, 2012 Chris Banes. Licensed under the Apache License, Version * 2.0 (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. *******************************************************************************/ package com.iwedia.gui.components; import android.content.Context; import android.graphics.Matrix; import android.graphics.Matrix.ScaleToFit; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ViewTreeObserver; import android.widget.ImageView; import java.lang.ref.WeakReference; public class A4TVPhotoView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener { private float mScale = 1; private WeakReference<ImageView> mImageView; // These are set so we don't keep allocating them on the heap private final Matrix mBaseMatrix = new Matrix(); private final Matrix mDrawMatrix = new Matrix(); private final Matrix mSuppMatrix = new Matrix(); private final RectF mDisplayRect = new RectF(); private int mIvTop, mIvRight, mIvBottom, mIvLeft; public A4TVPhotoView(Context context) { this(context, null); } public A4TVPhotoView(Context context, AttributeSet attr) { this(context, attr, 0); } public A4TVPhotoView(Context context, AttributeSet attr, int defStyle) { super(context, attr, defStyle); super.setScaleType(ScaleType.MATRIX); init(this); } public void init(ImageView imageView) { mImageView = new WeakReference<ImageView>(imageView); ViewTreeObserver observer = imageView.getViewTreeObserver(); if (null != observer) { observer.addOnGlobalLayoutListener(this); } // Finally, update the UI so that we're zoomable update(); } protected void onDetachedFromWindow() { cleanup(); super.onDetachedFromWindow(); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); update(); } @Override public void setImageResource(int resId) { super.setImageResource(resId); update(); } @SuppressWarnings("deprecation") public final void cleanup() { if (null == mImageView) { return; // cleanup already done } final ImageView imageView = mImageView.get(); if (null != imageView) { // Remove this as a global layout listener ViewTreeObserver observer = imageView.getViewTreeObserver(); if (null != observer && observer.isAlive()) { observer.removeGlobalOnLayoutListener(this); } } // Finally, clear ImageView mImageView = null; } public boolean setDisplayMatrix(Matrix finalMatrix) { if (finalMatrix == null) { throw new IllegalArgumentException("Matrix cannot be null"); } ImageView imageView = getImageView(); if (null == imageView) { return false; } if (null == imageView.getDrawable()) { return false; } mSuppMatrix.set(finalMatrix); setImageViewMatrix(getDrawMatrix()); checkMatrixBounds(); return true; } public final ImageView getImageView() { ImageView imageView = null; if (null != mImageView) { imageView = mImageView.get(); } // If we don't have an ImageView, call cleanup() if (null == imageView) { cleanup(); } return imageView; } public boolean scale() { ImageView imageView = getImageView(); float x = getImageViewWidth(imageView) / 2; float y = getImageViewHeight(imageView) / 2; setScale(mScale, x, y); return true; } @Override public final void onGlobalLayout() { ImageView imageView = getImageView(); if (null != imageView) { final int top = imageView.getTop(); final int right = imageView.getRight(); final int bottom = imageView.getBottom(); final int left = imageView.getLeft(); if (top != mIvTop || bottom != mIvBottom || left != mIvLeft || right != mIvRight) { // Update our base matrix, as the bounds have changed updateBaseMatrix(imageView.getDrawable()); // Update values as something has changed mIvTop = top; mIvRight = right; mIvBottom = bottom; mIvLeft = left; } } } public void setScale(float scale) { mScale = scale; } public void setScale(float scale, float focalX, float focalY) { ImageView imageView = getImageView(); if (null != imageView) { mSuppMatrix.setScale(scale, scale, focalX, focalY); checkAndDisplayMatrix(); } } public final void update() { ImageView imageView = getImageView(); if (null != imageView) { updateBaseMatrix(imageView.getDrawable()); } } protected Matrix getDrawMatrix() { mDrawMatrix.set(mBaseMatrix); mDrawMatrix.postConcat(mSuppMatrix); return mDrawMatrix; } /** * Helper method that simply checks the Matrix, and then displays the result */ private void checkAndDisplayMatrix() { if (checkMatrixBounds()) { setImageViewMatrix(getDrawMatrix()); } } private void checkImageViewScaleType() { ImageView imageView = getImageView(); /** * PhotoView's getScaleType() will just divert to this.getScaleType() so * only call if we're not attached to a PhotoView. */ if (null != imageView && !(imageView instanceof A4TVPhotoView)) { if (!ScaleType.MATRIX.equals(imageView.getScaleType())) { throw new IllegalStateException( "The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher"); } } } private boolean checkMatrixBounds() { final ImageView imageView = getImageView(); if (null == imageView) { return false; } final RectF rect = getDisplayRect(getDrawMatrix()); if (null == rect) { return false; } final float height = rect.height(), width = rect.width(); float deltaX = 0, deltaY = 0; final int viewHeight = getImageViewHeight(imageView); if (height <= viewHeight) { deltaY = (viewHeight - height) / 2 - rect.top; } else if (rect.top > 0) { deltaY = -rect.top; } else if (rect.bottom < viewHeight) { deltaY = viewHeight - rect.bottom; } final int viewWidth = getImageViewWidth(imageView); 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; } // Finally actually translate the matrix mSuppMatrix.postTranslate(deltaX, deltaY); return true; } /** * Helper method that maps the supplied Matrix to the current Drawable * * @param matrix * - Matrix to map Drawable against * @return RectF - Displayed Rectangle */ private RectF getDisplayRect(Matrix matrix) { ImageView imageView = getImageView(); if (null != imageView) { Drawable d = imageView.getDrawable(); if (null != d) { mDisplayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(mDisplayRect); return mDisplayRect; } } return null; } /** * Resets the Matrix back to FIT_CENTER, and then displays it.s */ private void resetMatrix() { mSuppMatrix.reset(); setImageViewMatrix(getDrawMatrix()); checkMatrixBounds(); } private void setImageViewMatrix(Matrix matrix) { ImageView imageView = getImageView(); if (null != imageView) { checkImageViewScaleType(); imageView.setImageMatrix(matrix); } } /** * Calculate Matrix for FIT_CENTER * * @param d * - Drawable being displayed */ private void updateBaseMatrix(Drawable d) { ImageView imageView = getImageView(); if (null == imageView || null == d) { return; } final float viewWidth = getImageViewWidth(imageView); final float viewHeight = getImageViewHeight(imageView); final int drawableWidth = d.getIntrinsicWidth(); final int drawableHeight = d.getIntrinsicHeight(); mBaseMatrix.reset(); RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight); RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight); mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER); resetMatrix(); } private int getImageViewWidth(ImageView imageView) { if (null == imageView) { return 0; } return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight(); } private int getImageViewHeight(ImageView imageView) { if (null == imageView) { return 0; } return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom(); } }