package external.otherview; import java.io.ByteArrayOutputStream; import java.io.InputStream; import com.aiyou.view.DarkImageView; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Display; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.Scroller; /** * 可缩放、旋转、平移的ImageView,可加载超大图片(请使用set方法设置图片来源),完美解决OOM问题 * * @author sollian */ public class MagicImageView extends DarkImageView { public interface OnTransformListener { void onDrag(float deltaX, float deltaY); /** * 当前缩放值 * * @param scale */ void onScale(float scale); /** * 当前图片旋转角度,顺时针为正,只能为0,90,180,270; * * @param degree 单位为角度 */ void onRotate(float degree); void onFling(float deltaX, float deltaY); } private OnTransformListener mListener = null; /** * 自动缩放、旋转、平移动画的持续时间 */ private static final float DEFAULT_ANIM_TIME = 200.0f; /** * 最大、最小缩放值 */ private static final float DEFAULT_MIN_SCALE = 0.75f; private static final float DEFAULT_MAX_SCALE = 5.0f; private float mMinScale = DEFAULT_MIN_SCALE; private float mMaxScale = DEFAULT_MAX_SCALE; /** * 标准化的缩放倍数 */ private float mNormalizedScale = 1; /** * 适应屏幕时,图片的宽高 */ private float mMatchedImageWidth = 0, mMatchedImageHeight = 0; private static enum State { NONE, ONE_POINT, TWO_POINT, } ; private State mState = State.NONE; private boolean mIsTranslate = false; private boolean mIsRotate = false; private boolean mIsScale = false; private boolean mIsRotateEnabled = true; /** * 需要自动旋转的角度 */ private float remainDegree = 0f; private static int SCREEN_WIDTH = 0, SCREEN_HEIGHT = 0; private int mViewHeight, mViewWidth; private Matrix mMatrix; private GestureDetector mGestureDetector; private ScaleGestureDetector mScaleDetector; private Fling mFling = null; private Rotate mRotate = null; private Translate mTranslate = null; private Scale mScale = null; /** * 图片旋转的角度,顺时针方向为正 */ private float mDegree = 0; private boolean mReachLeft = false; private boolean mReachRight = false; public MagicImageView(Context context) { super(context); init(); } public MagicImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MagicImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @SuppressLint("ClickableViewAccessibility") @SuppressWarnings("deprecation") private void init() { if (SCREEN_WIDTH == 0 || SCREEN_HEIGHT == 0) { /** * 屏幕宽高 */ WindowManager manager = (WindowManager) getContext() .getSystemService(Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); SCREEN_WIDTH = display.getWidth(); SCREEN_HEIGHT = display.getHeight(); } mMatrix = new Matrix(); mGestureDetector = new GestureDetector(getContext(), new GestureListener()); mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); setScaleType(ScaleType.MATRIX); setOnTouchListener(new TouchListener()); } public void setOnTransformListener(OnTransformListener listener) { mListener = listener; } public void setMaxScale(float scale) { mMaxScale = scale; } /** * 获取当前缩放值 * * @return */ public float getCurrentScale() { return mNormalizedScale; } /** * 关闭旋转功能 */ public void disableRotate() { mIsRotateEnabled = false; } @Override public void setImageResource(int resId) { InputStream is = getContext().getResources().openRawResource(resId); setInputStream(is); } public void setByte(byte[] data) { if (data == null) { return; } super.setImageBitmap(decodeByte(data)); } public void setInputStream(InputStream is) { if (is == null) { return; } byte[] data = null; try { data = is2Byte(is); } catch (Exception e) { } setByte(data); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); fitImageToView(); } @Override public void setImageBitmap(Bitmap bm) { if (bm == null) { return; } bm = decodeByte(bmp2Byte(bm)); super.setImageBitmap(bm); fitImageToView(); } @SuppressWarnings("deprecation") private Bitmap decodeByte(byte[] data) { if (data == null) { return null; } Bitmap bmp = null; BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPurgeable = true; options.inInputShareable = true; options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, options); int screenWidth = 0, screenHeight = 0; if (options.outWidth > options.outHeight) { screenWidth = SCREEN_HEIGHT; screenHeight = SCREEN_WIDTH; } else { screenWidth = SCREEN_WIDTH; screenHeight = SCREEN_HEIGHT; } float scaleX = (float) options.outWidth / screenWidth * 2; float scaleY = (float) options.outHeight / screenHeight * 2; options.inSampleSize = (int) Math.ceil(Math.max(scaleX, scaleY)); options.inJustDecodeBounds = false; while (true) { try { bmp = BitmapFactory.decodeByteArray(data, 0, data.length, options); break; } catch (OutOfMemoryError e) { options.inSampleSize++; } } return bmp; } private byte[] is2Byte(InputStream inStream) throws Exception { byte[] buffer = new byte[1024]; int len; ByteArrayOutputStream outStream = new ByteArrayOutputStream(); while ((len = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); } byte[] data = outStream.toByteArray(); outStream.close(); inStream.close(); return data; } private byte[] bmp2Byte(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mViewWidth = MeasureSpec.getSize(widthMeasureSpec); mViewHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(mViewWidth, mViewHeight); fitImageToView(); } private void reset() { mMatrix.reset(); mNormalizedScale = 1; mState = State.NONE; // mDegree = 0; } /** * 使图片适应屏幕 */ private void fitImageToView() { if (mMatrix == null) { return; } reset(); Drawable drawable = getDrawable(); if (drawable == null) { return; } int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); float scale1 = (float) mViewWidth / drawableWidth; float scale2 = (float) mViewHeight / drawableHeight; float scale3 = (float) mViewWidth / drawableHeight; float scale4 = (float) mViewHeight / drawableWidth; float scale = Math.min(Math.min(scale1, scale2), Math.min(scale3, scale4)); // mMatrix.setScale(scale, scale); mMatchedImageWidth = drawableWidth * scale; mMatchedImageHeight = drawableHeight * scale; mMatrix.setScale(scale, scale); mMatrix.postTranslate((mViewWidth - mMatchedImageWidth) / 2, (mViewHeight - mMatchedImageHeight) / 2); mMatrix.postRotate(mDegree, mViewWidth / 2, mViewHeight / 2); setImageMatrix(mMatrix); } private Size getImageSize() { return getImageSize(mNormalizedScale); } /** * 获取当前图片的宽高,考虑了图片的旋转角度 * * @param scale * @return */ private Size getImageSize(float scale) { Size size; if (mDegree == 0 || mDegree == 180) { size = new Size(mMatchedImageWidth * scale, mMatchedImageHeight * scale); } else { size = new Size(mMatchedImageHeight * scale, mMatchedImageWidth * scale); } return size; } private float[] getMatrixValues(Matrix matrix) { float[] value = new float[9]; matrix.getValues(value); return value; } private void fixTranslation() { fixTranslation(0, 0); } /** * 移动图片,使适应屏幕 * * @param deltaX * @param deltaY */ private void fixTranslation(float deltaX, float deltaY) { float[] matrixValues = getMatrixValues(mMatrix); float transX = matrixValues[Matrix.MTRANS_X] + deltaX; float transY = matrixValues[Matrix.MTRANS_Y] + deltaY; Size size = getImageSize(); transX = getFixTrans(transX, size.getWidth(), mViewWidth, true); transY = getFixTrans(transY, size.getHeight(), mViewHeight, false); matrixValues[Matrix.MTRANS_X] = transX; matrixValues[Matrix.MTRANS_Y] = transY; mMatrix.setValues(matrixValues); setImageMatrix(mMatrix); } private float getFixTrans(float transSize, float contentSize, float viewSize, boolean isWidth) { return getFixTrans(transSize, contentSize, viewSize, isWidth, true); } /** * 得到合适的偏移量 * * @param transSize * @param contentSize * @param viewSize * @param isWidth * @param fixed * @return */ private float getFixTrans(float transSize, float contentSize, float viewSize, boolean isWidth, boolean fixed) { int degree = 90; if (!isWidth) { degree = 270; } if (viewSize >= contentSize) { transSize = (viewSize - contentSize) / 2; if (mDegree == 180 || mDegree == degree) { transSize += contentSize; } } else { float minSize, maxSize; minSize = viewSize - contentSize; maxSize = 0; if (mDegree == 180 || mDegree == degree) { minSize += contentSize; maxSize += contentSize; } if (isWidth) { if (transSize >= maxSize) { mReachRight = true; mReachLeft = false; } else if (transSize <= minSize) { mReachLeft = true; mReachRight = false; } else { mReachRight = false; mReachLeft = false; } } if (fixed) { transSize = transSize > maxSize ? maxSize : transSize; transSize = transSize < minSize ? minSize : transSize; } } return transSize; } /** * 得到合适的旋转角度 * * @param degree * @return */ private float ModifyDegree(float degree) { while (degree < 0) { degree += 360; } degree %= 360; if (degree > 45 && degree <= 315) { if (degree > 45 && degree <= 135) { mDegree += 90; } else if (degree > 135 && degree <= 225) { mDegree += 180; } else { mDegree += 270; } } mDegree %= 360; if (mListener != null) { mListener.onRotate(mDegree); } degree %= 90; if (degree > 45) { degree = 90 - degree; } else { degree = -degree; } return degree; } private class TouchListener implements OnTouchListener { private PointF lastP1 = new PointF(); private PointF lastP2 = new PointF(); private PointF curP1 = new PointF(); private PointF curP2 = new PointF(); private float deltaX, deltaY; private float totalDeltaX = 0; @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { if (!mIsTranslate && !mIsRotate && !mIsScale) { if (mNormalizedScale == 1 || getImageSize().getWidth() <= mViewWidth) { getParent().requestDisallowInterceptTouchEvent(false); } else { getParent().requestDisallowInterceptTouchEvent(true); } mScaleDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: if (mFling != null) { mFling.cancelFling(); } mState = State.ONE_POINT; lastP1.set(event.getX(), event.getY()); break; case MotionEvent.ACTION_MOVE: switch (mState) { case ONE_POINT: // 拖动 performDrag(event); break; case TWO_POINT: // 旋转 if (mIsRotateEnabled) { performRotate(event); } break; default: break; } if (((mReachRight && deltaX > 0) || (mReachLeft && deltaX < 0)) && Math.abs(totalDeltaX) < 100) { getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: mState = State.NONE; totalDeltaX = 0; break; case MotionEvent.ACTION_POINTER_DOWN: if (mState == State.ONE_POINT) { mState = State.TWO_POINT; lastP2.set(event.getX(1), event.getY(1)); } break; case MotionEvent.ACTION_POINTER_UP: mState = State.NONE; if (!mIsRotate) { // 自动旋转图片 remainDegree = ModifyDegree(remainDegree); mRotate = new Rotate(remainDegree); mRotate.start(); remainDegree = 0; } break; } setImageMatrix(mMatrix); } return true; } /** * 拖动图片 * * @param event */ private void performDrag(MotionEvent event) { curP1.set(event.getX(0), event.getY(0)); deltaX = curP1.x - lastP1.x; deltaY = curP1.y - lastP1.y; totalDeltaX += Math.abs(deltaX); fixTranslation(deltaX, deltaY); lastP1.set(curP1); if (mListener != null) { mListener.onDrag(deltaX, deltaY); } } /** * 旋转图片 * * @param event */ private void performRotate(MotionEvent event) { curP1.set(event.getX(0), event.getY(0)); curP2.set(event.getX(1), event.getY(1)); float curDegree = calculateDegree(lastP1, lastP2, curP1, curP2); lastP1.set(curP1); lastP2.set(curP2); mMatrix.postRotate(curDegree, mViewWidth / 2, mViewHeight / 2); remainDegree += curDegree; } /** * 根据两对坐标点计算旋转角度 * * @param a1 * @param b1 * @param a2 * @param b2 * @return */ private float calculateDegree(PointF a1, PointF b1, PointF a2, PointF b2) { float degree; double d1, d2; d1 = Math.atan((b1.y - a1.y) / (b1.x - a1.x)); d2 = Math.atan((b2.y - a2.y) / (b2.x - a2.x)); d1 = (float) (d1 * 180 / Math.PI); d2 = (float) (d2 * 180 / Math.PI); degree = (float) (d2 - d1); if (degree > 90) { degree = 180 - degree; } else if (degree < -90) { degree += 180; } return degree; } } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { public boolean onScale(ScaleGestureDetector detector) { scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY()); return true; } // public void onScaleEnd(ScaleGestureDetector detector) { // if (mNormalizedScale < 1 && !mIsScale) { // mScale = new Scale(1, mViewWidth / 2, mViewHeight / 2); // mScale.start(); // } // } } private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { return performClick(); } @Override public void onLongPress(MotionEvent e) { performLongClick(); } @Override public boolean onDoubleTap(MotionEvent e) { // 双击图片缩放的最大或最小 boolean consumed = false; if (mState == State.NONE && !mIsTranslate && !mIsRotate && !mIsScale) { float targetZoom = (mNormalizedScale == 1) ? mMaxScale : 1; mScale = new Scale(targetZoom, e.getX(), e.getY(), true); mScale.setDuration(500); mScale.start(); consumed = true; } return consumed; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mFling != null) { mFling.cancelFling(); } mFling = new Fling((int) velocityX, (int) velocityY); mFling.start(); return super.onFling(e1, e2, velocityX, velocityY); } } /** * 缩放图片 * * @param deltaScale * @param focusX * @param focusY */ private void scaleImage(float deltaScale, float focusX, float focusY) { float lowerScale, upperScale; lowerScale = mMinScale; upperScale = mMaxScale; float origScale = mNormalizedScale; mNormalizedScale *= deltaScale; if (mNormalizedScale > upperScale) { mNormalizedScale = upperScale; deltaScale = upperScale / origScale; } else if (mNormalizedScale < lowerScale) { mNormalizedScale = lowerScale; deltaScale = lowerScale / origScale; } if (mListener != null) { mListener.onScale(mNormalizedScale); } mMatrix.postScale(deltaScale, deltaScale, focusX, focusY); setImageMatrix(mMatrix); } private class Scale implements Runnable { private long startTime; private float startScale, targetScale; private float bitmapX, bitmapY; private Interpolator interpolator = new DecelerateInterpolator(); private PointF startTouch; private PointF endTouch; private float duration = DEFAULT_ANIM_TIME; private boolean isScale = false; private boolean fixTrans = false; public Scale(float targetScale, float focusX, float focusY, boolean fixTrans) { startScale = mNormalizedScale; this.targetScale = targetScale; PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); bitmapX = bitmapPoint.x; bitmapY = bitmapPoint.y; startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); endTouch = new PointF(mViewWidth / 2, mViewHeight / 2); this.fixTrans = fixTrans; } public void start() { startTime = System.currentTimeMillis(); mIsScale = true; isScale = true; postRunnable(this); } public void setDuration(float duration) { this.duration = duration; } @Override public void run() { float t = interpolate(); float deltaScale = calculateDeltaScale(t); scaleImage(deltaScale, bitmapX, bitmapY); translateImageToCenterTouchPosition(t); if (fixTrans) { fixTranslation(); } else { setImageMatrix(mMatrix); } if (mIsScale) { if (!isScale) { mIsScale = false; } postRunnable(this); } else { if (!fixTrans) { if (!mIsTranslate) { mTranslate = new Translate(); mTranslate.start(); } } } } private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) { float[] m = getMatrixValues(mMatrix); float origW = getDrawable().getIntrinsicWidth(); float origH = getDrawable().getIntrinsicHeight(); float transX = m[Matrix.MTRANS_X]; float transY = m[Matrix.MTRANS_Y]; Size size = getImageSize(); float finalX = ((x - transX) * origW) / size.getWidth(); float finalY = ((y - transY) * origH) / size.getHeight(); if (clipToBitmap) { finalX = Math.min(Math.max(finalX, 0), origW); finalY = Math.min(Math.max(finalY, 0), origH); } return new PointF(finalX, finalY); } private PointF transformCoordBitmapToTouch(float bx, float by) { float[] m = getMatrixValues(mMatrix); float origW = getDrawable().getIntrinsicWidth(); float origH = getDrawable().getIntrinsicHeight(); float px = bx / origW; float py = by / origH; Size size = getImageSize(); float finalX = m[Matrix.MTRANS_X] + size.getWidth() * px; float finalY = m[Matrix.MTRANS_Y] + size.getHeight() * py; return new PointF(finalX, finalY); } private void translateImageToCenterTouchPosition(float t) { float targetX = startTouch.x + t * (endTouch.x - startTouch.x); float targetY = startTouch.y + t * (endTouch.y - startTouch.y); PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); mMatrix.postTranslate(targetX - curr.x, targetY - curr.y); } private float interpolate() { long currTime = System.currentTimeMillis(); float elapsed = (currTime - startTime) / duration; elapsed = Math.min(1f, elapsed); if (elapsed >= 1) { isScale = false; } return interpolator.getInterpolation(elapsed); } private float calculateDeltaScale(float t) { float zoom = startScale + t * (targetScale - startScale); return zoom / mNormalizedScale; } } private class Translate implements Runnable { private float beginTransX, beginTransY; private float endTransX, endTransY; private float curTransX, curTransY; private Interpolator interpolator = new DecelerateInterpolator(); private long startTime; private Size size; private float duration = DEFAULT_ANIM_TIME; private boolean isTranslate = false; public Translate() { this(mNormalizedScale); } public Translate(float scaleFactor) { size = getImageSize(scaleFactor); updateBeginAndEnd(); } public void updateBeginAndEnd() { float[] matrixValues = getMatrixValues(mMatrix); beginTransX = matrixValues[Matrix.MTRANS_X]; beginTransY = matrixValues[Matrix.MTRANS_Y]; endTransX = getFixTrans(beginTransX, size.getWidth(), mViewWidth, true); endTransY = getFixTrans(beginTransY, size.getHeight(), mViewHeight, false); } public void start() { if (beginTransX == endTransX && beginTransY == endTransY) { return; } mIsTranslate = true; isTranslate = true; startTime = System.currentTimeMillis(); postRunnable(this); } private void interpolateTrans() { float elapsed = (System.currentTimeMillis() - startTime) / duration; elapsed = Math.min(1f, elapsed); if (elapsed >= 1) { isTranslate = false; } float percent = interpolator.getInterpolation(elapsed); curTransX = beginTransX + (endTransX - beginTransX) * percent; curTransY = beginTransY + (endTransY - beginTransY) * percent; } @Override public void run() { interpolateTrans(); float[] matrixValues = getMatrixValues(mMatrix); matrixValues[Matrix.MTRANS_X] = curTransX; matrixValues[Matrix.MTRANS_Y] = curTransY; mMatrix.setValues(matrixValues); setImageMatrix(mMatrix); if (mIsTranslate) { if (!isTranslate) { mIsTranslate = false; } postRunnable(this); } } } private class Rotate implements Runnable { private float totalDegree; private float lastDegree = 0; private Interpolator interpolator = new DecelerateInterpolator(); private long startTime; private float focusX, focusY; private boolean isRotate = false; public Rotate(float d) { this(d, mViewWidth / 2, mViewHeight / 2); } public Rotate(float d, float focusX, float focusY) { totalDegree = d; this.focusX = focusX; this.focusY = focusY; } public void start() { mIsRotate = true; isRotate = true; startTime = System.currentTimeMillis(); postRunnable(this); } private float interpolateDegree() { float elapsed = (System.currentTimeMillis() - startTime) / DEFAULT_ANIM_TIME; elapsed = Math.min(1f, elapsed); if (elapsed >= 1) { isRotate = false; } float curDegree = totalDegree * interpolator.getInterpolation(elapsed); float deltaDegree = curDegree - lastDegree; lastDegree = curDegree; return deltaDegree; } @Override public void run() { float deltaDegree = interpolateDegree(); mMatrix.postRotate(deltaDegree, focusX, focusY); setImageMatrix(mMatrix); if (mIsRotate) { if (!isRotate) { mIsRotate = false; } postRunnable(this); } else { if (mNormalizedScale < 1 && !mIsScale) { mScale = new Scale(1, mViewWidth / 2, mViewHeight / 2, false); mScale.start(); return; } if (!mIsTranslate) { mTranslate = new Translate(); mTranslate.start(); } } } } private class Fling implements Runnable { private Scroller scroller = null; private int lastX, lastY; public Fling(int velocityX, int velocityY) { scroller = new Scroller(getContext()); float[] mtrixValues = getMatrixValues(mMatrix); Size size = getImageSize(); int startX = (int) mtrixValues[Matrix.MTRANS_X]; int startY = (int) mtrixValues[Matrix.MTRANS_Y]; int minX, maxX, minY, maxY; if (size.getWidth() > mViewWidth) { minX = mViewWidth - (int) size.getWidth(); maxX = 0; if (mDegree == 90 || mDegree == 180) { minX += size.getWidth(); maxX += size.getWidth(); } } else { minX = maxX = startX; } if (size.getHeight() > mViewHeight) { minY = mViewHeight - (int) size.getHeight(); maxY = 0; if (mDegree == 270 || mDegree == 180) { minY += size.getHeight(); maxY += size.getHeight(); } } else { minY = maxY = startY; } scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); lastX = startX; lastY = startY; } public void start() { postRunnable(this); } public void cancelFling() { if (scroller != null) { scroller.forceFinished(true); } } @Override public void run() { if (scroller.isFinished()) { scroller = null; return; } if (scroller.computeScrollOffset()) { int newX = scroller.getCurrX(); int newY = scroller.getCurrY(); int deltaX = newX - lastX; int deltaY = newY - lastY; lastX = newX; lastY = newY; if (mListener != null) { mListener.onFling(deltaX, deltaY); } fixTranslation(deltaX, deltaY); postRunnable(this); } else { scroller = null; } } } private void postRunnable(Runnable runnable) { postDelayed(runnable, 1000 / 60); } private class Size { private float mWidth; private float mHeight; public Size(float width, float height) { mWidth = width; mHeight = height; } public float getWidth() { return mWidth; } public float getHeight() { return mHeight; } } }