Android-相册效果(图片缩放 自由滑动)
先上图:
很多时候 我们会有这么一个需求:
展示一组图片 每个Item的图片 可以自由拉伸 滑动 焦点不冲突
网上有很多实现方法 通过自定义Gallery和ImageView来实现
个人不是很推荐 在这里推荐有ViewPager加ZoomImageView
ViewPager就不过多解释 主要是ZoomImageView
上代码:
public class ZoomImageView extends ImageView implements OnTouchListener, OnGestureListener{ private int WIDTH; private int HEIGHT; private int mStatusHeight; private static final String TAG = ZoomImageView.class.getSimpleName(); // This is the base transformation which is used to show the image // initially. The current computation for this shows the image in // it's entirety, letterboxing as needed. One could choose to // show the image as cropped instead. // // This matrix is recomputed when we go from the thumbnail image to // the full size image. protected Matrix mBaseMatrix = new Matrix(); // This is the supplementary transformation which reflects what // the user has done in terms of zooming and panning. // // This matrix reHonagAnGalleryActivitys the same when we go from the // thumbnail image // to the full size image. protected Matrix mSuppMatrix = new Matrix(); // This is the final matrix which is computed as the concatentation // of the base matrix and the supplementary matrix. private final Matrix mDisplayMatrix = new Matrix(); // Temporary buffer used for getting the values out of a matrix. private final float[] mMatrixValues = new float[9]; // The current bitmap being displayed. // protected final RotateBitmap mBitmapDisplayed = new RotateBitmap(null); protected Bitmap image = null; int mThisWidth = -1, mThisHeight = -1; float mMaxZoom = 4.0f; float mMinZoom = 0.1f; private int imageWidth; private int imageHeight; private float scaleRate; public ZoomImageView(Context context) { super(context); init(); } public ZoomImageView(Context context, int imageWidth, int imageHeight) { super(context); this.imageHeight = imageHeight; this.imageWidth = imageWidth; init(); } public ZoomImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void arithScaleRate() { float scaleWidth = WIDTH / (float) imageWidth; float scaleHeight = HEIGHT / (float) imageHeight; scaleRate = Math.min(scaleWidth, scaleHeight); if (scaleRate > 1) { scaleRate = 1f; } } public float getScaleRate() { return scaleRate; } public int getImageWidth() { return imageWidth; } public void setImageWidth(int imageWidth) { this.imageWidth = imageWidth; } public int getImageHeight() { return imageHeight; } public void setImageHeight(int imageHeight) { this.imageHeight = imageHeight; } protected Handler mHandler = new Handler(); @Override public void setImageResource(int resId) { Bitmap bit = BitmapFactory.decodeResource(getResources(), resId); setImageBitmap(bit); } @Override public void setImageBitmap(Bitmap bitmap) { super.setImageBitmap(bitmap); this.imageHeight = bitmap.getHeight(); this.imageWidth = bitmap.getWidth(); image = bitmap; requestImage(); } public void requestImage() { arithScaleRate(); zoomTo(scaleRate, WIDTH / 2f, WIDTH / 2f); layoutToCenter(); } // Center as much as possible in one or both axis. Centering is // defined as follows: if the image is scaled down below the // view's dimensions then center it (literally). If the image // is scaled larger than the view and is translated out of view // then translate it back into view (i.e. eliminate black bars). protected void center(boolean horizontal, boolean vertical) { // if (mBitmapDisplayed.getBitmap() == null) { // return; // } if (image == null) { return; } Matrix m = getImageViewMatrix(); RectF rect = new RectF(0, 0, image.getWidth(), image.getHeight()); // RectF rect = new RectF(0, 0, imageWidth*getScale(), // imageHeight*getScale()); m.mapRect(rect); float height = rect.height(); float width = rect.width(); float deltaX = 0, deltaY = 0; if (vertical) { int viewHeight = getHeight(); if (height < viewHeight) { deltaY = (viewHeight - height) / 2 - rect.top; } else if (rect.top > 0) { deltaY = -rect.top; } else if (rect.bottom < viewHeight) { deltaY = getHeight() - rect.bottom; } } if (horizontal) { int viewWidth = getWidth(); if (width < viewWidth) { deltaX = (viewWidth - width) / 2 - rect.left; } else if (rect.left > 0) { deltaX = -rect.left; } else if (rect.right < viewWidth) { deltaX = viewWidth - rect.right; } } postTranslate(deltaX, deltaY); setImageMatrix(getImageViewMatrix()); } public void setStatusHeight(int height) { HEIGHT -= height; } private void init() { // except the height of the top bar DisplayMetrics dm = getResources().getDisplayMetrics(); WIDTH = dm.widthPixels; HEIGHT = dm.heightPixels; // HEIGHT -= getResources().getDimension(R.dimen.top_bar_height); setScaleType(ImageView.ScaleType.MATRIX); setOnTouchListener(this); mDetector = new GestureDetector(getContext(), this); } public void layoutToCenter() { float width = imageWidth * getScale(); float height = imageHeight * getScale(); float fill_width = WIDTH - width; float fill_height = HEIGHT - height; float tran_width = 0f; float tran_height = 0f; if (fill_width > 0) tran_width = fill_width / 2; if (fill_height > 0) tran_height = fill_height / 2; float v[] = new float[9]; Matrix m = getImageMatrix(); m.getValues(v); float left = v[Matrix.MTRANS_X]; float top = v[Matrix.MTRANS_Y]; tran_width -= left; tran_height -= top; postTranslate(tran_width, tran_height); // setImageMatrix(getImageViewMatrix()); } protected float getValue(Matrix matrix, int whichValue) { matrix.getValues(mMatrixValues); // mMinZoom = (BrowsePictureActivity.screenWidth / 2f) / imageWidth; mMinZoom = 0; return mMatrixValues[whichValue]; } // Get the scale factor out of the matrix. protected float getScale(Matrix matrix) { return getValue(matrix, Matrix.MSCALE_X); } protected float getScale() { return getScale(mSuppMatrix); } // Combine the base matrix and the supp matrix to make the final matrix. protected Matrix getImageViewMatrix() { // The final matrix is computed as the concatentation of the base matrix // and the supplementary matrix. mDisplayMatrix.set(mBaseMatrix); mDisplayMatrix.postConcat(mSuppMatrix); return mDisplayMatrix; } static final float SCALE_RATE = 1.25F; // Sets the maximum zoom, which is a scale relative to the base matrix. It // is calculated to show the image at 400% zoom regardless of screen or // image orientation. If in the future we decode the full 3 megapixel image, // rather than the current 1024x768, this should be changed down to 200%. protected float maxZoom() { if (image == null) { return 1F; } float fw = (float) image.getWidth() / (float) mThisWidth; float fh = (float) image.getHeight() / (float) mThisHeight; float max = Math.max(fw, fh) * 4; return max; } protected void zoomTo(float scale, float centerX, float centerY) { zoomTo(scale, centerX, centerY, true); } protected void zoomTo(float scale, float centerX, float centerY, boolean limit) { if (limit) { if (scale > mMaxZoom) { scale = mMaxZoom; } else if (scale < mMinZoom) { scale = mMinZoom; } } float oldScale = getScale(); float deltaScale = scale / oldScale; mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY); setImageMatrix(getImageViewMatrix()); center(true, true); } protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) { final float incrementPerMs = (scale - getScale()) / durationMs; final float oldScale = getScale() + mMinZoom; final long startTime = System.currentTimeMillis(); mHandler.post(new Runnable() { public void run() { long now = System.currentTimeMillis(); float currentMs = Math.min(durationMs, now - startTime); float target = oldScale + (incrementPerMs * currentMs); zoomTo(target, centerX, centerY); if (currentMs < durationMs) { mHandler.post(this); } } }); } protected void zoomBigToSmall(final float scale, final float centerX, final float centerY, final float durationMs) { final float incrementPerMs = (scale - getScale()) / durationMs; final float oldScale = getScale() + mMaxZoom; final long startTime = System.currentTimeMillis(); mHandler.post(new Runnable() { public void run() { long now = System.currentTimeMillis(); float currentMs = Math.min(durationMs, now - startTime); float target = oldScale + (incrementPerMs * currentMs); zoomTo(target, centerX, centerY); if (currentMs < durationMs) { mHandler.post(this); } } }); } protected void zoomTo(float scale) { float cx = getWidth() / 2F; float cy = getHeight() / 2F; zoomTo(scale, cx, cy); } protected void zoomToPoint(float scale, float pointX, float pointY) { float cx = getWidth() / 2F; float cy = getHeight() / 2F; panBy(cx - pointX, cy - pointY); zoomTo(scale, cx, cy); } protected void zoomIn() { zoomIn(SCALE_RATE); } protected void zoomOut() { zoomOut(SCALE_RATE); } protected void zoomIn(float rate) { if (getScale() >= mMaxZoom) { return; // Don't let the user zoom into the molecular level. } else if (getScale() <= mMinZoom) { return; } if (image == null) { return; } float cx = getWidth() / 2F; float cy = getHeight() / 2F; mSuppMatrix.postScale(rate, rate, cx, cy); setImageMatrix(getImageViewMatrix()); } protected void zoomOut(float rate) { if (image == null) { return; } float cx = getWidth() / 2F; float cy = getHeight() / 2F; // Zoom out to at most 1x. Matrix tmp = new Matrix(mSuppMatrix); tmp.postScale(1F / rate, 1F / rate, cx, cy); if (getScale(tmp) < 1F) { mSuppMatrix.setScale(1F, 1F, cx, cy); } else { mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy); } setImageMatrix(getImageViewMatrix()); center(true, true); } public void postTranslate(float dx, float dy) { mSuppMatrix.postTranslate(dx, dy); setImageMatrix(getImageViewMatrix()); } private float mdy = 0.0f; protected void postTransVerticalDuration(final float dy, final float durationMs) { final float incrementPerMs = dy / durationMs; final long startTime = System.currentTimeMillis(); mdy = 0.0f; mHandler.post(new Runnable() { public void run() { long now = System.currentTimeMillis(); float currentMs = Math.min(durationMs, now - startTime); postTranslate(0, incrementPerMs * currentMs - mdy); mdy = incrementPerMs * currentMs; if (currentMs < durationMs) { mHandler.post(this); } } }); } private float mdx = 0.0f; protected void postTransHorizontalDuration(final float dx, final float durationMs) { final float incrementPerMs = dx / durationMs; final long startTime = System.currentTimeMillis(); mdx = 0.0f; mHandler.post(new Runnable() { public void run() { long now = System.currentTimeMillis(); float currentMs = Math.min(durationMs, now - startTime); postTranslate(incrementPerMs * currentMs - mdx, 0); mdx = incrementPerMs * currentMs; if (currentMs < durationMs) { mHandler.post(this); } } }); } protected void panBy(float dx, float dy) { postTranslate(dx, dy); setImageMatrix(getImageViewMatrix()); } private float mDeltaX, mDeltaY; private float sx, sy; private float width, height; private float values[] = new float[9]; private float baseValue; private float originalScale; private final int NONE = 0; private final int DRAG = 1; private final int ZOOM = 2; private int mMod = 0; @Override public boolean onTouch(View v, MotionEvent event) { if (mDetector != null) { mDetector.onTouchEvent(event); } final ZoomImageView imageView = this; final int sWidth = WIDTH; final int sHeight = HEIGHT; width = imageView.getScale() * imageView.getImageWidth(); height = imageView.getScale() * imageView.getImageHeight(); Matrix matrix = imageView.getImageMatrix(); matrix.getValues(values); float x = event.getX(); float y = event.getY(); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mMod = DRAG; baseValue = 0; originalScale = imageView.getScale(); mDeltaX = 0; mDeltaY = 0; sx = x; sy = y; break; case MotionEvent.ACTION_POINTER_DOWN: mMod = ZOOM; break; case MotionEvent.ACTION_MOVE: if (event.getPointerCount() == 2) { mMod = ZOOM; } float deltaX = sx - x; float deltaY = sy - y; sx = x; sy = y; if (deltaX != 0.0f) { mDeltaX = deltaX; } if (deltaY != 0.0f) { mDeltaY = deltaY; } switch (mMod) { case DRAG: final float left = values[Matrix.MTRANS_X]; final float right = left + width; if(right < sWidth && Math.abs(right - sWidth) >= 0.0001f && right < width) { v.getParent().requestDisallowInterceptTouchEvent(false); } else if(right > width) { v.getParent().requestDisallowInterceptTouchEvent(false); } else { v.getParent().requestDisallowInterceptTouchEvent(true); } if (width < sWidth) { deltaX = 0.0f; mDeltaX = deltaX; } if (height < sHeight) { deltaY = 0.0f; mDeltaY = deltaY; } imageView.postTranslate(-deltaX, -deltaY); break; case ZOOM: if (event.getPointerCount() < 2) { return true; } float dx = event.getX(0) - event.getX(1); float dy = event.getY(0) - event.getY(1); float value = FloatMath.sqrt(dx * dx + dy * dy); if (baseValue == 0) { baseValue = value; } else { float scale = value / baseValue; // scale the image imageView.zoomTo(originalScale * scale, (event.getX(0) + event.getX(1)) / 2, (event.getY(0) + event.getY(1)) / 2, false); } break; default: } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: final float left = values[Matrix.MTRANS_X]; final float right = left + width; final float top = values[Matrix.MTRANS_Y]; final float bottom = top + height; switch (mMod) { case DRAG: final float startX = mDeltaX; final float startY = mDeltaY; if (startX != 0) { if (right < sWidth) { // ((ViewPager) v.getParent()).requestDisallowInterceptTouchEvent(false); postTransHorizontalDuration(sWidth - right, 200.0f); } if (left >= 0) { postTransHorizontalDuration(-left, 200.0f); } } if (startY != 0) { if (bottom < sHeight) { postTransVerticalDuration(sHeight - bottom, 200.0f); } if (top > 0) { postTransVerticalDuration(-top, 200.0f); } } break; case ZOOM: final float sr = getScaleRate(); final float scale = getScale(); mDoubleClick = false; if (sr > scale) { imageView.zoomTo(sr, sWidth / 2, sHeight / 2, 200.0f); return true; } else if (mMaxZoom < scale) { imageView.zoomBigToSmall(sr, sWidth / 2, sHeight / 2, 300.0f); return true; } break; } mMod = NONE; break; default: } return true; } private boolean mDoubleClick; private GestureDetector mDetector; private OnSimpleListener listener; public void doubleClick() { float scale = getScale(); if (!mDoubleClick) { zoomTo(1.0f); mDoubleClick = true; } else if (scale == 1.0f) { zoomTo(2.0f); } else if (scale == 2.0f) { zoomTo(4.0f); } else if (scale == 4.0f) { zoomTo(1.0f); } } @Override public boolean onDown(MotionEvent e) { // TODO Auto-generated method stub return false; } @Override public void onShowPress(MotionEvent e) { // TODO Auto-generated method stub } @Override public boolean onSingleTapUp(MotionEvent e) { // TODO Auto-generated method stub if(listener != null) { listener.setOnSimpleClickListenr(); } return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // TODO Auto-generated method stub return false; } @Override public void onLongPress(MotionEvent e) { // TODO Auto-generated method stub } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // TODO Auto-generated method stub return false; } public interface OnSimpleListener { void setOnSimpleClickListenr(); } public void setOnSimpleClickListenr(OnSimpleListener listener) { this.listener = listener; } }
为了不和ViewPager的滑动冲突 主要是在action_move里面加了一个判断:
final float left = values[Matrix.MTRANS_X]; final float right = left + width; if(right < sWidth && Math.abs(right - sWidth) >= 0.0001f && right < width) { v.getParent().requestDisallowInterceptTouchEvent(false); } else if(right > width) { v.getParent().requestDisallowInterceptTouchEvent(false); } else { v.getParent().requestDisallowInterceptTouchEvent(true); }
需要源码的M我~~~