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.support.annotation.NonNull; 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; import it.sephiroth.android.library.TapListener; public class ImageViewTouch extends ImageViewTouchBase { protected static final float SCROLL_DELTA_THRESHOLD = 1.0f; protected ScaleGestureDetector mScaleDetector; protected GestureDetector mGestureDetector; protected int mTouchSlop; protected float mScaleFactor; protected OnGestureListener mGestureListener; protected OnScaleGestureListener mScaleListener; protected boolean mScaleEnabled = true; protected boolean mScaleModeAuto = true; private TapListener mTapListener; public ImageViewTouch(Context context) { super(context); } public ImageViewTouch(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ImageViewTouch(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void init(Context context, AttributeSet attrs, int defStyle) { super.init(context, attrs, defStyle); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mGestureListener = getGestureListener(); mScaleListener = getScaleListener(); mScaleDetector = new ScaleGestureDetector(getContext(), mScaleListener); mGestureDetector = new GestureDetector(getContext(), mGestureListener, null, true); } /* public void setDoubleTapListener(OnImageViewTouchDoubleTapListener listener) { mDoubleTapListener = listener; } public void setSingleTapListener(OnImageViewTouchSingleTapListener listener) { mSingleTapListener = listener; } */ public void setTapListener(TapListener nTapListener) { mTapListener = nTapListener; } public void setScaleEnabled(boolean value) { mScaleEnabled = value; } /* public void setScrollEnabled(boolean value) { mScrollEnabled = value; } public boolean getDoubleTapEnabled() { return mDoubleTapEnabled; } public void setDoubleTapEnabled(boolean value) { mDoubleTapEnabled = value; } */ protected OnGestureListener getGestureListener() { return new GestureListener(); } protected OnScaleGestureListener getScaleListener() { return new ScaleListener(); } @Override protected void _setImageDrawable( final Drawable drawable, final Matrix initial_matrix, float min_zoom, float max_zoom) { super._setImageDrawable(drawable, initial_matrix, min_zoom, max_zoom); mScaleFactor = getMaxScale() / 3; } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { if (getBitmapChanged()) return false; mScaleDetector.onTouchEvent(event); if (!mScaleDetector.isInProgress()) mGestureDetector.onTouchEvent(event); int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_UP: return onUp(event); } return true; } @Override protected void onZoomAnimationCompleted(float scale) { if (scale < getMinScale()) zoomTo(getMinScale(), 50); } public boolean onSingleTapConfirmed(MotionEvent e) { return true; } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //if (getScale() == 1f) return false; mUserScaled = true; scrollBy(-distanceX, -distanceY); invalidate(); return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { float diffX = e2.getX() - e1.getX(); float diffY = e2.getY() - e1.getY(); if (Math.abs(velocityX) > 800 || Math.abs(velocityY) > 800) { mUserScaled = true; scrollBy(diffX / 2, diffY / 2, 300); invalidate(); return true; } return false; } public boolean onDown(MotionEvent e) { return !getBitmapChanged(); } public boolean onUp(MotionEvent e) { if (getBitmapChanged()) return false; if (getScale() < getMinScale()) { zoomTo(getMinScale(), 50); } return true; } public boolean onSingleTapUp(MotionEvent e) { return !getBitmapChanged(); } /** * 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 (null == bitmapRect) { return false; } 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 boolean canScrollV(int direction) { RectF bitmapRect = getBitmapRect(); updateRect(bitmapRect, mScrollRect); Rect imageViewRect = new Rect(); getGlobalVisibleRect(imageViewRect); if (null == bitmapRect) { return false; } if (bitmapRect.bottom - bitmapRect.top < imageViewRect.bottom - imageViewRect.top) { return false; } if (bitmapRect.bottom >= imageViewRect.bottom) { if (direction < 0) { return Math.abs(bitmapRect.bottom - imageViewRect.bottom) > SCROLL_DELTA_THRESHOLD; } } double bitmapScrollRectDelta = Math.abs(bitmapRect.top - mScrollRect.top); return bitmapScrollRectDelta > SCROLL_DELTA_THRESHOLD; } /* public void scrollToOrigin() { RectF bitmapRect = getBitmapRect(); scrollBy(-bitmapRect.right, -bitmapRect.top); } public interface OnImageViewTouchDoubleTapListener { void onDoubleTap(); } public interface OnImageViewTouchSingleTapListener { void onSingleTapConfirmed(); } */ public class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (null != mTapListener) { try { mTapListener.onSingleTapConfirmed(e); } catch (Exception ex) { // nothing } } //if (null != mSingleTapListener) mSingleTapListener.onSingleTapConfirmed(); return ImageViewTouch.this.onSingleTapConfirmed(e); } protected float onDoubleTapPost(float scale) { float defScale = getDefaultScale(mScaleType); if (scale <= 1.7f * defScale) { return 1.8f * defScale; } else if (scale <= 2.7f * defScale) { return 2.8f * defScale; } else { return defScale; } } @Override public boolean onDoubleTapEvent(MotionEvent e) { if (mScaleModeAuto && e.getAction() == MotionEvent.ACTION_UP) { float targetScale = Math.min(getMaxScale(), Math.max(onDoubleTapPost(getScale()), getMinScale())); zoomTo(targetScale, e.getX(), e.getY(), DEFAULT_ANIMATION_DURATION); invalidate(); } mScaleModeAuto = true; return super.onDoubleTapEvent(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; return ImageViewTouch.this.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; if (getScale() == 1f) return false; return ImageViewTouch.this.onFling(e1, e2, velocityX, velocityY); } @Override public boolean onSingleTapUp(MotionEvent e) { return ImageViewTouch.this.onSingleTapUp(e); } @Override public boolean onDown(MotionEvent e) { return ImageViewTouch.this.onDown(e); } } public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { protected boolean mScaled = false; @Override public boolean onScale(ScaleGestureDetector detector) { float span = detector.getCurrentSpan() - detector.getPreviousSpan(); float targetScale = getScale() * detector.getScaleFactor(); if (mScaleEnabled) { mScaleModeAuto = false; if (mScaled && span != 0) { mUserScaled = true; targetScale = Math.min(getMaxScale(), Math.max(targetScale, getMinScale() - 0.1f)); zoomTo(targetScale, detector.getFocusX(), detector.getFocusY()); invalidate(); return true; } // This is to prevent a glitch the first time image is scaled. if (!mScaled) mScaled = true; } return true; } } }