From 7a3c544825e7c6c7fe3af8b947f2ea463b4723cb Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Wed, 17 Oct 2012 14:21:48 +0400 Subject: switch to ImageViewTouch --- .../android/library/imagezoom/ImageViewTouch.java | 261 +++++++++++ .../library/imagezoom/ImageViewTouchBase.java | 486 +++++++++++++++++++++ .../android/library/imagezoom/easing/Cubic.java | 20 + .../android/library/imagezoom/easing/Easing.java | 10 + .../imagezoom/graphics/FastBitmapDrawable.java | 84 ++++ .../imagezoom/graphics/IBitmapDrawable.java | 14 + .../library/imagezoom/utils/IDisposable.java | 6 + src/org/fox/ttcomics/ComicFragment.java | 18 +- src/org/fox/ttcomics/TouchImageView.java | 269 ------------ 9 files changed, 891 insertions(+), 277 deletions(-) create mode 100644 src/it/sephiroth/android/library/imagezoom/ImageViewTouch.java create mode 100644 src/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java create mode 100644 src/it/sephiroth/android/library/imagezoom/easing/Cubic.java create mode 100644 src/it/sephiroth/android/library/imagezoom/easing/Easing.java create mode 100644 src/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java create mode 100644 src/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java create mode 100644 src/it/sephiroth/android/library/imagezoom/utils/IDisposable.java delete mode 100644 src/org/fox/ttcomics/TouchImageView.java (limited to 'src') diff --git a/src/it/sephiroth/android/library/imagezoom/ImageViewTouch.java b/src/it/sephiroth/android/library/imagezoom/ImageViewTouch.java new file mode 100644 index 0000000..eff1dd3 --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/ImageViewTouch.java @@ -0,0 +1,261 @@ +package it.sephiroth.android.library.imagezoom; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.ViewConfiguration; + +public class ImageViewTouch extends ImageViewTouchBase { + + private static final float SCROLL_DELTA_THRESHOLD = 1.0f; + static final float MIN_ZOOM = 0.9f; + protected ScaleGestureDetector mScaleDetector; + protected GestureDetector mGestureDetector; + protected int mTouchSlop; + protected float mCurrentScaleFactor; + protected float mScaleFactor; + protected int mDoubleTapDirection; + protected OnGestureListener mGestureListener; + protected OnScaleGestureListener mScaleListener; + protected boolean mDoubleTapToZoomEnabled = true; + protected boolean mScaleEnabled = true; + protected boolean mScrollEnabled = true; + + private OnImageViewTouchDoubleTapListener doubleTapListener; + + public interface OnScaleChangedListener { + public void onScaleChanged(float scale); + } + + protected OnScaleChangedListener mScaleChangedListener; + + public ImageViewTouch( Context context, AttributeSet attrs ) { + super( context, attrs ); + } + + @Override + protected void init() { + super.init(); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mGestureListener = getGestureListener(); + mScaleListener = getScaleListener(); + + mScaleDetector = new ScaleGestureDetector( getContext(), mScaleListener ); + mGestureDetector = new GestureDetector( getContext(), mGestureListener, null, true ); + + mCurrentScaleFactor = 1f; + mDoubleTapDirection = 1; + } + + public void setDoubleTapListener( OnImageViewTouchDoubleTapListener doubleTapListener ){ + this.doubleTapListener = doubleTapListener; + } + + public void setDoubleTapToZoomEnabled( boolean value ) { + mDoubleTapToZoomEnabled = value; + } + + public void setScaleEnabled( boolean value ) { + mScaleEnabled = value; + } + + public void setScrollEnabled( boolean value ) { + mScrollEnabled = value; + } + + public boolean getDoubleTapEnabled() { + return mDoubleTapToZoomEnabled; + } + + protected OnGestureListener getGestureListener() { + return new GestureListener(); + } + + protected OnScaleGestureListener getScaleListener() { + return new ScaleListener(); + } + + @Override + protected void onBitmapChanged( Drawable drawable ) { + super.onBitmapChanged( drawable ); + + float v[] = new float[9]; + mSuppMatrix.getValues( v ); + mCurrentScaleFactor = v[Matrix.MSCALE_X]; + } + + @Override + protected void _setImageDrawable( final Drawable drawable, final boolean reset, final Matrix initial_matrix, final float maxZoom ) { + super._setImageDrawable( drawable, reset, initial_matrix, maxZoom ); + mScaleFactor = getMaxZoom() / 3; + } + + @Override + public boolean onTouchEvent( MotionEvent event ) { + mScaleDetector.onTouchEvent( event ); + if ( !mScaleDetector.isInProgress() ) mGestureDetector.onTouchEvent( event ); + int action = event.getAction(); + switch ( action & MotionEvent.ACTION_MASK ) { + case MotionEvent.ACTION_UP: + if ( getScale() < 1f ) { + zoomTo( 1f, 50 ); + } + break; + } + if (mScaleChangedListener != null) { + mScaleChangedListener.onScaleChanged(mCurrentScaleFactor); + } + return true; + } + + @Override + protected void onZoom( float scale ) { + super.onZoom( scale ); + if ( !mScaleDetector.isInProgress() ) mCurrentScaleFactor = scale; + } + + protected float onDoubleTapPost( float scale, float maxZoom ) { + if ( mDoubleTapDirection == 1 ) { + if (mCurrentScaleFactor - 1.0f < 0.01) { //( scale + ( mScaleFactor * 2 ) ) <= maxZoom + + float w = getDrawable().getIntrinsicWidth() * mCurrentScaleFactor; + float scaleFactor = mScaleFactor; + + if (w < getWidth()) { + scaleFactor = (getWidth() / w) - 1f + 0.1f; + } + + return scale + scaleFactor; + } else { + mDoubleTapDirection = -1; + return maxZoom; + } + } else { + mDoubleTapDirection = 1; + return 1f; + } + } + + /** + * Determines whether this ImageViewTouch can be scrolled. + * @param direction + * - positive direction value means scroll from right to left, + * negative value means scroll from left to right + * + * @return true if there is some more place to scroll, false - otherwise. + */ + public boolean canScroll(int direction) { + RectF bitmapRect = getBitmapRect(); + updateRect(bitmapRect, mScrollRect); + Rect imageViewRect = new Rect(); + getGlobalVisibleRect(imageViewRect); + + if (bitmapRect.right >= imageViewRect.right) { + if (direction < 0) { + return Math.abs(bitmapRect.right - imageViewRect.right) > SCROLL_DELTA_THRESHOLD; + } + } + + double bitmapScrollRectDelta = Math.abs(bitmapRect.left - mScrollRect.left); + return bitmapScrollRectDelta > SCROLL_DELTA_THRESHOLD; + } + + public class GestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onDoubleTap( MotionEvent e ) { + Log.i( LOG_TAG, "onDoubleTap. double tap enabled? " + mDoubleTapToZoomEnabled); + if (mDoubleTapToZoomEnabled) { + float scale = getScale(); + float targetScale = scale; + targetScale = onDoubleTapPost( scale, getMaxZoom() ); + targetScale = Math.min( getMaxZoom(), Math.max( targetScale, MIN_ZOOM ) ); + mCurrentScaleFactor = targetScale; + zoomTo( targetScale, e.getX(), e.getY(), 200 ); + invalidate(); + } + + if( null != doubleTapListener ){ + doubleTapListener.onDoubleTap(); + } + + return super.onDoubleTap( e ); + } + + @Override + public void onLongPress( MotionEvent e ) { + if ( isLongClickable() ) { + if ( !mScaleDetector.isInProgress() ) { + setPressed( true ); + performLongClick(); + } + } + } + + @Override + public boolean onScroll( MotionEvent e1, MotionEvent e2, float distanceX, float distanceY ) { + if ( !mScrollEnabled ) return false; + + if ( e1 == null || e2 == null ) return false; + if ( e1.getPointerCount() > 1 || e2.getPointerCount() > 1 ) return false; + if ( mScaleDetector.isInProgress() ) return false; + if ( getScale() == 1f ) return false; + scrollBy( -distanceX, -distanceY ); + invalidate(); + return super.onScroll( e1, e2, distanceX, distanceY ); + } + + @Override + public boolean onFling( MotionEvent e1, MotionEvent e2, float velocityX, float velocityY ) { + if ( !mScrollEnabled ) return false; + + if ( e1.getPointerCount() > 1 || e2.getPointerCount() > 1 ) return false; + if ( mScaleDetector.isInProgress() ) return false; + + float diffX = e2.getX() - e1.getX(); + float diffY = e2.getY() - e1.getY(); + + if ( Math.abs( velocityX ) > 800 || Math.abs( velocityY ) > 800 ) { + scrollBy( diffX / 2, diffY / 2, 300 ); + invalidate(); + } + return super.onFling( e1, e2, velocityX, velocityY ); + } + } + + public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + @SuppressWarnings("unused") + @Override + public boolean onScale( ScaleGestureDetector detector ) { + float span = detector.getCurrentSpan() - detector.getPreviousSpan(); + float targetScale = mCurrentScaleFactor * detector.getScaleFactor(); + if ( mScaleEnabled ) { + targetScale = Math.min( getMaxZoom(), Math.max( targetScale, MIN_ZOOM ) ); + zoomTo( targetScale, detector.getFocusX(), detector.getFocusY() ); + mCurrentScaleFactor = Math.min( getMaxZoom(), Math.max( targetScale, MIN_ZOOM ) ); + mDoubleTapDirection = 1; + invalidate(); + return true; + } + return false; + } + } + + public interface OnImageViewTouchDoubleTapListener { + void onDoubleTap(); + } + + public void setOnScaleChangedListener(OnScaleChangedListener listener) { + mScaleChangedListener = listener; + } +} diff --git a/src/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java b/src/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java new file mode 100644 index 0000000..a0e1a3b --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java @@ -0,0 +1,486 @@ +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; + 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( 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(); + } + } + + @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() ); + } + } + + @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.
+ * 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 ); + } 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 ) / MAX_ZOOM, ( viewHeight - h * scale ) / MAX_ZOOM ); + } + + 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 ) { + // Log.i(LOG_TAG, "zoomTo"); + + 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(); + } +} diff --git a/src/it/sephiroth/android/library/imagezoom/easing/Cubic.java b/src/it/sephiroth/android/library/imagezoom/easing/Cubic.java new file mode 100644 index 0000000..6f7e87d --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/easing/Cubic.java @@ -0,0 +1,20 @@ +package it.sephiroth.android.library.imagezoom.easing; + +public class Cubic implements Easing { + + @Override + public double easeOut( double time, double start, double end, double duration ) { + return end * ( ( time = time / duration - 1.0 ) * time * time + 1.0 ) + start; + } + + @Override + public double easeIn( double time, double start, double end, double duration ) { + return end * ( time /= duration ) * time * time + start; + } + + @Override + public double easeInOut( double time, double start, double end, double duration ) { + if ( ( time /= duration / 2.0 ) < 1.0 ) return end / 2.0 * time * time * time + start; + return end / 2.0 * ( ( time -= 2.0 ) * time * time + 2.0 ) + start; + } +} diff --git a/src/it/sephiroth/android/library/imagezoom/easing/Easing.java b/src/it/sephiroth/android/library/imagezoom/easing/Easing.java new file mode 100644 index 0000000..202e9d9 --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/easing/Easing.java @@ -0,0 +1,10 @@ +package it.sephiroth.android.library.imagezoom.easing; + +public interface Easing { + + double easeOut( double time, double start, double end, double duration ); + + double easeIn( double time, double start, double end, double duration ); + + double easeInOut( double time, double start, double end, double duration ); +} diff --git a/src/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java b/src/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java new file mode 100644 index 0000000..8afc38e --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java @@ -0,0 +1,84 @@ +package it.sephiroth.android.library.imagezoom.graphics; + +import java.io.InputStream; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; + +/** + * Fast bitmap drawable. Does not support states. it only + * support alpha and colormatrix + * @author alessandro + * + */ +public class FastBitmapDrawable extends Drawable implements IBitmapDrawable { + + protected Bitmap mBitmap; + protected Paint mPaint; + + public FastBitmapDrawable( Bitmap b ) { + mBitmap = b; + mPaint = new Paint(); + mPaint.setDither( true ); + mPaint.setFilterBitmap( true ); + } + + public FastBitmapDrawable( Resources res, InputStream is ){ + this(BitmapFactory.decodeStream(is)); + } + + @Override + public void draw( Canvas canvas ) { + canvas.drawBitmap( mBitmap, 0.0f, 0.0f, mPaint ); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha( int alpha ) { + mPaint.setAlpha( alpha ); + } + + @Override + public void setColorFilter( ColorFilter cf ) { + mPaint.setColorFilter( cf ); + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight(); + } + + @Override + public int getMinimumWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getMinimumHeight() { + return mBitmap.getHeight(); + } + + public void setAntiAlias( boolean value ){ + mPaint.setAntiAlias( value ); + invalidateSelf(); + } + + @Override + public Bitmap getBitmap() { + return mBitmap; + } +} \ No newline at end of file diff --git a/src/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java b/src/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java new file mode 100644 index 0000000..5a2892a --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java @@ -0,0 +1,14 @@ +package it.sephiroth.android.library.imagezoom.graphics; + +import it.sephiroth.android.library.imagezoom.ImageViewTouchBase; +import android.graphics.Bitmap; + +/** + * Base interface used in the {@link ImageViewTouchBase} view + * @author alessandro + * + */ +public interface IBitmapDrawable { + + Bitmap getBitmap(); +} diff --git a/src/it/sephiroth/android/library/imagezoom/utils/IDisposable.java b/src/it/sephiroth/android/library/imagezoom/utils/IDisposable.java new file mode 100644 index 0000000..da991a7 --- /dev/null +++ b/src/it/sephiroth/android/library/imagezoom/utils/IDisposable.java @@ -0,0 +1,6 @@ +package it.sephiroth.android.library.imagezoom.utils; + +public interface IDisposable { + + void dispose(); +} diff --git a/src/org/fox/ttcomics/ComicFragment.java b/src/org/fox/ttcomics/ComicFragment.java index 4acd732..2221b3a 100644 --- a/src/org/fox/ttcomics/ComicFragment.java +++ b/src/org/fox/ttcomics/ComicFragment.java @@ -1,5 +1,7 @@ package org.fox.ttcomics; +import it.sephiroth.android.library.imagezoom.ImageViewTouch; + import java.io.IOException; import java.io.InputStream; @@ -16,6 +18,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.ImageView.ScaleType; import android.widget.TextView; public class ComicFragment extends Fragment { @@ -66,7 +69,7 @@ public class ComicFragment extends Fragment { View view = inflater.inflate(R.layout.fragment_comic, container, false); - TouchImageView image = (TouchImageView) view.findViewById(R.id.comic_image); + ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.comic_image); if (savedInstanceState != null) { m_page = savedInstanceState.getInt("page"); @@ -79,10 +82,9 @@ public class ComicFragment extends Fragment { image.setBackgroundColor(0xff000000); } - + image.setFitToScreen(true); image.setImageBitmap(loadImage(pager.getArchive(), m_page)); - image.setMaxZoom(4f); - image.setOnScaleChangedListener(new TouchImageView.OnScaleChangedListener() { + image.setOnScaleChangedListener(new ImageViewTouch.OnScaleChangedListener() { @Override public void onScaleChanged(float scale) { ViewPager pager = (ViewPager) getActivity().findViewById(R.id.comics_pager); @@ -91,9 +93,9 @@ public class ComicFragment extends Fragment { pager.setPagingEnabled(scale - 1.0f < 0.01); } } - }); + }); - image.setCustomOnTouchListener(new View.OnTouchListener() { + image.setOnTouchListener(new View.OnTouchListener() { int m_x; int m_y; @@ -121,9 +123,9 @@ public class ComicFragment extends Fragment { } return false; } - }); + }); - } + } TextView page = (TextView) view.findViewById(R.id.comic_page); diff --git a/src/org/fox/ttcomics/TouchImageView.java b/src/org/fox/ttcomics/TouchImageView.java deleted file mode 100644 index 9a9efe6..0000000 --- a/src/org/fox/ttcomics/TouchImageView.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * TouchImageView.java - * By: Michael Ortiz - * Updated By: Patrick Lackemacher - * ------------------- - * Extends Android ImageView to include pinch zooming and panning. - */ - -package org.fox.ttcomics; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.widget.ImageView; - -public class TouchImageView extends ImageView { - - public interface OnScaleChangedListener { - public void onScaleChanged(float scale); - } - - OnScaleChangedListener mScaleChangedListener; - View.OnTouchListener mCustomOnTouchListener; - - Matrix matrix = new Matrix(); - - // We can be in one of these 3 states - static final int NONE = 0; - static final int DRAG = 1; - static final int ZOOM = 2; - int mode = NONE; - - // Remember some things for zooming - PointF last = new PointF(); - PointF start = new PointF(); - float minScale = 1f; - float maxScale = 3f; - float[] m; - - float redundantXSpace, redundantYSpace; - - float width, height; - static final int CLICK = 3; - float saveScale = 1f; - float right, bottom, origWidth, origHeight, bmWidth, bmHeight; - - ScaleGestureDetector mScaleDetector; - - Context context; - - public TouchImageView(Context context) { - super(context); - sharedConstructing(context); - } - - public TouchImageView(Context context, AttributeSet attrs) { - super(context, attrs); - sharedConstructing(context); - } - - private void sharedConstructing(Context context) { - super.setClickable(true); - this.context = context; - mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); - matrix.setTranslate(1f, 1f); - m = new float[9]; - setImageMatrix(matrix); - setScaleType(ScaleType.MATRIX); - - setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - mScaleDetector.onTouchEvent(event); - - matrix.getValues(m); - float x = m[Matrix.MTRANS_X]; - float y = m[Matrix.MTRANS_Y]; - PointF curr = new PointF(event.getX(), event.getY()); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - last.set(event.getX(), event.getY()); - start.set(last); - mode = DRAG; - break; - case MotionEvent.ACTION_MOVE: - if (mode == DRAG) { - float deltaX = curr.x - last.x; - float deltaY = curr.y - last.y; - float scaleWidth = Math.round(origWidth * saveScale); - float scaleHeight = Math.round(origHeight * saveScale); - if (scaleWidth < width) { - deltaX = 0; - if (y + deltaY > 0) - deltaY = -y; - else if (y + deltaY < -bottom) - deltaY = -(y + bottom); - } else if (scaleHeight < height) { - deltaY = 0; - if (x + deltaX > 0) - deltaX = -x; - else if (x + deltaX < -right) - deltaX = -(x + right); - } else { - if (x + deltaX > 0) - deltaX = -x; - else if (x + deltaX < -right) - deltaX = -(x + right); - - if (y + deltaY > 0) - deltaY = -y; - else if (y + deltaY < -bottom) - deltaY = -(y + bottom); - } - matrix.postTranslate(deltaX, deltaY); - last.set(curr.x, curr.y); - } - break; - - case MotionEvent.ACTION_UP: - mode = NONE; - int xDiff = (int) Math.abs(curr.x - start.x); - int yDiff = (int) Math.abs(curr.y - start.y); - if (xDiff < CLICK && yDiff < CLICK) - performClick(); - break; - - case MotionEvent.ACTION_POINTER_UP: - mode = NONE; - break; - } - setImageMatrix(matrix); - invalidate(); - - if (mCustomOnTouchListener != null) { - mCustomOnTouchListener.onTouch(v, event); - } - - return true; // indicate event was handled - } - - }); - } - - @Override - public void setImageBitmap(Bitmap bm) { - super.setImageBitmap(bm); - if(bm != null) { - bmWidth = bm.getWidth(); - bmHeight = bm.getHeight(); - } - } - - public void setMaxZoom(float x) - { - maxScale = x; - } - - private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - mode = ZOOM; - return true; - } - - @Override - public boolean onScale(ScaleGestureDetector detector) { - float mScaleFactor = detector.getScaleFactor(); - float origScale = saveScale; - saveScale *= mScaleFactor; - if (saveScale > maxScale) { - saveScale = maxScale; - mScaleFactor = maxScale / origScale; - } else if (saveScale < minScale) { - saveScale = minScale; - mScaleFactor = minScale / origScale; - } - right = width * saveScale - width - (2 * redundantXSpace * saveScale); - bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); - if (origWidth * saveScale <= width || origHeight * saveScale <= height) { - matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2); - if (mScaleFactor < 1) { - matrix.getValues(m); - float x = m[Matrix.MTRANS_X]; - float y = m[Matrix.MTRANS_Y]; - if (mScaleFactor < 1) { - if (Math.round(origWidth * saveScale) < width) { - if (y < -bottom) - matrix.postTranslate(0, -(y + bottom)); - else if (y > 0) - matrix.postTranslate(0, -y); - } else { - if (x < -right) - matrix.postTranslate(-(x + right), 0); - else if (x > 0) - matrix.postTranslate(-x, 0); - } - } - } - } else { - matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY()); - matrix.getValues(m); - float x = m[Matrix.MTRANS_X]; - float y = m[Matrix.MTRANS_Y]; - if (mScaleFactor < 1) { - if (x < -right) - matrix.postTranslate(-(x + right), 0); - else if (x > 0) - matrix.postTranslate(-x, 0); - if (y < -bottom) - matrix.postTranslate(0, -(y + bottom)); - else if (y > 0) - matrix.postTranslate(0, -y); - } - } - - if (mScaleChangedListener != null) { - mScaleChangedListener.onScaleChanged(saveScale); - } - - return true; - - } - } - - @Override - protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) - { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - width = MeasureSpec.getSize(widthMeasureSpec); - height = MeasureSpec.getSize(heightMeasureSpec); - //Fit to screen. - float scale; - float scaleX = (float)width / (float)bmWidth; - float scaleY = (float)height / (float)bmHeight; - scale = Math.min(scaleX, scaleY); - matrix.setScale(scale, scale); - setImageMatrix(matrix); - saveScale = 1f; - - // Center the image - redundantYSpace = (float)height - (scale * (float)bmHeight) ; - redundantXSpace = (float)width - (scale * (float)bmWidth); - redundantYSpace /= (float)2; - redundantXSpace /= (float)2; - - matrix.postTranslate(redundantXSpace, redundantYSpace); - - origWidth = width - 2 * redundantXSpace; - origHeight = height - 2 * redundantYSpace; - right = width * saveScale - width - (2 * redundantXSpace * saveScale); - bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); - setImageMatrix(matrix); - } - - public void setOnScaleChangedListener(OnScaleChangedListener listener) { - mScaleChangedListener = listener; - } - - public void setCustomOnTouchListener(View.OnTouchListener listener) { - mCustomOnTouchListener = listener; - } -} -- cgit v1.2.3