Android 自定義imageview實現圖片縮放例項詳解

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> 

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支援!

您可能感興趣的文章:

Android自定義ImageView實現自動放大縮小動畫Android自定義圓角ImageView控制元件Android自定義控制元件之圓形、圓角ImageViewAndroid自定義GestureDetector實現手勢ImageViewandroid自定義ImageView仿圖片上傳示例Android自定義ImageView實現在圖片上新增圖層效果Android通過自定義ImageView控制元件實現圖片的縮放和拖動的實現程式碼Android 自定義圓形頭像CircleImageView支援載入網路圖片的實現程式碼Android自定義圓角ImageViewAndroid佈局自定義Shap圓形ImageView可以單獨設定背景與圖片Android程式設計實現自定義ImageView圓圖功能的方法