首页 > 系统 > Android > 正文

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

2019-12-12 03:08:04
字体:
来源:转载
供稿:网友

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> 

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表