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

作者:lqh 时间:2023-03-08 10:56:08 

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

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

1.项目结构图

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

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,图片缩放
0
投稿

猜你喜欢

  • 详细讲述Java中的对象转型

    2023-09-24 04:09:49
  • c#获取本机的IP地址的代码

    2021-08-21 08:37:07
  • 创建动态代理对象bean,并动态注入到spring容器中的操作

    2021-09-04 01:02:43
  • Eclipse项目怎么导入IDEA并运行(超详细)

    2021-11-15 06:51:41
  • android保存Bitmap图片到指定文件夹示例

    2022-10-11 22:59:14
  • Android使用CrashHandler来获取应用的crash信息的方法

    2023-07-25 20:27:38
  • C# 泛型List排序的实现

    2021-09-22 07:03:16
  • C#隐藏控制台键盘输入的方法

    2022-04-29 21:11:06
  • 解决C# winForm自定义鼠标样式的两种实现方法详解

    2021-07-15 17:10:46
  • C#使用文件流FileStream和内存流MemoryStream操作底层字节数组byte[]

    2023-09-04 00:30:55
  • C#中WPF ListView绑定数据的实例详解

    2023-03-11 12:08:41
  • Java 中的抽象介绍

    2023-03-27 06:10:20
  • C#显示文件夹下所有图片文件的方法

    2021-11-25 23:50:10
  • C#使用WebSocket实现聊天室功能

    2022-08-10 09:29:33
  • Java NIO实现聊天系统

    2023-08-08 08:35:36
  • Unity实现倒计时组件

    2021-06-18 01:06:44
  • Java 归并排序算法、堆排序算法实例详解

    2023-11-25 09:43:25
  • SpringBoot 配置文件加密的步骤

    2023-10-23 02:55:55
  • Java NIO实战之多人聊天室

    2022-02-28 15:05:00
  • C# 位图BitArray的使用

    2022-09-13 23:54:46
  • asp之家 软件编程 m.aspxhome.com