Android Open Source - campus Zoom Image View






From Project

Back to project page campus.

License

The source code is released under:

GNU General Public License

If you think the Android project campus listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*******************************************************************************
 * Copyright 2013 Tomasz Zawada/*w  ww  .j a  v  a 2 s  .co m*/
 * 
 * Based on the excellent PhotoView by Chris Banes:
 * https://github.com/chrisbanes/PhotoView
 *
 * 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 lecho.app.campus.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.OverScroller;
import android.widget.Scroller;

public class ZoomImageView extends ImageView implements View.OnTouchListener, ViewTreeObserver.OnGlobalLayoutListener {

  /**
   * Interface definition for a callback to be invoked when the Photo is tapped with a single tap.
   * 
   * @author tomasz.zawada@gmail.com
   */
  public static interface OnPhotoTapListener {
    /**
     * A callback to receive where the user taps on a photo. You will only receive a callback if the user taps on
     * the actual photo, tapping on 'whitespace' will be ignored.
     * 
     * @param view
     *            - View the user tapped.
     * @param x
     *            - where the user tapped from the of the Drawable, as percentage of the Drawable width.
     * @param y
     *            - where the user tapped from the top of the Drawable, as percentage of the Drawable height.
     */
    public void onPhotoTap(View view, float x, float y);
  }

  /**
   * Interface definition for a callback to be invoked when the ImageView is tapped with a single tap.
   * 
   * @author tomasz.zawada@gmail.com
   */
  public static interface OnViewTapListener {
    /**
     * A callback to receive where the user taps on a ImageView. You will receive a callback if the user taps
     * anywhere on the view, tapping on 'whitespace' will not be ignored.
     * 
     * @param view
     *            - View the user tapped.
     * @param x
     *            - where the user tapped from the left of the View.
     * @param y
     *            - where the user tapped from the top of the View.
     */
    public void onViewTap(View view, float x, float y);
  }

  /**
   * 
   * The MultiGestureDetector manages the multi-finger pinch zoom, pan and tap
   * 
   * @author tomasz.zawada@gmail.com
   * 
   */
  private class MultiGestureDetector extends GestureDetector.SimpleOnGestureListener implements
      OnScaleGestureListener {
    private final ScaleGestureDetector scaleGestureDetector;
    private final GestureDetector gestureDetector;

    private VelocityTracker velocityTracker;
    private boolean isDragging;

    private float lastTouchX;
    private float lastTouchY;
    private float lastPointerCount;
    private final float scaledTouchSlop;
    private final float scaledMinimumFlingVelocity;

    public MultiGestureDetector(Context context) {
      scaleGestureDetector = new ScaleGestureDetector(context, this);

      gestureDetector = new GestureDetector(context, this);
      gestureDetector.setOnDoubleTapListener(this);

      final ViewConfiguration configuration = ViewConfiguration.get(context);
      scaledMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
      scaledTouchSlop = configuration.getScaledTouchSlop();
    }

    public boolean isScaling() {
      return scaleGestureDetector.isInProgress();
    }

    public boolean onTouchEvent(MotionEvent event) {
      if (gestureDetector.onTouchEvent(event)) {
        return true;
      }

      scaleGestureDetector.onTouchEvent(event);

      /*
       * Get the center x, y of all the pointers
       */
      float x = 0, y = 0;
      final int pointerCount = event.getPointerCount();
      for (int i = 0; i < pointerCount; i++) {
        x += event.getX(i);
        y += event.getY(i);
      }
      x = x / pointerCount;
      y = y / pointerCount;

      /*
       * If the pointer count has changed cancel the drag
       */
      if (pointerCount != lastPointerCount) {
        isDragging = false;
        if (velocityTracker != null) {
          velocityTracker.clear();
        }
        lastTouchX = x;
        lastTouchY = y;
      }
      lastPointerCount = pointerCount;

      switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        if (velocityTracker == null) {
          velocityTracker = VelocityTracker.obtain();
        } else {
          velocityTracker.clear();
        }
        velocityTracker.addMovement(event);

        lastTouchX = x;
        lastTouchY = y;
        isDragging = false;
        break;

      case MotionEvent.ACTION_MOVE: {
        final float dx = x - lastTouchX, dy = y - lastTouchY;

        if (isDragging == false) {
          // Use Pythagoras to see if drag length is larger than
          // touch slop
          isDragging = Math.sqrt((dx * dx) + (dy * dy)) >= scaledTouchSlop;
        }

        if (isDragging) {
          if (getDrawable() != null) {
            suppMatrix.postTranslate(dx, dy);
            checkAndDisplayMatrix();

            /**
             * Here we decide whether to let the ImageView's parent to start taking over the touch event.
             * 
             * First we check whether this function is enabled. We never want the parent to take over if
             * we're scaling. We then check the edge we're on, and the direction of the scroll (i.e. if
             * we're pulling against the edge, aka 'overscrolling', let the parent take over).
             */
            if (allowParentInterceptOnEdge && !multiGestureDetector.isScaling()) {
              if ((scrollEdge == EDGE_BOTH) || ((scrollEdge == EDGE_LEFT) && (dx >= 1f))
                  || ((scrollEdge == EDGE_RIGHT) && (dx <= -1f))) {

                if (getParent() != null) {
                  getParent().requestDisallowInterceptTouchEvent(false);
                }
              }
            }
          }

          lastTouchX = x;
          lastTouchY = y;

          if (velocityTracker != null) {
            velocityTracker.addMovement(event);
          }
        }
        break;
      }
      case MotionEvent.ACTION_UP: {
        if (isDragging && velocityTracker != null) {
          lastTouchX = x;
          lastTouchY = y;

          velocityTracker.addMovement(event);
          velocityTracker.computeCurrentVelocity(1000);
          final float vX = velocityTracker.getXVelocity(), vY = velocityTracker.getYVelocity();

          // If the velocity is greater than minVelocity perform
          // a fling
          if ((Math.max(Math.abs(vX), Math.abs(vY)) >= scaledMinimumFlingVelocity) && (getDrawable() != null)) {
            currentFlingRunnable = new FlingRunnable(getContext());
            currentFlingRunnable.fling(getWidth(), getHeight(), (int) -vX, (int) -vY);
            post(currentFlingRunnable);
          }
        }
      }
      case MotionEvent.ACTION_CANCEL:
        lastPointerCount = 0;
        if (velocityTracker != null) {
          velocityTracker.recycle();
          velocityTracker = null;
        }
        break;
      }

      return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
      float scale = getScale();
      float scaleFactor = detector.getScaleFactor();

      if ((getDrawable() != null)
          && (!(((scale >= maxScale) && (scaleFactor > 1f)) || ((scale <= 0.75) && (scaleFactor < 1f))))) {
        suppMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
        checkAndDisplayMatrix();
      }

      return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
      return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
      try {
        float scale = getScale();
        float x = event.getX();
        float y = event.getY();

        if (scale < midScale) {
          post(new AnimatedZoomRunnable(scale, midScale, x, y));
        } else if ((scale >= midScale) && (scale < maxScale)) {
          post(new AnimatedZoomRunnable(scale, maxScale, x, y));
        } else {
          post(new AnimatedZoomRunnable(scale, minScale, x, y));
        }
      } catch (Exception e) {
        // Can sometimes happen when getX() and getY() is called
      }

      return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
      // Wait for the confirmed onDoubleTap() instead
      return false;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
      if (photoTapListener != null) {
        final RectF displayRect = getDisplayRect();

        if (null != displayRect) {
          final float x = event.getX(), y = event.getY();

          // Check to see if the user tapped on the photo
          if (displayRect.contains(x, y)) {

            float xResult = (x - displayRect.left) / displayRect.width();
            float yResult = (y - displayRect.top) / displayRect.height();

            photoTapListener.onPhotoTap(ZoomImageView.this, xResult, yResult);
            return true;
          }
        }
      }
      if (viewTapListener != null) {
        viewTapListener.onViewTap(ZoomImageView.this, event.getX(), event.getY());
      }

      return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
      if (longClickListener != null) {
        longClickListener.onLongClick(ZoomImageView.this);
      }
    }
  }

  /**
   * 
   * The ScrollerProxy encapsulates the Scroller and OverScroller classes. OverScroller is available since API 9.
   * 
   * @author tomasz.zawada@gmail.com
   * 
   */
  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
  private class ScrollerProxy {

    private boolean isOld;
    private Object scroller;

    public ScrollerProxy(Context context) {
      if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
        isOld = true;
        scroller = new Scroller(context);
      } else {
        isOld = false;
        scroller = new OverScroller(context);
      }
    }

    public boolean computeScrollOffset() {
      return isOld ? ((Scroller) scroller).computeScrollOffset() : ((OverScroller) scroller)
          .computeScrollOffset();
    }

    public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
        int overX, int overY) {

      if (isOld) {
        ((Scroller) scroller).fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
      } else {
        ((OverScroller) scroller).fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX,
            overY);
      }
    }

    public void forceFinished(boolean finished) {
      if (isOld) {
        ((Scroller) scroller).forceFinished(finished);
      } else {
        ((OverScroller) scroller).forceFinished(finished);
      }
    }

    public int getCurrX() {
      return isOld ? ((Scroller) scroller).getCurrX() : ((OverScroller) scroller).getCurrX();
    }

    public int getCurrY() {
      return isOld ? ((Scroller) scroller).getCurrY() : ((OverScroller) scroller).getCurrY();
    }
  }

  private static final int EDGE_NONE = -1;
  private static final int EDGE_LEFT = 0;
  private static final int EDGE_RIGHT = 1;
  private static final int EDGE_BOTH = 2;

  public static final float DEFAULT_MAX_SCALE = 3.0f;
  public static final float DEFAULT_MID_SCALE = 1.75f;
  public static final float DEFAULT_MIN_SCALE = 1f;

  private float minScale = DEFAULT_MIN_SCALE;
  private float midScale = DEFAULT_MID_SCALE;
  private float maxScale = DEFAULT_MAX_SCALE;

  private boolean allowParentInterceptOnEdge = true;

  private MultiGestureDetector multiGestureDetector;

  // These are set so we don't keep allocating them on the heap
  private final Matrix baseMatrix = new Matrix();
  private final Matrix drawMatrix = new Matrix();
  private final Matrix suppMatrix = new Matrix();
  private final RectF displayRect = new RectF();
  private final float[] matrixValues = new float[9];

  // Listeners
  private OnPhotoTapListener photoTapListener;
  private OnViewTapListener viewTapListener;
  private OnLongClickListener longClickListener;

  private int top, right, bottom, left;
  private FlingRunnable currentFlingRunnable;
  private int scrollEdge = EDGE_BOTH;

  private boolean isZoomEnabled;
  private ScaleType scaleType = ScaleType.FIT_CENTER;

  public ZoomImageView(Context context) {
    this(context, null);
  }

  public ZoomImageView(Context context, AttributeSet attr) {
    this(context, attr, 0);
  }

  public ZoomImageView(Context context, AttributeSet attr, int defStyle) {
    super(context, attr, defStyle);

    super.setScaleType(ScaleType.MATRIX);

    setOnTouchListener(this);

    multiGestureDetector = new MultiGestureDetector(context);

    setIsZoomEnabled(true);
  }

  /**
   * Gets the Display Rectangle of the currently displayed Drawable. The Rectangle is relative to this View and
   * includes all scaling and translations.
   * 
   * @return - RectF of Displayed Drawable
   */
  public final RectF getDisplayRect() {
    checkMatrixBounds();
    return getDisplayRect(getDisplayMatrix());
  }

  /**
   * @return The current minimum scale level. What this value represents depends on the current
   *         {@link android.widget.ImageView.ScaleType}
   */
  public float getMinScale() {
    return minScale;
  }

  /**
   * Sets the minimum scale level. What this value represents depends on the current
   * {@link android.widget.ImageView.ScaleType}.
   */
  public void setMinScale(float minScale) {
    checkZoomLevels(minScale, midScale, maxScale);
    this.minScale = minScale;
  }

  /**
   * @return The current middle scale level. What this value represents depends on the current
   *         {@link android.widget.ImageView.ScaleType}
   */
  public float getMidScale() {
    return midScale;
  }

  /**
   * Sets the middle scale level. What this value represents depends on the current
   * {@link android.widget.ImageView.ScaleType}.
   */
  public void setMidScale(float midScale) {
    checkZoomLevels(minScale, midScale, maxScale);
    this.midScale = midScale;
  }

  /**
   * @return The current maximum scale level. What this value represents depends on the current
   *         {@link android.widget.ImageView.ScaleType}
   */
  public float getMaxScale() {
    return maxScale;
  }

  /**
   * Sets the maximum scale level. What this value represents depends on the current
   * {@link android.widget.ImageView.ScaleType}.
   */
  public void setMaxScale(float maxScale) {
    checkZoomLevels(minScale, midScale, maxScale);
    this.maxScale = maxScale;
  }

  /**
   * Returns the current scale value
   * 
   * @return float - current scale value
   */
  public final float getScale() {
    suppMatrix.getValues(matrixValues);
    return matrixValues[Matrix.MSCALE_X];
  }

  /**
   * Return the current scale type in use by the ImageView.
   */
  @Override
  public final ScaleType getScaleType() {
    return scaleType;
  }

  /**
   * Controls how the image should be resized or moved to match the size of the ImageView. Any scaling or panning will
   * happen within the confines of this {@link android.widget.ImageView.ScaleType}.
   * 
   * @param scaleType
   *            - The desired scaling mode.
   */
  @Override
  public final void setScaleType(ScaleType scaleType) {
    if (scaleType == ScaleType.MATRIX) {
      throw new IllegalArgumentException(scaleType.name() + " is not supported in ZoomImageView");
    }

    if (scaleType != this.scaleType) {
      this.scaleType = scaleType;
      update();
    }
  }

  /**
   * Returns true if the ZoomImageView is set to allow zooming of Photos.
   * 
   * @return true if the ZoomImageView allows zooming.
   */
  public final boolean isZoomEnabled() {
    return isZoomEnabled;
  }

  /**
   * Allows you to enable/disable the zoom functionality on the ImageView. When disable the ImageView reverts to using
   * the FIT_CENTER matrix.
   * 
   * @param isZoomEnabled
   *            - Whether the zoom functionality is enabled.
   */
  public final void setIsZoomEnabled(boolean isZoomEnabled) {
    this.isZoomEnabled = isZoomEnabled;
    update();
  }

  /**
   * Whether to allow the ImageView's parent to intercept the touch event when the photo is scroll to it's horizontal
   * edge.
   */
  public void setAllowParentInterceptOnEdge(boolean allowParentInterceptOnEdge) {
    this.allowParentInterceptOnEdge = allowParentInterceptOnEdge;
  }

  @Override
  public void setImageBitmap(Bitmap bitmap) {
    super.setImageBitmap(bitmap);
    update();
  }

  @Override
  public void setImageDrawable(Drawable drawable) {
    super.setImageDrawable(drawable);
    update();
  }

  @Override
  public void setImageResource(int resId) {
    super.setImageResource(resId);
    update();
  }

  @Override
  public void setImageURI(Uri uri) {
    super.setImageURI(uri);
    update();
  }

  /**
   * Register a callback to be invoked when the Photo displayed by this view is long-pressed.
   * 
   * @param listener
   *            - Listener to be registered.
   */
  @Override
  public final void setOnLongClickListener(OnLongClickListener listener) {
    longClickListener = listener;
  }

  /**
   * Register a callback to be invoked when the Photo displayed by this View is tapped with a single tap.
   * 
   * @param listener
   *            - Listener to be registered.
   */
  public final void setOnPhotoTapListener(OnPhotoTapListener listener) {
    photoTapListener = listener;
  }

  /**
   * Register a callback to be invoked when the View is tapped with a single tap.
   * 
   * @param listener
   *            - Listener to be registered.
   */
  public final void setOnViewTapListener(OnViewTapListener listener) {
    viewTapListener = listener;
  }

  @Override
  public final void onGlobalLayout() {
    if (isZoomEnabled) {
      final int top = getTop();
      final int right = getRight();
      final int bottom = getBottom();
      final int left = getLeft();

      /**
       * We need to check whether the ImageView's bounds have changed. This would be easier if we targeted API 11+
       * as we could just use View.OnLayoutChangeListener. Instead we have to replicate the work, keeping track of
       * the ImageView's bounds and then checking if the values change.
       */
      if ((top != this.top) || (bottom != this.bottom) || (left != this.left) || (right != this.right)) {
        // Update our base matrix, as the bounds have changed
        updateBaseMatrix(getDrawable());

        // Update values as something has changed
        this.top = top;
        this.right = right;
        this.bottom = bottom;
        this.left = left;
      }
    }
  }

  @Override
  public final boolean onTouch(View v, MotionEvent ev) {
    boolean handled = false;

    if (isZoomEnabled) {
      switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        // First, disable the Parent from intercepting the touch
        // event
        if (v.getParent() != null) {
          v.getParent().requestDisallowInterceptTouchEvent(true);
        }

        // If we're flinging, and the user presses down, cancel
        // fling
        if (currentFlingRunnable != null) {
          currentFlingRunnable.cancelFling();
          currentFlingRunnable = null;
        }
        break;

      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        // If the user has zoomed less than min scale, zoom back
        // to min scale
        if (getScale() < minScale) {
          RectF rect = getDisplayRect();
          if (null != rect) {
            v.post(new AnimatedZoomRunnable(getScale(), minScale, rect.centerX(), rect.centerY()));
            handled = true;
          }
        }
        break;
      }

      // Finally, try the scale/drag/tap detector
      if ((multiGestureDetector != null) && multiGestureDetector.onTouchEvent(ev)) {
        handled = true;
      }
    }

    return handled;
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    getViewTreeObserver().addOnGlobalLayoutListener(this);
  }

  @SuppressWarnings("deprecation")
  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();

    getViewTreeObserver().removeGlobalOnLayoutListener(this);
  }

  protected Matrix getDisplayMatrix() {
    drawMatrix.set(baseMatrix);
    drawMatrix.postConcat(suppMatrix);
    return drawMatrix;
  }

  private final void update() {
    if (isZoomEnabled) {
      super.setScaleType(ScaleType.MATRIX);
      updateBaseMatrix(getDrawable());
    } else {
      resetMatrix();
    }
  }

  /**
   * Helper method that simply checks the Matrix, and then displays the result
   */
  private void checkAndDisplayMatrix() {
    checkMatrixBounds();
    setImageMatrix(getDisplayMatrix());
  }

  private void checkMatrixBounds() {
    final RectF rect = getDisplayRect(getDisplayMatrix());
    if (null == rect) {
      return;
    }

    final float height = rect.height(), width = rect.width();
    float deltaX = 0, deltaY = 0;

    final int viewHeight = getHeight();
    if (height <= viewHeight) {
      switch (scaleType) {
      case FIT_START:
        deltaY = -rect.top;
        break;
      case FIT_END:
        deltaY = viewHeight - height - rect.top;
        break;
      default:
        deltaY = ((viewHeight - height) / 2) - rect.top;
        break;
      }
    } else if (rect.top > 0) {
      deltaY = -rect.top;
    } else if (rect.bottom < viewHeight) {
      deltaY = viewHeight - rect.bottom;
    }

    final int viewWidth = getWidth();
    if (width <= viewWidth) {
      switch (scaleType) {
      case FIT_START:
        deltaX = -rect.left;
        break;
      case FIT_END:
        deltaX = viewWidth - width - rect.left;
        break;
      default:
        deltaX = ((viewWidth - width) / 2) - rect.left;
        break;
      }
      scrollEdge = EDGE_BOTH;
    } else if (rect.left > 0) {
      scrollEdge = EDGE_LEFT;
      deltaX = -rect.left;
    } else if (rect.right < viewWidth) {
      deltaX = viewWidth - rect.right;
      scrollEdge = EDGE_RIGHT;
    } else {
      scrollEdge = EDGE_NONE;
    }

    // Finally actually translate the matrix
    suppMatrix.postTranslate(deltaX, deltaY);
  }

  /**
   * 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) {
    Drawable d = getDrawable();
    if (null != d) {
      displayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
      matrix.mapRect(displayRect);
      return displayRect;
    }

    return null;
  }

  /**
   * Resets the Matrix back to FIT_CENTER, and then displays it.s
   */
  private void resetMatrix() {
    suppMatrix.reset();
    setImageMatrix(getDisplayMatrix());
    checkMatrixBounds();
  }

  /**
   * Calculate Matrix for FIT_CENTER
   * 
   * @param d
   *            - Drawable being displayed
   */
  private void updateBaseMatrix(Drawable d) {
    if (null == d) {
      return;
    }

    final float viewWidth = getWidth();
    final float viewHeight = getHeight();
    final int drawableWidth = d.getIntrinsicWidth();
    final int drawableHeight = d.getIntrinsicHeight();

    baseMatrix.reset();

    final float widthScale = viewWidth / drawableWidth;
    final float heightScale = viewHeight / drawableHeight;

    if (scaleType == ScaleType.CENTER) {
      baseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, (viewHeight - drawableHeight) / 2F);

    } else if (scaleType == ScaleType.CENTER_CROP) {
      float scale = Math.max(widthScale, heightScale);
      baseMatrix.postScale(scale, scale);
      baseMatrix.postTranslate((viewWidth - (drawableWidth * scale)) / 2F,
          (viewHeight - (drawableHeight * scale)) / 2F);

    } else if (scaleType == ScaleType.CENTER_INSIDE) {
      float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
      baseMatrix.postScale(scale, scale);
      baseMatrix.postTranslate((viewWidth - (drawableWidth * scale)) / 2F,
          (viewHeight - (drawableHeight * scale)) / 2F);

    } else {
      RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
      RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);

      switch (scaleType) {
      case FIT_CENTER:
        baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
        break;

      case FIT_START:
        baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
        break;

      case FIT_END:
        baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
        break;

      case FIT_XY:
        baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
        break;

      default:
        break;
      }
    }

    resetMatrix();
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void postOnAnimation(View view, Runnable runnable) {
    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
      view.postOnAnimation(runnable);
    } else {
      view.postDelayed(runnable, 16);
    }
  }

  private void checkZoomLevels(float minZoom, float midZoom, float maxZoom) {
    if (minZoom >= midZoom) {
      throw new IllegalArgumentException("MinZoom should be less than MidZoom");
    } else if (midZoom >= maxZoom) {
      throw new IllegalArgumentException("MidZoom should be less than MaxZoom");
    }
  }

  private class AnimatedZoomRunnable implements Runnable {
    // These are 'postScale' values, means they're compounded each iteration
    static final float ANIMATION_SCALE_PER_ITERATION_IN = 1.07f;
    static final float ANIMATION_SCALE_PER_ITERATION_OUT = 0.93f;

    private final float focalX, focalY;
    private final float targetZoom;
    private final float deltaScale;

    public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX,
        final float focalY) {
      this.targetZoom = targetZoom;
      this.focalX = focalX;
      this.focalY = focalY;

      if (currentZoom < targetZoom) {
        deltaScale = ANIMATION_SCALE_PER_ITERATION_IN;
      } else {
        deltaScale = ANIMATION_SCALE_PER_ITERATION_OUT;
      }
    }

    public void run() {
      suppMatrix.postScale(deltaScale, deltaScale, focalX, focalY);
      checkAndDisplayMatrix();

      final float currentScale = getScale();

      if (((deltaScale > 1f) && (currentScale < targetZoom))
          || ((deltaScale < 1f) && (targetZoom < currentScale))) {
        // We haven't hit our target scale yet, so post ourselves
        // again
        postOnAnimation(ZoomImageView.this, this);

      } else {
        // We've scaled past our target zoom, so calculate the
        // necessary scale so we're back at target zoom
        final float delta = targetZoom / currentScale;
        suppMatrix.postScale(delta, delta, focalX, focalY);
        checkAndDisplayMatrix();
      }
    }
  }

  private class FlingRunnable implements Runnable {
    private final ScrollerProxy scroller;
    private int currentX, currentY;

    public FlingRunnable(Context context) {
      scroller = new ScrollerProxy(context);
    }

    public void cancelFling() {
      scroller.forceFinished(true);
    }

    public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
      final RectF rect = getDisplayRect();
      if (null == rect) {
        return;
      }

      final int startX = Math.round(-rect.left);
      final int minX, maxX, minY, maxY;

      if (viewWidth < rect.width()) {
        minX = 0;
        maxX = Math.round(rect.width() - viewWidth);
      } else {
        minX = maxX = startX;
      }

      final int startY = Math.round(-rect.top);
      if (viewHeight < rect.height()) {
        minY = 0;
        maxY = Math.round(rect.height() - viewHeight);
      } else {
        minY = maxY = startY;
      }

      currentX = startX;
      currentY = startY;

      // If we actually can move, fling the scroller
      if ((startX != maxX) || (startY != maxY)) {
        scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
      }
    }

    @Override
    public void run() {
      if (scroller.computeScrollOffset()) {
        final int newX = scroller.getCurrX();
        final int newY = scroller.getCurrY();

        suppMatrix.postTranslate(currentX - newX, currentY - newY);
        setImageMatrix(getDisplayMatrix());

        currentX = newX;
        currentY = newY;

        // Post On animation
        postOnAnimation(ZoomImageView.this, this);
      }
    }
  }
}




Java Source Code List

lecho.app.campus.activity.AboutAppActivity.java
lecho.app.campus.activity.CampusMapActivity.java
lecho.app.campus.activity.GalleryActivity.java
lecho.app.campus.activity.LegalInfoActivity.java
lecho.app.campus.activity.PlaceDetailsActivity.java
lecho.app.campus.adapter.GalleryPagesFragmentAdapter.java
lecho.app.campus.adapter.MarkerInfoWindowAdapter.java
lecho.app.campus.adapter.NavigationDrawerAdapter.java
lecho.app.campus.adapter.PlaceDetailsFragmentAdapter.java
lecho.app.campus.adapter.SearchResultViewAdapter.java
lecho.app.campus.adapter.SearchSuggestionAdapter.java
lecho.app.campus.dao.CategoryDao.java
lecho.app.campus.dao.CategoryDao.java
lecho.app.campus.dao.Category.java
lecho.app.campus.dao.Category.java
lecho.app.campus.dao.DaoMaster.java
lecho.app.campus.dao.DaoMaster.java
lecho.app.campus.dao.DaoSession.java
lecho.app.campus.dao.DaoSession.java
lecho.app.campus.dao.FacultyDao.java
lecho.app.campus.dao.FacultyDao.java
lecho.app.campus.dao.Faculty.java
lecho.app.campus.dao.Faculty.java
lecho.app.campus.dao.Main.java
lecho.app.campus.dao.PlaceCategoryDao.java
lecho.app.campus.dao.PlaceCategoryDao.java
lecho.app.campus.dao.PlaceCategory.java
lecho.app.campus.dao.PlaceCategory.java
lecho.app.campus.dao.PlaceDao.java
lecho.app.campus.dao.PlaceDao.java
lecho.app.campus.dao.PlaceFacultyDao.java
lecho.app.campus.dao.PlaceFacultyDao.java
lecho.app.campus.dao.PlaceFaculty.java
lecho.app.campus.dao.PlaceFaculty.java
lecho.app.campus.dao.PlaceUnitDao.java
lecho.app.campus.dao.PlaceUnitDao.java
lecho.app.campus.dao.PlaceUnit.java
lecho.app.campus.dao.PlaceUnit.java
lecho.app.campus.dao.Place.java
lecho.app.campus.dao.Place.java
lecho.app.campus.dao.UnitDao.java
lecho.app.campus.dao.UnitDao.java
lecho.app.campus.dao.Unit.java
lecho.app.campus.dao.Unit.java
lecho.app.campus.fragment.GalleryPageFragment.java
lecho.app.campus.fragment.PlaceDetailsFragment.java
lecho.app.campus.fragment.dialog.NoInternetConnectionDialogFragment.java
lecho.app.campus.fragment.dialog.PlayServicesErrorDialogFragment.java
lecho.app.campus.loader.PlaceDetailsLoader.java
lecho.app.campus.loader.PlacesLoader.java
lecho.app.campus.provider.SearchSuggestionProvider.java
lecho.app.campus.service.PopulateDBIntentService.java
lecho.app.campus.utils.ABSMenuItemConverter.java
lecho.app.campus.utils.BitmapAsyncTask.java
lecho.app.campus.utils.Config.java
lecho.app.campus.utils.DataParser.java
lecho.app.campus.utils.DatabaseHelper.java
lecho.app.campus.utils.ImagesDirAsyncTask.java
lecho.app.campus.utils.NavigationDrawerItem.java
lecho.app.campus.utils.PlaceDetails.java
lecho.app.campus.utils.PlacesList.java
lecho.app.campus.utils.UnitsGroup.java
lecho.app.campus.utils.Utils.java
lecho.app.campus.view.CheckableDrawerItem.java
lecho.app.campus.view.UnitsGroupLayout.java
lecho.app.campus.view.ZoomImageView.java
lecho.app.campus.view.ZoomViewPager.java