package com.BeeFramework.view; import android.content.Context; import android.graphics.*; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.FloatMath; import android.util.Log; import android.view.MotionEvent; import android.view.View; import com.BeeFramework.AppConst; /* * ______ ______ ______ * /\ __ \ /\ ___\ /\ ___\ * \ \ __< \ \ __\_ \ \ __\_ * \ \_____\ \ \_____\ \ \_____\ * \/_____/ \/_____/ \/_____/ * * * Copyright (c) 2013-2014, {Bee} open source community * http://www.bee-framework.com * * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ public class TouchableImageView extends WebImageView implements View.OnTouchListener{ private static boolean DEBUG = AppConst.DEBUG; private static String TAG = "image"; private static final int TOUCH_MODE_NONE = 0; private static final int TOUCH_MODE_DRAG = 1; private static final int TOUCH_MODE_ZOOM = 2; private static final int PIC_OUTOF_BOUND_X_Y = 0; private static final int PIC_OUTOF_BOUND_Y = 1; private static final int PIC_OUTOF_BOUND_X = 2; private static final int PIC_IN_BOUND_X_Y = 3; private static final int MSG_ON_CLICK = 1; private static final int MSG_ON_CLICK_DELAY = 250; private Context mContext; private Bitmap mBitmap = null; private int mTouchMode = TOUCH_MODE_NONE; private boolean mMoved = false; private OnClickListener mOnClickListener = null; private int mWLimit = 0; private int mHLimit = 0; private int mPaddingX = 0; private int mPaddingY = 0; private Matrix currentMatrix = new Matrix(); private Matrix savedMatrix = new Matrix(); private PointF startPoint = new PointF(); private PointF midPoint = new PointF(); private float oldDist = 0; private RectF mWindowRect = null; private RectF mImageRect = new RectF();; private float minScale = 0.5f; private float maxScale = 5; private int bmpWidth; private int bmpHeight; private boolean mRevertXY = false; private int mRotatedDegree = 0; private Handler mHandle = null; public TouchableImageView(Context context) { super(context); setScaleType(ScaleType.MATRIX); init(context); } public TouchableImageView(Context context, AttributeSet as) { super(context, as); setScaleType(ScaleType.CENTER); init(context); } private void init(Context context) { mContext = context; mHandle = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == MSG_ON_CLICK && mOnClickListener != null) { mOnClickListener.onClick(TouchableImageView.this); } } }; setOnTouchListener(this); } public void setMinScale(float minScale) { this.minScale = minScale; } private RectF getWindowRect() { if (mWindowRect == null) { mWindowRect = new RectF(mPaddingX, mPaddingY, mWLimit - mPaddingX, mHLimit - mPaddingY); } return mWindowRect; } private void resetImageRect() { mImageRect.left = 0; mImageRect.top = 0; mImageRect.right = bmpWidth; mImageRect.bottom = bmpHeight; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); getWindowRect(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mHLimit == 0 || mWLimit == 0) { mHLimit = bottom - top; mWLimit = right - left; setVisableRectLimit(mPaddingX, mPaddingY); resetImageBitmap(mBitmap); } else { mWindowRect = null; mHLimit = bottom - top; mWLimit = right - left; fixPositionCenter(false); fixPositionLimit(); setImageMatrix(currentMatrix); } } public void rotateRight() { if (mBitmap == null) { return; } mRevertXY = !mRevertXY; mRotatedDegree = (mRotatedDegree + 90) % 360; RectF windowRect = getWindowRect(); currentMatrix.postRotate(90, windowRect.centerX(), windowRect.centerY()); fixPositionLimit(); fixPositionCenter(false); setImageMatrix(currentMatrix); } public void rotateLeft() { if (mBitmap == null) { return; } mRevertXY = !mRevertXY; mRotatedDegree = (mRotatedDegree + 270) % 360; RectF windowRect = getWindowRect(); currentMatrix.postRotate(270, windowRect.centerX(), windowRect.centerY()); fixPositionLimit(); fixPositionCenter(false); setImageMatrix(currentMatrix); } private void autoFixImagePosition() { if (bmpHeight > mHLimit) { float heightDivWidth = bmpHeight / bmpWidth; if (heightDivWidth >= 3) { fixImageAlignTop(); } else { float scale = (float) mHLimit / bmpHeight; scale = Math.max(scale, minScale); currentMatrix.postScale(scale, scale); fixPositionCenter(true); } } } private void setImageBitmap() { mWindowRect = null; super.setImageBitmap(mBitmap); bmpWidth = mBitmap.getWidth(); bmpHeight = mBitmap.getHeight(); currentMatrix.reset(); resetImageRect(); setScaleType(ScaleType.MATRIX); fixPositionLimit(); fixPositionCenter(true); autoFixImagePosition(); setImageMatrix(currentMatrix); savedMatrix.set(currentMatrix); } public void resetImageBitmap(Bitmap bitmap) { if (bitmap != null) { mBitmap = bitmap; setImageBitmap(); } } public void setImageBitmap(Bitmap bitmap) { if (mBitmap == null && bitmap != null) { mBitmap = bitmap; setImageBitmap(); } } @Override public void setOnClickListener(OnClickListener l) { mOnClickListener = l; } @Override public boolean onTouch(View v, MotionEvent event) { if (mBitmap == null) { return true; } RectF curImageRect = new RectF(); currentMatrix.mapRect(curImageRect, mImageRect); if (DEBUG) { Log.i(TAG, "-------------------------------------------"); } int action = event.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: if (DEBUG) { Log.i(TAG, "-------ACTION_DOWN-------"); } mMoved = false; savedMatrix.set(currentMatrix); startPoint.set(event.getX(), event.getY()); mTouchMode = TOUCH_MODE_DRAG; break; case MotionEvent.ACTION_POINTER_DOWN: if (DEBUG) { Log.i(TAG, "-------ACTION_POINTER_DOWN-------"); } mMoved = true; oldDist = spacing(event); if (oldDist > 6f) { savedMatrix.set(currentMatrix); midPoint = setMidPoint(midPoint, event); mTouchMode = TOUCH_MODE_ZOOM; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: if (DEBUG && action == MotionEvent.ACTION_UP) { Log.i(TAG, "-------ACTION_UP-------"); } else if (DEBUG && action == MotionEvent.ACTION_POINTER_UP) { Log.i(TAG, "-------ACTION_POINTER_DOWN-------"); } mTouchMode = TOUCH_MODE_NONE; savedMatrix.set(currentMatrix); if (!mMoved) { if (mHandle.hasMessages(MSG_ON_CLICK)) { // onDoubleClick mHandle.removeMessages(MSG_ON_CLICK); currentMatrix.reset(); resetImageBitmap(mBitmap); } else { mHandle.sendEmptyMessageDelayed(MSG_ON_CLICK, MSG_ON_CLICK_DELAY); } } return true; // break; case MotionEvent.ACTION_MOVE: if (DEBUG) { Log.i(TAG, "-------ACTION_MOVE-------"); } if (!mMoved && Math.abs(startPoint.x - event.getX()) >= 2 && Math.abs(startPoint.y - event.getY()) >= 2) { mMoved = true; } currentMatrix.set(savedMatrix); if (mTouchMode == TOUCH_MODE_DRAG) { if (DEBUG) { Log.i(TAG, "-------ACTION_MOVE: DRAG-------"); } float[] values = new float[9]; currentMatrix.getValues(values); float scale = Math.max(getScaleX(values), 1f); switch (getBitmapState(0)) { case PIC_OUTOF_BOUND_Y: currentMatrix.postTranslate(0, (event.getY() - startPoint.y) * scale); break; case PIC_OUTOF_BOUND_X: currentMatrix.postTranslate((event.getX() - startPoint.x) * scale, 0); break; case PIC_OUTOF_BOUND_X_Y: currentMatrix.postTranslate((event.getX() - startPoint.x) * scale, (event.getY() - startPoint.y) * scale); break; // case PIC_IN_BOUND_X_Y: // if (mEnableOutofBoundXY) { // currentMatrix.postTranslate((event.getX() - startPoint.x) * scale, (event.getY() - startPoint.y) * scale); // } // break; } } else if (mTouchMode == TOUCH_MODE_ZOOM) { if (DEBUG) { Log.i(TAG, "-------ACTION_MOVE: ZOOM-------"); } float newDist = spacing(event); if (newDist > 10f) { float scale = newDist / oldDist; currentMatrix.postScale(scale, scale, midPoint.x, midPoint.y); float[] values = new float[9]; currentMatrix.getValues(values); float s = getScaleX(values); if (s < minScale) { currentMatrix.postScale(minScale/s, minScale/s, midPoint.x, midPoint.y); } else if (s > maxScale) { currentMatrix.postScale(maxScale/s, maxScale/s, midPoint.x, midPoint.y); } if (DEBUG) { Log.i(TAG, "-------1-------" + currentMatrix.toString()); } fixPositionCenter(false); if (DEBUG) { Log.i(TAG, "-------2-------" + currentMatrix.toString()); } } } if (DEBUG) { Log.i(TAG, "-------3-------" + currentMatrix.toString()); } fixPositionLimit(); if (DEBUG) { Log.i(TAG, "-------4-------" + currentMatrix.toString()); } break; } if (mBitmap == null) { currentMatrix.reset(); } else if (!getImageMatrix().equals(currentMatrix)) { if (DEBUG) { Log.i(TAG, "-------5------- from " + getImageMatrix().toString() + " to " + currentMatrix.toShortString()); } Log.i("zoom", "-------6-------" + currentMatrix.toString()); setImageMatrix(currentMatrix); mMoved = true; } if (DEBUG) { Log.i(TAG, "-------------------------------------------"); } return true; } private int getBitmapState(float scale) { float w, h; int result = PIC_OUTOF_BOUND_X_Y; // if (mEnableOutofBoundXY) { // return PIC_IN_BOUND_X_Y; // } if (scale == 0) { float[] values = new float[9]; currentMatrix.getValues(values); w = bmpWidth * getScaleX(values); h = bmpHeight * getScaleY(values); } else { w = bmpWidth * scale; h = bmpHeight * scale; } if (mRevertXY) { float t = w; w = h; h = t; } RectF wr = getWindowRect(); if (w <= wr.width()) { result = (h <= wr.height()) ? PIC_IN_BOUND_X_Y : PIC_OUTOF_BOUND_Y; } else if (h <= wr.height()) { result = PIC_OUTOF_BOUND_X; } return result; } private float spacing(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return FloatMath.sqrt(x * x + y * y); } private PointF setMidPoint(PointF point, MotionEvent event) { float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); point.set(x / 2, y / 2); return point; } private void fixPositionLimit() { float w, h; float[] values = new float[9]; currentMatrix.getValues(values); w = bmpWidth * getScaleX(values); h = bmpHeight * getScaleY(values); if (mRevertXY) { float t = w; w = h; h = t; } RectF curImageRect = new RectF(); RectF windowRect = getWindowRect(); currentMatrix.mapRect(curImageRect, mImageRect); float deltaX = 0; float deltaY = 0; if (curImageRect.width() > windowRect.width()) { if (curImageRect.left > windowRect.left) { deltaX = windowRect.left - curImageRect.left; } else if (curImageRect.right < windowRect.right) { deltaX = windowRect.right - curImageRect.right; } } if (curImageRect.height() > windowRect.height()) { if (curImageRect.top > windowRect.top) { deltaY = windowRect.top - curImageRect.top; } else if (curImageRect.bottom < windowRect.bottom) { deltaY = windowRect.bottom - curImageRect.bottom; } } if (deltaX != 0 || deltaY != 0) { currentMatrix.postTranslate(deltaX, deltaY); } } private void fixPositionCenter(boolean force) { float w, h; float[] values = new float[9]; currentMatrix.getValues(values); w = bmpWidth * getScaleX(values); h = bmpHeight * getScaleY(values); if (mRevertXY) { float t = w; w = h; h = t; } if (force || w <= mWLimit - mPaddingX - mPaddingX) { float tX = mWLimit / 2 - w / 2; float post = tX - values[Matrix.MTRANS_X] + getRotatedDeltaX(w, values); currentMatrix.postTranslate(post, 0); midPoint.x = mWLimit / 2; } if (force || h <= mHLimit - mPaddingY - mPaddingY) { float tY = mHLimit / 2 - h / 2; float post = tY - values[Matrix.MTRANS_Y] + getRotatedDeltaY(h, values); currentMatrix.postTranslate(0, post); midPoint.y = mHLimit / 2; } } private void fixImageAlignTop() { float[] values = new float[9]; currentMatrix.getValues(values); float deltaY = values[Matrix.MTRANS_Y]; if (deltaY < 0) { currentMatrix.postTranslate(0, -deltaY); } } private float calculateFixedScale(int widthLimit, int heightLimit) { float scale; boolean divWithScreen = ((float) bmpWidth / bmpHeight) > (float) widthLimit / heightLimit; if (divWithScreen) { scale = (float) widthLimit / bmpWidth; } else { scale = (float) heightLimit / bmpHeight; } scale = Math.max(scale, minScale); scale = Math.min(scale, maxScale); return scale; } public void setImageFullScreenAndFitCenter() { float scale = calculateFixedScale(mWLimit, mHLimit); float[] values = new float[9]; currentMatrix.getValues(values); scale /= getScaleX(values); currentMatrix.postScale(scale, scale); fixPositionCenter(true); setImageMatrix(currentMatrix); } public void setImageFullWindow() { getWindowRect(); float scale = calculateFixedScale((int)mWindowRect.width(), (int)mWindowRect.height()); float[] values = new float[9]; currentMatrix.getValues(values); scale /= getScaleX(values); currentMatrix.postScale(scale, scale); fixPositionCenter(true); setImageMatrix(currentMatrix); } private float getRotatedDeltaX(float width, float[] values) { float x = Math.min(values[Matrix.MSCALE_X], values[Matrix.MSKEW_X]); return x < 0 ? width : 0; } private float getRotatedDeltaY(float height, float[] values) { float y = Math.min(values[Matrix.MSCALE_Y], values[Matrix.MSKEW_Y]); return y < 0 ? height : 0; } private float getScaleX(float[] values) { return Math.abs(mRevertXY ? values[Matrix.MSKEW_X] : values[Matrix.MSCALE_X]); } private float getScaleY(float[] values) { return Math.abs(mRevertXY ? values[Matrix.MSKEW_Y] : values[Matrix.MSCALE_Y]); } public Bitmap getBitmap() { return mBitmap; } public void setVisableRectLimit(int paddingX, int paddingY) { mPaddingX = paddingX; mPaddingY = paddingY; if (mPaddingX != 0 || mPaddingY != 0) { resetImageRect(); mWindowRect = new RectF(mPaddingX, mPaddingY, mWLimit - mPaddingX, mHLimit - mPaddingY); } else { mWindowRect = null; } } public Bitmap getBitmapInWindow() { final int X = 0; final int Y = 1; Bitmap output = null; getWindowRect(); if (mBitmap != null && mWindowRect != null) { float [] imageLeftTop = new float [] { mImageRect.left, mImageRect.top }; float [] windowLeftTop = new float [2]; if (mRotatedDegree % 360 == 0) { windowLeftTop[X] = mWindowRect.left; windowLeftTop[Y] = mWindowRect.top; } else if (mRotatedDegree % 360 == 90) { windowLeftTop[X] = mWindowRect.right; windowLeftTop[Y] = mWindowRect.top; } else if (mRotatedDegree % 360 == 180) { windowLeftTop[X] = mWindowRect.right; windowLeftTop[Y] = mWindowRect.bottom; } else if (mRotatedDegree % 360 == 270) { windowLeftTop[X] = mWindowRect.left; windowLeftTop[Y] = mWindowRect.bottom; } currentMatrix.mapPoints(imageLeftTop); float[] values = new float[9]; currentMatrix.getValues(values); float scaleX = mRevertXY ? values[Matrix.MSKEW_X] : values[Matrix.MSCALE_X]; float scaleY = mRevertXY ? values[Matrix.MSKEW_Y] : values[Matrix.MSCALE_Y]; float x = (windowLeftTop[X] - imageLeftTop[X]) / scaleX; float y = (windowLeftTop[Y] - imageLeftTop[Y]) / scaleY; if (mRevertXY) { float t = x; x = y; y = t; } scaleX = Math.abs(scaleX); scaleY = Math.abs(scaleY); int outputWidth = (int)(mWindowRect.width() / scaleX); int outputHeight = (int)(mWindowRect.height() / scaleY); output = Bitmap.createBitmap(outputWidth, outputHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); canvas.drawBitmap(mBitmap, new Rect((int)x, (int)y, (int)(x + outputWidth), (int)(y + outputHeight)), new RectF(0, 0, output.getWidth(), output.getHeight()), new Paint()); if (mRotatedDegree % 360 != 0) { Bitmap outputRotate = output; output = Bitmap.createBitmap(outputWidth, outputHeight, Bitmap.Config.ARGB_8888); canvas = new Canvas(output); Matrix rotate = new Matrix(); rotate.postRotate(mRotatedDegree, outputWidth / 2, outputHeight / 2); canvas.drawBitmap(outputRotate, rotate, new Paint()); } } return output; } public void recycle() { mBitmap = null; } }