diff options
Diffstat (limited to 'org.fox.ttrss/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java')
-rw-r--r-- | org.fox.ttrss/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/org.fox.ttrss/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java b/org.fox.ttrss/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java new file mode 100644 index 00000000..8452a21d --- /dev/null +++ b/org.fox.ttrss/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java @@ -0,0 +1,509 @@ +package it.sephiroth.android.library.imagezoom; + +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; +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; + +/** + * Base View to manage image zoom/scrool/pinch operations + * + * @author alessandro + * + */ +public class ImageViewTouchBase extends ImageView implements IDisposable { + + public interface OnBitmapChangedListener { + + void onBitmapChanged( Drawable drawable ); + }; + + 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; + protected boolean mFitToWidth = false; + final protected float MAX_ZOOM = 3.0f; + + protected RectF mBitmapRect = new RectF(); + protected RectF mCenterRect = new RectF(); + protected RectF mScrollRect = new RectF(); + + private OnBitmapChangedListener mListener; + + public ImageViewTouchBase( Context context ) { + super( context ); + init(); + } + + public ImageViewTouchBase( Context context, AttributeSet attrs ) { + super( context, attrs ); + init(); + } + + public void setOnBitmapChangedListener( OnBitmapChangedListener listener ) { + mListener = listener; + } + + protected void init() { + setScaleType( ImageView.ScaleType.MATRIX ); + } + + public void clear() { + setImageBitmap( null, true ); + } + + public void setFitToScreen( boolean value ) { + if ( value != mFitToScreen ) { + mFitToScreen = value; + requestLayout(); + } + } + + public void setFitToWidth( boolean value ) { + if ( value != mFitToWidth ) { + mFitToWidth = value; + requestLayout(); + } + } + + @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 ( getDrawable() != null ) { + if ( mFitToScreen ) + getProperBaseMatrix2( getDrawable(), mBaseMatrix ); + else + getProperBaseMatrix( getDrawable(), mBaseMatrix ); + setImageMatrix( getImageViewMatrix() ); + + if (mFitToWidth) zoomToWidth(); + } + } + + @Override + public void setImageBitmap( Bitmap bm ) { + setImageBitmap( bm, true ); + } + + @Override + public void setImageResource( int resId ) { + setImageDrawable( getContext().getResources().getDrawable( resId ) ); + } + + /** + * 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, 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, Matrix matrix, 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( 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 ); + } + + 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 ); + + if (mFitToWidth) zoomToWidth(); + + } 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 onBitmapChanged( final Drawable bitmap ) { + if ( mListener != null ) { + mListener.onBitmapChanged( bitmap ); + } + } + + protected float maxZoom() { + final Drawable drawable = getDrawable(); + + if ( drawable == null ) { + return 1F; + } + + float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth; + float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight; + float max = Math.max( fw, fh ) * 4; + return max; + } + + public float getMaxZoom() { + return mMaxZoom; + } + + public Matrix getImageViewMatrix() { + mDisplayMatrix.set( mBaseMatrix ); + mDisplayMatrix.postConcat( mSuppMatrix ); + return mDisplayMatrix; + } + + /** + * 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 ); + } + + /** + * Setup the base matrix so that the image is centered and scaled properly. + * + * @param bitmap + * @param matrix + */ + protected void getProperBaseMatrix( Drawable drawable, Matrix matrix ) { + float viewWidth = getWidth(); + float viewHeight = getHeight(); + float w = drawable.getIntrinsicWidth(); + float h = drawable.getIntrinsicHeight(); + matrix.reset(); + + if ( w > viewWidth || h > viewHeight ) { + float widthScale = Math.min( viewWidth / w, 2.0f ); + float heightScale = Math.min( viewHeight / h, 2.0f ); + 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 { + float tw = ( viewWidth - w ) / 2.0f; + 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( Drawable bitmap, Matrix matrix ) { + float viewWidth = getWidth(); + float viewHeight = getHeight(); + float w = bitmap.getIntrinsicWidth(); + float h = bitmap.getIntrinsicHeight(); + 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.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]; + } + + protected RectF getBitmapRect() { + final Drawable drawable = getDrawable(); + + if ( drawable == null ) return null; + Matrix m = getImageViewMatrix(); + mBitmapRect.set( 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight() ); + m.mapRect( mBitmapRect ); + return mBitmapRect; + } + + protected float getScale( Matrix matrix ) { + return getValue( matrix, Matrix.MSCALE_X ); + } + + public float getRotation() { + return 0; + } + + public float getScale() { + return getScale( mSuppMatrix ); + } + + protected void center( boolean horizontal, boolean vertical ) { + // Log.i(LOG_TAG, "center"); + final Drawable drawable = getDrawable(); + + if ( drawable == null ) return; + RectF rect = getCenter( horizontal, vertical ); + if ( rect.left != 0 || rect.top != 0 ) { + postTranslate( rect.left, rect.top ); + } + } + + protected RectF getCenter( boolean horizontal, boolean vertical ) { + final Drawable drawable = getDrawable(); + + if ( drawable == 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; + } + } + mCenterRect.set( deltaX, deltaY, 0, 0 ); + return mCenterRect; + } + + protected void postTranslate( float deltaX, float deltaY ) { + mSuppMatrix.postTranslate( deltaX, deltaY ); + setImageMatrix( getImageViewMatrix() ); + } + + protected void postScale( float scale, float centerX, float centerY ) { + mSuppMatrix.postScale( scale, scale, centerX, centerY ); + setImageMatrix( 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( double dx, double dy ) { + RectF rect = getBitmapRect(); + mScrollRect.set( (float) dx, (float) dy, 0, 0 ); + updateRect( rect, mScrollRect ); + postTranslate( mScrollRect.left, mScrollRect.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 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( 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.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() { + 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() { + clear(); + } + + protected void zoomToWidth() { + RectF bitmapRect = getBitmapRect(); + + float w = bitmapRect.right - bitmapRect.left; + + if (w < getWidth()) { + float scale = (float)getWidth() / w; + + zoomTo(scale, 0f, 0f); + } + } +} |