Android 自定义imageview实现图片缩放实例详解

Android 自定义imageview实现图片缩放实例详解

觉得这个自定义的imageview很好用 性能不错  所以拿出来分享给大家  因为不会做gif图  所以项目效果 就不好贴出来了  把代码贴出来

1.项目结构图

2.Compat.class

package com.suo.image; 

import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View; 

public class Compat { 

 private static final int SIXTY_FPS_INTERVAL = 1000 / 60; 

 public static void postOnAnimation(View view, Runnable runnable) {
  if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
   SDK16.postOnAnimation(view, runnable);
  } else {
   view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
  }
 } 

}

3.HackyViewPager.class

package com.suo.image; 

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent; 

/**
 * Hacky fix for Issue #4 and
 * http://code.google.com/p/android/issues/detail?id=18990
 *
 * ScaleGestureDetector seems to mess up the touch events, which means that
 * ViewGroups which make use of onInterceptTouchEvent throw a lot of
 * IllegalArgumentException: pointerIndex out of range.
 *
 * There's not much I can do in my code for now, but we can mask the result by
 * just catching the problem and ignoring it.
 *
 * @author Chris Banes
 */
public class HackyViewPager extends ViewPager { 

 public HackyViewPager(Context context) {
  super(context);
 } 

 public HackyViewPager(Context context, AttributeSet attrs) {
  super(context, attrs);
  // TODO Auto-generated constructor stub
 } 

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  try {
   return super.onInterceptTouchEvent(ev);
  } catch (IllegalArgumentException e) {
   e.printStackTrace();
   return false;
  }
 } 

}

4.IScaleView.class

/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.suo.image; 

import android.graphics.RectF;
import android.view.View;
import android.widget.ImageView; 

public interface IScaleView {
 /**
  * Returns true if the ScaleView is set to allow zooming of Scales.
  *
  * @return true if the ScaleView allows zooming.
  */
 boolean canZoom(); 

 /**
  * 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
  */
 RectF getDisplayRect(); 

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

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

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

 /**
  * Returns the current scale value
  *
  * @return float - current scale value
  */
 float getScale(); 

 /**
  * Return the current scale type in use by the ImageView.
  */
 ImageView.ScaleType getScaleType(); 

 /**
  * Whether to allow the ImageView's parent to intercept the touch event when the Scale is scroll to it's horizontal edge.
  */
 void setAllowParentInterceptOnEdge(boolean allow); 

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

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

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

 /**
  * Register a callback to be invoked when the Scale displayed by this view is long-pressed.
  *
  * @param listener - Listener to be registered.
  */
 void setOnLongClickListener(View.OnLongClickListener listener); 

 /**
  * Register a callback to be invoked when the Matrix has changed for this
  * View. An example would be the user panning or scaling the Scale.
  *
  * @param listener - Listener to be registered.
  */
 void setOnMatrixChangeListener(ScaleViewAttacher.OnMatrixChangedListener listener); 

 /**
  * Register a callback to be invoked when the Scale displayed by this View
  * is tapped with a single tap.
  *
  * @param listener - Listener to be registered.
  */
 void setOnScaleTapListener(ScaleViewAttacher.OnScaleTapListener listener); 

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

 /**
  * 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.
  */
 void setScaleType(ImageView.ScaleType scaleType); 

 /**
  * Allows you to enable/disable the zoom functionality on the ImageView.
  * When disable the ImageView reverts to using the FIT_CENTER matrix.
  *
  * @param zoomable - Whether the zoom functionality is enabled.
  */
 void setZoomable(boolean zoomable); 

 /**
  * Zooms to the specified scale, around the focal point given.
  *
  * @param scale - Scale to zoom to
  * @param focalX - X Focus Point
  * @param focalY - Y Focus Point
  */
 void zoomTo(float scale, float focalX, float focalY);
}

5.ScaleView

/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.suo.image; 

import com.suo.image.ScaleViewAttacher.OnMatrixChangedListener;
import com.suo.image.ScaleViewAttacher.OnScaleTapListener;
import com.suo.image.ScaleViewAttacher.OnViewTapListener; 

import android.content.Context;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView; 

public class ScaleView extends ImageView implements IScaleView { 

 @SuppressWarnings("unused")
 private static final String TAG = "ScaleView";
 private final ScaleViewAttacher mAttacher; 

 private ScaleType mPendingScaleType; 

 public ScaleView(Context context) {
  this(context, null);
  setZoomable(false);
 } 

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

 public ScaleView(Context context, AttributeSet attr, int defStyle) {
  super(context, attr, defStyle);
  super.setScaleType(ScaleType.MATRIX);
  mAttacher = new ScaleViewAttacher(this); 

  if (null != mPendingScaleType) {
   setScaleType(mPendingScaleType);
   mPendingScaleType = null;
  }
 } 

 public void setOnClickListener(OnClickListener listener){
  mAttacher.setOnClickLinstener(listener);
 } 

 @Override
 public boolean canZoom() {
  return mAttacher.canZoom();
 } 

 @Override
 public RectF getDisplayRect() {
  return mAttacher.getDisplayRect();
 } 

 @Override
 public float getMinScale() {
  return mAttacher.getMinScale();
 } 

 @Override
 public float getMidScale() {
  return mAttacher.getMidScale();
 } 

 @Override
 public float getMaxScale() {
  return mAttacher.getMaxScale();
 } 

 @Override
 public float getScale() {
  return mAttacher.getScale();
 } 

 @Override
 public ScaleType getScaleType() {
  return mAttacher.getScaleType();
 } 

 @Override
 public void setAllowParentInterceptOnEdge(boolean allow) {
  mAttacher.setAllowParentInterceptOnEdge(allow);
 } 

 @Override
 public void setMinScale(float minScale) {
  mAttacher.setMinScale(minScale);
 } 

 @Override
 public void setMidScale(float midScale) {
  mAttacher.setMidScale(midScale);
 } 

 @Override
 public void setMaxScale(float maxScale) {
  mAttacher.setMaxScale(maxScale);
 } 

 @Override
 // setImageBitmap calls through to this method
 public void setImageDrawable(Drawable drawable) {
  super.setImageDrawable(drawable);
  if (null != mAttacher) {
   mAttacher.update();
  }
 } 

 @Override
 public void setImageResource(int resId) {
  super.setImageResource(resId);
  if (null != mAttacher) {
   mAttacher.update();
  }
 } 

 @Override
 public void setImageURI(Uri uri) {
  super.setImageURI(uri);
  if (null != mAttacher) {
   mAttacher.update();
  }
 } 

 @Override
 public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
  mAttacher.setOnMatrixChangeListener(listener);
 } 

 @Override
 public void setOnLongClickListener(OnLongClickListener l) {
  mAttacher.setOnLongClickListener(l);
 } 

 @Override
 public void setOnScaleTapListener(OnScaleTapListener listener) {
  mAttacher.setOnScaleTapListener(listener);
 } 

 @Override
 public void setOnViewTapListener(OnViewTapListener listener) {
  mAttacher.setOnViewTapListener(listener);
 } 

 @Override
 public void setScaleType(ScaleType scaleType) {
  if (null != mAttacher) {
   mAttacher.setScaleType(scaleType);
  } else {
   mPendingScaleType = scaleType;
  }
 } 

 @Override
 public void setZoomable(boolean zoomable) {
  mAttacher.setZoomable(zoomable);
 } 

 @Override
 public void zoomTo(float scale, float focalX, float focalY) {
  mAttacher.zoomTo(scale, focalX, focalY);
 } 

 @Override
 protected void onDetachedFromWindow() {
  mAttacher.cleanup();
  super.onDetachedFromWindow();
 } 

}

6.ScaleViewAttacher  这个是最关键的

/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.suo.image; 

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType; 

import java.lang.ref.WeakReference; 

public class ScaleViewAttacher implements IScaleView, View.OnTouchListener, VersionedGestureDetector.OnGestureListener,
  GestureDetector.OnDoubleTapListener, ViewTreeObserver.OnGlobalLayoutListener { 

 static final String LOG_TAG = "ScaleViewAttacher"; 

 // let debug flag be dynamic, but still Proguard can be used to remove from release builds
 static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); 

 static final int EDGE_NONE = -1;
 static final int EDGE_LEFT = 0;
 static final int EDGE_RIGHT = 1;
 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 = 1.0f; 

 private float mMinScale = DEFAULT_MIN_SCALE;
 private float mMidScale = DEFAULT_MID_SCALE;
 private float mMaxScale = DEFAULT_MAX_SCALE; 

 private boolean mAllowParentInterceptOnEdge = true; 

 private static 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");
  }
 } 

 /**
  * @return true if the ImageView exists, and it's Drawable existss
  */
 private static boolean hasDrawable(ImageView imageView) {
  return null != imageView && null != imageView.getDrawable();
 } 

 /**
  * @return true if the ScaleType is supported.
  */
 private static boolean isSupportedScaleType(final ScaleType scaleType) {
  if (null == scaleType) {
   return false;
  } 

  switch (scaleType) {
   case MATRIX:
    throw new IllegalArgumentException(scaleType.name() + " is not supported in ScaleView"); 

   default:
    return true;
  }
 } 

 /**
  * Set's the ImageView's ScaleType to Matrix.
  */
 private static void setImageViewScaleTypeMatrix(ImageView imageView) {
  if (null != imageView) {
   if (imageView instanceof ScaleView) {
    /**
     * ScaleView sets it's own ScaleType to Matrix, then diverts all
     * calls setScaleType to this.setScaleType. Basically we don't
     * need to do anything here
     */
   } else {
    imageView.setScaleType(ScaleType.MATRIX);
   }
  }
 } 

 private WeakReference<ImageView> mImageView;
 private ViewTreeObserver mViewTreeObserver; 

 // Gesture Detectors
 private GestureDetector mGestureDetector;
 private VersionedGestureDetector mScaleDragDetector; 

 // These are set so we don't keep allocating them on the heap
 private final Matrix mBaseMatrix = new Matrix();
 private final Matrix mDrawMatrix = new Matrix();
 private final Matrix mSuppMatrix = new Matrix();
 private final RectF mDisplayRect = new RectF();
 private final float[] mMatrixValues = new float[9]; 

 // Listeners
 private OnMatrixChangedListener mMatrixChangeListener;
 private OnScaleTapListener mScaleTapListener;
 private OnViewTapListener mViewTapListener;
 private OnLongClickListener mLongClickListener; 

 private int mIvTop, mIvRight, mIvBottom, mIvLeft;
 private FlingRunnable mCurrentFlingRunnable;
 private int mScrollEdge = EDGE_BOTH; 

 private boolean mZoomEnabled;
 private ScaleType mScaleType = ScaleType.FIT_CENTER;
 private OnClickListener onClickListener; 

 public ScaleViewAttacher(ImageView imageView) {
  mImageView = new WeakReference<ImageView>(imageView); 

  imageView.setOnTouchListener(this); 

  mViewTreeObserver = imageView.getViewTreeObserver();
  if (mViewTreeObserver != null) {
   mViewTreeObserver.addOnGlobalLayoutListener(this);
  }
  onClickListener = null; 

  // Make sure we using MATRIX Scale Type
  setImageViewScaleTypeMatrix(imageView); 

  if (!imageView.isInEditMode()) {
   // Create Gesture Detectors...
   mScaleDragDetector = VersionedGestureDetector.newInstance(imageView.getContext(), this); 

   mGestureDetector = new GestureDetector(imageView.getContext(),
     new GestureDetector.SimpleOnGestureListener() { 

      // forward long click listener
      @Override
      public void onLongPress(MotionEvent e) {
       if(null != mLongClickListener) {
        mLongClickListener.onLongClick(mImageView.get());
       }
      }}); 

   mGestureDetector.setOnDoubleTapListener(this); 

   // Finally, update the UI so that we're zoomable
   setZoomable(true);
  }
 } 

 @Override
 public final boolean canZoom() {
  return mZoomEnabled;
 } 

 /**
  * Clean-up the resources attached to this object. This needs to be called
  * when the ImageView is no longer used. A good example is from
  * {@link android.view.View#onDetachedFromWindow()} or from {@link android.app.Activity#onDestroy()}.
  * This is automatically called if you are using {@link ScaleView.co.senab.Scaleview.ScaleView}.
  */
 @SuppressWarnings("deprecation")
 public final void cleanup() {
  if (null != mImageView) {
   android.view.ViewTreeObserver obs = mImageView.get().getViewTreeObserver();
   if (obs != null) {
    obs.removeGlobalOnLayoutListener(this);
   }
  }
  mViewTreeObserver = null; 

  // Clear listeners too
  mMatrixChangeListener = null;
  mScaleTapListener = null;
  mViewTapListener = null; 

  // Finally, clear ImageView
  mImageView = null;
 } 

 @Override
 public final RectF getDisplayRect() {
  checkMatrixBounds();
  return getDisplayRect(getDisplayMatrix());
 } 

 public final ImageView getImageView() {
  ImageView imageView = null; 

  if (null != mImageView) {
   imageView = mImageView.get();
  } 

  // If we don't have an ImageView, call cleanup()
  if (null == imageView) {
   cleanup();
//   throw new IllegalStateException(
//     "ImageView no longer exists. You should not use this ScaleViewAttacher any more.");
  } 

  return imageView;
 } 

 @Override
 public float getMinScale() {
  return mMinScale;
 } 

 @Override
 public float getMidScale() {
  return mMidScale;
 } 

 @Override
 public float getMaxScale() {
  return mMaxScale;
 } 

 @Override
 public final float getScale() {
  return getValue(mSuppMatrix, Matrix.MSCALE_X);
 } 

 @Override
 public final ScaleType getScaleType() {
  return mScaleType;
 } 

 public final boolean onDoubleTap(MotionEvent ev) {
  try {
   float scale = getScale();
   float x = ev.getX();
   float y = ev.getY(); 

   if (scale < mMidScale) {
    zoomTo(mMidScale, x, y);
   } else if (scale >= mMidScale && scale < mMaxScale) {
    zoomTo(mMaxScale, x, y);
   } else {
    zoomTo(mMinScale, x, y);
   }
  } catch (ArrayIndexOutOfBoundsException e) {
   // Can sometimes happen when getX() and getY() is called
  } 

  return true;
 } 

 public final boolean onDoubleTapEvent(MotionEvent e) {
  // Wait for the confirmed onDoubleTap() instead
  return false;
 } 

 public final void onDrag(float dx, float dy) {
  if (DEBUG) {
   Log.d(LOG_TAG, String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
  } 

  ImageView imageView = getImageView(); 

  if (null != imageView && hasDrawable(imageView)) {
   mSuppMatrix.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 (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling()) {
    if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f)
      || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
      android.view.ViewParent vParent = imageView.getParent();
     if (vParent != null) {
      vParent.requestDisallowInterceptTouchEvent(false);
     }
    }
   }
  }
 } 

 @Override
 public final void onFling(float startX, float startY, float velocityX, float velocityY) {
  if (DEBUG) {
   Log.d(LOG_TAG, "onFling. sX: " + startX + " sY: " + startY + " Vx: " + velocityX + " Vy: " + velocityY);
  } 

  ImageView imageView = getImageView();
  if (hasDrawable(imageView)) {
   mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
   mCurrentFlingRunnable.fling(imageView.getWidth(), imageView.getHeight(), (int) velocityX, (int) velocityY);
   imageView.post(mCurrentFlingRunnable);
  }
 } 

 @Override
 public final void onGlobalLayout() {
  ImageView imageView = getImageView(); 

  if (null != imageView && mZoomEnabled) {
   final int top = imageView.getTop();
   final int right = imageView.getRight();
   final int bottom = imageView.getBottom();
   final int left = imageView.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 != mIvTop || bottom != mIvBottom || left != mIvLeft || right != mIvRight) {
    // Update our base matrix, as the bounds have changed
    updateBaseMatrix(imageView.getDrawable()); 

    // Update values as something has changed
    mIvTop = top;
    mIvRight = right;
    mIvBottom = bottom;
    mIvLeft = left;
   }
  }
 } 

 public final void setOnClickLinstener(OnClickListener listener){
  onClickListener = listener;
 } 

 public final void onScale(float scaleFactor, float focusX, float focusY) {
  if (DEBUG) {
   Log.d(LOG_TAG, String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f", scaleFactor, focusX, focusY));
  } 

  if (hasDrawable(getImageView()) && (getScale() < mMaxScale || scaleFactor < 1f)) {
   mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
   checkAndDisplayMatrix();
  }
 } 

 public final boolean onSingleTapConfirmed(MotionEvent e) {
  ImageView imageView = getImageView(); 

  if (null != imageView) {
   if (null != mScaleTapListener) {
    final RectF displayRect = getDisplayRect(); 

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

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

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

      mScaleTapListener.onScaleTap(imageView, xResult, yResult);
      return true;
     }
    }
   }
   if (null != mViewTapListener) {
    mViewTapListener.onViewTap(imageView, e.getX(), e.getY());
   }
  } 

  return false;
 } 

 private float lastPosX, lastPosY;
 private long firClick = 0; 

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

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

     lastPosX = ev.getX();
     lastPosY = ev.getY();
     // If we're flinging, and the user presses down, cancel
     // fling
     cancelFling();
     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() < mMinScale) {
      RectF rect = getDisplayRect();
      if (null != rect) {
       v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY()));
       handled = true;
      }
     } 

     if(ev.getX() == lastPosX && ev.getY() == lastPosY){
      long time = System.currentTimeMillis();
      if(time - firClick > 500){
       firClick = System.currentTimeMillis();
       if(onClickListener != null){
        onClickListener.onClick(getImageView());
       }
      }
     }
     break;
   } 

   // Check to see if the user double tapped
   if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
    handled = true;
   } 

   // Finally, try the Scale/Drag detector
   if (null != mScaleDragDetector && mScaleDragDetector.onTouchEvent(ev)) {
    handled = true;
   }
  } 

  return handled;
 } 

 @Override
 public void setAllowParentInterceptOnEdge(boolean allow) {
  mAllowParentInterceptOnEdge = allow;
 } 

 @Override
 public void setMinScale(float minScale) {
  checkZoomLevels(minScale, mMidScale, mMaxScale);
  mMinScale = minScale;
 } 

 @Override
 public void setMidScale(float midScale) {
  checkZoomLevels(mMinScale, midScale, mMaxScale);
  mMidScale = midScale;
 } 

 @Override
 public void setMaxScale(float maxScale) {
  checkZoomLevels(mMinScale, mMidScale, maxScale);
  mMaxScale = maxScale;
 } 

 @Override
 public final void setOnLongClickListener(OnLongClickListener listener) {
  mLongClickListener = listener;
 } 

 @Override
 public final void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
  mMatrixChangeListener = listener;
 } 

 @Override
 public final void setOnScaleTapListener(OnScaleTapListener listener) {
  mScaleTapListener = listener;
 } 

 @Override
 public final void setOnViewTapListener(OnViewTapListener listener) {
  mViewTapListener = listener;
 } 

 @Override
 public final void setScaleType(ScaleType scaleType) {
  if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
//   mScaleType = scaleType; 

   // Finally update
   update();
  }
 } 

 @Override
 public final void setZoomable(boolean zoomable) {
  mZoomEnabled = zoomable;
  update();
 } 

 public final void update() {
  ImageView imageView = getImageView(); 

  if (null != imageView) {
   if (mZoomEnabled) {
    // Make sure we using MATRIX Scale Type
    setImageViewScaleTypeMatrix(imageView); 

    // Update the base matrix using the current drawable
    updateBaseMatrix(imageView.getDrawable());
   } else {
    // Reset the Matrix...
    resetMatrix();
   }
  }
 } 

 @Override
 public final void zoomTo(float scale, float focalX, float focalY) {
  ImageView imageView = getImageView(); 

  if (null != imageView) {
   imageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY));
  }
 } 

 protected Matrix getDisplayMatrix() {
  mDrawMatrix.set(mBaseMatrix);
  mDrawMatrix.postConcat(mSuppMatrix);
  return mDrawMatrix;
 } 

 private void cancelFling() {
  if (null != mCurrentFlingRunnable) {
   mCurrentFlingRunnable.cancelFling();
   mCurrentFlingRunnable = null;
  }
 } 

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

 private void checkImageViewScaleType() {
  ImageView imageView = getImageView(); 

  /**
   * ScaleView's getScaleType() will just divert to this.getScaleType() so
   * only call if we're not attached to a ScaleView.
   */
  if (null != imageView && !(imageView instanceof ScaleView)) {
   if (imageView.getScaleType() != ScaleType.MATRIX) {
    throw new IllegalStateException(
      "The ImageView's ScaleType has been changed since attaching a ScaleViewAttacher");
   }
  }
 } 

 private void checkMatrixBounds() {
  final ImageView imageView = getImageView();
  if (null == imageView) {
   return;
  } 

  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 = imageView.getHeight();
  if (height <= viewHeight) {
   switch (mScaleType) {
    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 = imageView.getWidth();
  if (width <= viewWidth) {
   switch (mScaleType) {
    case FIT_START:
     deltaX = -rect.left;
     break;
    case FIT_END:
     deltaX = viewWidth - width - rect.left;
     break;
    default:
     deltaX = (viewWidth - width) / 2 - rect.left;
     break;
   }
   mScrollEdge = EDGE_BOTH;
  } else if (rect.left > 0) {
   mScrollEdge = EDGE_LEFT;
   deltaX = -rect.left;
  } else if (rect.right < viewWidth) {
   deltaX = viewWidth - rect.right;
   mScrollEdge = EDGE_RIGHT;
  } else {
   mScrollEdge = EDGE_NONE;
  } 

  // Finally actually translate the matrix
  mSuppMatrix.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) {
  ImageView imageView = getImageView(); 

  if (null != imageView) {
   Drawable d = imageView.getDrawable();
   if (null != d) {
    mDisplayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
    matrix.mapRect(mDisplayRect);
    return mDisplayRect;
   }
  }
  return null;
 } 

 /**
  * Helper method that 'unpacks' a Matrix and returns the required value
  *
  * @param matrix - Matrix to unpack
  * @param whichValue - Which value from Matrix.M* to return
  * @return float - returned value
  */
 private float getValue(Matrix matrix, int whichValue) {
  matrix.getValues(mMatrixValues);
  return mMatrixValues[whichValue];
 } 

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

 private void setImageViewMatrix(Matrix matrix) {
  ImageView imageView = getImageView();
  if (null != imageView) { 

   checkImageViewScaleType();
   imageView.setImageMatrix(matrix); 

   // Call MatrixChangedListener if needed
   if (null != mMatrixChangeListener) {
    RectF displayRect = getDisplayRect(matrix);
    if (null != displayRect) {
     mMatrixChangeListener.onMatrixChanged(displayRect);
    }
   }
  }
 } 

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

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

  mBaseMatrix.reset(); 

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

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

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

  } else if (mScaleType == ScaleType.CENTER_INSIDE) {
   float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
   mBaseMatrix.postScale(scale, scale);
   mBaseMatrix.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 (mScaleType) {
    case FIT_CENTER:
     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
     break; 

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

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

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

    default:
     break;
   }
  } 

  resetMatrix();
 } 

 /**
  * Interface definition for a callback to be invoked when the internal
  * Matrix has changed for this View.
  *
  * @author Chris Banes
  */
 public static interface OnMatrixChangedListener {
  /**
   * Callback for when the Matrix displaying the Drawable has changed.
   * This could be because the View's bounds have changed, or the user has
   * zoomed.
   *
   * @param rect - Rectangle displaying the Drawable's new bounds.
   */
  void onMatrixChanged(RectF rect);
 } 

 /**
  * Interface definition for a callback to be invoked when the Scale is
  * tapped with a single tap.
  *
  * @author Chris Banes
  */
 public static interface OnScaleTapListener { 

  /**
   * A callback to receive where the user taps on a Scale. You will only
   * receive a callback if the user taps on the actual Scale, 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.
   */
  void onScaleTap(View view, float x, float y);
 } 

 /**
  * Interface definition for a callback to be invoked when the ImageView is
  * tapped with a single tap.
  *
  * @author Chris Banes
  */
 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.
   */
  void onViewTap(View view, float x, float y);
 } 

 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 mFocalX, mFocalY;
  private final float mTargetZoom;
  private final float mDeltaScale; 

  public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX,
    final float focalY) {
   mTargetZoom = targetZoom;
   mFocalX = focalX;
   mFocalY = focalY; 

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

  public void run() {
   ImageView imageView = getImageView(); 

   if (null != imageView) {
    mSuppMatrix.postScale(mDeltaScale, mDeltaScale, mFocalX, mFocalY);
    checkAndDisplayMatrix(); 

    final float currentScale = getScale(); 

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

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

 private class FlingRunnable implements Runnable { 

  private final ScrollerProxy mScroller;
  private int mCurrentX, mCurrentY; 

  public FlingRunnable(Context context) {
   mScroller = ScrollerProxy.getScroller(context);
  } 

  public void cancelFling() {
   if (DEBUG) {
    Log.d(LOG_TAG, "Cancel Fling");
   }
   mScroller.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;
   } 

   mCurrentX = startX;
   mCurrentY = startY; 

   if (DEBUG) {
    Log.d(LOG_TAG, "fling. StartX:" + startX + " StartY:" + startY + " MaxX:" + maxX + " MaxY:" + maxY);
   } 

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

  @Override
  public void run() {
   ImageView imageView = getImageView();
   if (null != imageView && mScroller.computeScrollOffset()) { 

    final int newX = mScroller.getCurrX();
    final int newY = mScroller.getCurrY(); 

    if (DEBUG) {
     Log.d(LOG_TAG, "fling run(). CurrentX:" + mCurrentX + " CurrentY:" + mCurrentY + " NewX:" + newX
       + " NewY:" + newY);
    } 

    mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
    setImageViewMatrix(getDisplayMatrix()); 

    mCurrentX = newX;
    mCurrentY = newY; 

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

7.ScrollerProxy

/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.suo.image; 

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.widget.OverScroller;
import android.widget.Scroller; 

public abstract class ScrollerProxy { 

 public static ScrollerProxy getScroller(Context context) {
  if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
   return new PreGingerScroller(context);
  } else {
   return new GingerScroller(context);
  }
 } 

 public abstract boolean computeScrollOffset(); 

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

 public abstract void forceFinished(boolean finished); 

 public abstract int getCurrX(); 

 public abstract int getCurrY(); 

 @TargetApi(9)
 private static class GingerScroller extends ScrollerProxy { 

  private OverScroller mScroller; 

  public GingerScroller(Context context) {
   mScroller = new OverScroller(context);
  } 

  @Override
  public boolean computeScrollOffset() {
   return mScroller.computeScrollOffset();
  } 

  @Override
  public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
    int overX, int overY) {
   mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, overY);
  } 

  @Override
  public void forceFinished(boolean finished) {
   mScroller.forceFinished(finished);
  } 

  @Override
  public int getCurrX() {
   return mScroller.getCurrX();
  } 

  @Override
  public int getCurrY() {
   return mScroller.getCurrY();
  }
 } 

 private static class PreGingerScroller extends ScrollerProxy { 

  private Scroller mScroller; 

  public PreGingerScroller(Context context) {
   mScroller = new Scroller(context);
  } 

  @Override
  public boolean computeScrollOffset() {
   return mScroller.computeScrollOffset();
  } 

  @Override
  public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
    int overX, int overY) {
   mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
  } 

  @Override
  public void forceFinished(boolean finished) {
   mScroller.forceFinished(finished);
  } 

  @Override
  public int getCurrX() {
   return mScroller.getCurrX();
  } 

  @Override
  public int getCurrY() {
   return mScroller.getCurrY();
  }
 }
}

8.SDK16

/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.suo.image; 

import android.annotation.TargetApi;
import android.view.View; 

@TargetApi(16)
public class SDK16 { 

 public static void postOnAnimation(View view, Runnable r) {
  view.postOnAnimation(r);
 } 

}

9.VersionedGestureDetector

package com.suo.image; 

/*******************************************************************************
 * Copyright 2011, 2012 Chris Banes.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/ 

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.VelocityTracker;
import android.view.ViewConfiguration; 

public abstract class VersionedGestureDetector {
 static final String LOG_TAG = "VersionedGestureDetector";
 OnGestureListener mListener; 

 public static VersionedGestureDetector newInstance(Context context, OnGestureListener listener) {
  final int sdkVersion = Build.VERSION.SDK_INT;
  VersionedGestureDetector detector = null; 

  if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
   detector = new CupcakeDetector(context);
  } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
   detector = new EclairDetector(context);
  } else {
   detector = new FroyoDetector(context);
  } 

  detector.mListener = listener; 

  return detector;
 } 

 public abstract boolean onTouchEvent(MotionEvent ev); 

 public abstract boolean isScaling(); 

 public static interface OnGestureListener {
  public void onDrag(float dx, float dy); 

  public void onFling(float startX, float startY, float velocityX, float velocityY); 

  public void onScale(float scaleFactor, float focusX, float focusY);
 } 

 private static class CupcakeDetector extends VersionedGestureDetector { 

  float mLastTouchX;
  float mLastTouchY;
  final float mTouchSlop;
  final float mMinimumVelocity; 

  public CupcakeDetector(Context context) {
   final ViewConfiguration configuration = ViewConfiguration.get(context);
   mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
   mTouchSlop = configuration.getScaledTouchSlop();
  } 

  private VelocityTracker mVelocityTracker;
  private boolean mIsDragging; 

  float getActiveX(MotionEvent ev) {
   return ev.getX();
  } 

  float getActiveY(MotionEvent ev) {
   return ev.getY();
  } 

  public boolean isScaling() {
   return false;
  } 

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
   boolean result = true;
   switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN: {
     mVelocityTracker = VelocityTracker.obtain();
     if (mVelocityTracker != null) {
      mVelocityTracker.addMovement(ev);
     } 

     mLastTouchX = getActiveX(ev);
     mLastTouchY = getActiveY(ev);
     mIsDragging = false;
     break;
    } 

    case MotionEvent.ACTION_MOVE: {
     final float x = getActiveX(ev);
     final float y = getActiveY(ev);
     final float dx = x - mLastTouchX, dy = y - mLastTouchY; 

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

     if (mIsDragging) {
      mListener.onDrag(dx, dy);
      mLastTouchX = x;
      mLastTouchY = y; 

      if (null != mVelocityTracker) {
       mVelocityTracker.addMovement(ev);
      }
     }
     break;
    } 

    case MotionEvent.ACTION_CANCEL: {
     // Recycle Velocity Tracker 

     if (null != mVelocityTracker) {
      mVelocityTracker.recycle();
      mVelocityTracker = null;
     }
     break;
    } 

    case MotionEvent.ACTION_UP: {
     if (mIsDragging) {
      if (null != mVelocityTracker) {
       mLastTouchX = getActiveX(ev);
       mLastTouchY = getActiveY(ev); 

       // Compute velocity within the last 1000ms
       mVelocityTracker.addMovement(ev);
       mVelocityTracker.computeCurrentVelocity(1000); 

       final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker.getYVelocity(); 

       // If the velocity is greater than minVelocity, call
       // listener
       if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
        mListener.onFling(mLastTouchX, mLastTouchY, -vX, -vY);
       }
      }
     } 

     // Recycle Velocity Tracker
     if (null != mVelocityTracker) {
      mVelocityTracker.recycle();
      mVelocityTracker = null;
     } 

     break;
    }
   } 

   return result;
  }
 } 

 @TargetApi(5)
 private static class EclairDetector extends CupcakeDetector {
  private static final int INVALID_POINTER_ID = -1;
  private int mActivePointerId = INVALID_POINTER_ID;
  private int mActivePointerIndex = 0; 

  public EclairDetector(Context context) {
   super(context);
  } 

  @Override
  float getActiveX(MotionEvent ev) {
   try {
    return ev.getX(mActivePointerIndex);
   } catch (Exception e) {
    return ev.getX();
   }
  } 

  @Override
  float getActiveY(MotionEvent ev) {
   try {
    return ev.getY(mActivePointerIndex);
   } catch (Exception e) {
    return ev.getY();
   }
  } 

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
   final int action = ev.getAction();
   switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
     mActivePointerId = ev.getPointerId(0);
     break;
    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP:
     mActivePointerId = INVALID_POINTER_ID;
     break;
    case MotionEvent.ACTION_POINTER_UP:
     final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
     final int pointerId = ev.getPointerId(pointerIndex);
     if (pointerId == mActivePointerId) {
      // This was our active pointer going up. Choose a new
      // active pointer and adjust accordingly.
      final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
      mActivePointerId = ev.getPointerId(newPointerIndex);
      mLastTouchX = ev.getX(newPointerIndex);
      mLastTouchY = ev.getY(newPointerIndex);
     }
     break;
   } 

   mActivePointerIndex = ev.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId : 0);
   return super.onTouchEvent(ev);
  }
 } 

 @TargetApi(8)
 private static class FroyoDetector extends EclairDetector { 

  private final ScaleGestureDetector mDetector; 

  // Needs to be an inner class so that we don't hit
  // VerifyError's on API 4.
  private final OnScaleGestureListener mScaleListener = new OnScaleGestureListener() { 

   @Override
   public boolean onScale(ScaleGestureDetector detector) {
    mListener.onScale(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY());
    return true;
   } 

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

   @Override
   public void onScaleEnd(ScaleGestureDetector detector) {
    // NO-OP
   }
  }; 

  public FroyoDetector(Context context) {
   super(context);
   mDetector = new ScaleGestureDetector(context, mScaleListener);
  } 

  @Override
  public boolean isScaling() {
   return mDetector.isInProgress();
  } 

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
   mDetector.onTouchEvent(ev);
   return super.onTouchEvent(ev);
  } 

 }
}

10.MainActivity 

package com.suo.myimage; 

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu; 

public class MainActivity extends Activity { 

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 } 

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 } 

}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".MainActivity" > 

 <TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_centerHorizontal="true"
  android:layout_centerVertical="true"
  android:text="@string/hello_world" /> 

 <com.suo.image.ScaleView
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:src="@drawable/a"/> 

</RelativeLayout>

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android自定义ImageView实现在图片上添加图层效果

    首先我们先看下效果图 实现思路 这是两张前后对比图,右边第二张图里面的已抢光标签图片当已经没有商品的时候就会显示了,在每个图片的中心位置,第一想法是在ImageView的外层再套一层RelativeLayout 实现方法 <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <SelectableRoundedImageVi

  • Android自定义圆角ImageView控件

    目前一些比较火的图片加载库虽然支持圆角加载,若你是接的别人作了一半的项目,刚好别人用的图片加载库刚好不支持圆角加载,那么这颗控件你值得拥有.(支持网络图片的加载) 1.创建CustomImageView 类在你的项目中(源码如下) import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Bitmap.

  • Android通过自定义ImageView控件实现图片的缩放和拖动的实现代码

    概述:通过自定义ImageView控件,在xml布局里面调用自定的组件实现图片的缩放. /** * 自定义的ImageView控制,可对图片进行多点触控缩放和拖动 * * @author qiuwanyong */ public class MyImageView extends ImageView { /** * 初始化状态常量 */ public static final int STATUS_INIT = 1; /** * 图片放大状态常量 */ public static final i

  • Android编程实现自定义ImageView圆图功能的方法

    本文实例讲述了Android编程实现自定义ImageView圆图功能的方法.分享给大家供大家参考,具体如下: 首先很感谢开源项目Universal Image Loader图片加载框架.之前也看过一段时间框架源码,但是却没有时间进行知识点的总结. 今天项目遇到了需要实现圆头像的编辑显示,Universal就已经提供了这个显示RoundedBitmapDisplayer这个类实现了圆图功能.看它的代码可以发现是实现的Drawable public static class RoundedDrawa

  • Android自定义圆角ImageView

    废话不多说了,直接给大家贴代码了. java类如下: import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.gra

  • Android 自定义圆形头像CircleImageView支持加载网络图片的实现代码

    在Android开发中我们常常用到圆形的头像,如果每次加载之后再进行圆形裁剪特别麻烦.所以在这里写一个自定义圆形ImageView,直接去加载网络图片,这样的话就特别的方便. 先上效果图 主要的方法 1.让自定义 CircleImageView 继承ImageView /** * 自定义圆形头像 * Created by Dylan on 2015/11/26 0026. */ public class CircleImageView extends ImageView { } 2.在构造方法中

  • android自定义ImageView仿图片上传示例

    看下效果图 主要看下自定义view 代码 public class ProcessImageView extends ImageView{ private Context context; private Paint paint; private LogUtil log=LogUtil.getInstance(); int progress = 0; private boolean flag; public ProcessImageView(Context context) { super(co

  • Android自定义GestureDetector实现手势ImageView

    不说废话了,进入我们今天的主题吧. 先贴上前面内容的地址: Android手势ImageView三部曲(一) Android手势ImageView三部曲(二) Android手势ImageView三部曲(三) 前面我们讲到了ScaleGestureDetector这个工具类,我在疑惑,为什么搞出一个ScaleGestureDetector,不顺带把什么旋转.移动.做了呢? 好吧-! 谷歌肯定还是想给开发者留一点自己的空间哈. 仿照ScaleGestureDetector,我们来定义一个叫Move

  • Android布局自定义Shap圆形ImageView可以单独设置背景与图片

    一.图片预览: 一.实现功能: 需求要实现布局中为圆形图片,图片背景与图标分开且合并到一个ImageView. 二.具体实现: XML中布局中定义ImageView,关健设置两个参数 Android:backgroup(设置背景),Android:src(设置ImageVIew中图片),圆形图片制作Drawable下定义xml shap样式(solid-color,size-width\hight) XML代码如下: <ImageView android:id="@+id/zhongjie

  • Android自定义ImageView实现自动放大缩小动画

    这篇讲的是如何生成一个自定义的ImageView,实现自动放大缩小动画. 为什么实现这个功能呢?因为我想在ViewPager实现图片放大缩小的动画,但是ViewPager几个页面的动画会一起动,而且放大全屏图片的话会相互覆盖,很诡异.于是上网搜demo,一无所获.迫于无奈... 废话不多说,直接贴代码. 1.配置文件直接添加 当直接在布局文件中添加图片的话,可以在自定义View代码中用getDrawable()获取图片资源,然后通过DrawBitmap绘制图片.通过不断绘制图片的位置,达到放大缩

  • Android自定义控件之圆形、圆角ImageView

    一.问题在哪里? 问题来源于app开发中一个很常见的场景--用户头像要展示成圆的:  二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在头像上不就完事了嘛,哈哈哈! 在背景纯色的前提下,这的确能简单解决问题,但是如果背景没有这么简单呢? 在这种不规则背景下,有两个问题: 1).背景图常常是适应手机宽度缩放,而头像的尺寸又是固定宽高DP的,所以固定的蒙板图片是没法保证在不同机型上都和背景图案吻合的. 2).在这种非纯色背景下,哪天想调整一下头像

随机推荐