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;
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
package com.suo.image;
import android.graphics.RectF;
import android.view.View;
import android.widget.ImageView;
public interface IScaleView {
boolean canZoom();
RectF getDisplayRect();
float getMinScale();
float getMidScale();
float getMaxScale();
float getScale();
ImageView.ScaleType getScaleType();
void setAllowParentInterceptonEdge(boolean allow);
void setMinScale(float minScale);
void setMidScale(float midScale);
void setMaxScale(float maxScale);
void setonLongClickListener(View.onLongClickListener listener);
void setonMatrixChangeListener(ScaleViewAttacher.onMatrixChangedListener listener);
void setonScaleTapListener(ScaleViewAttacher.onScaleTapListener listener);
void setonViewTapListener(ScaleViewAttacher.onViewTapListener listener);
void setScaleType(ImageView.ScaleType scaleType);
void setZoomable(boolean zoomable);
void zoomTo(float scale, float focalX, float focalY);
}
5.ScaleView
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 这个是最关键的
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");
}
}
private static boolean hasDrawable(ImageView imageView) {
return null != imageView && null != imageView.getDrawable();
}
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;
}
}
private static void setImageViewScaleTypeMatrix(ImageView imageView) {
if (null != imageView) {
if (imageView instanceof ScaleView) {
} else {
imageView.setScaleType(ScaleType.MATRIX);
}
}
}
private WeakReference 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.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;
}
@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();
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();
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 onSingleTap/confirm/ied(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;
}
}
private void checkAndDisplayMatrix() {
checkMatrixBounds();
setImageViewMatrix(getDisplayMatrix());
}
private void checkImageViewScaleType() {
ImageView imageView = getImageView();
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);
}
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;
}
private float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
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);
}
}
}
}
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();
}
public static interface onMatrixChangedListener {
void onMatrixChanged(RectF rect);
}
public static interface onScaleTapListener {
void onScaleTap(View view, float x, float y);
}
public static interface onViewTapListener {
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
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
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;
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
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!



