package com.topnews.view.imageshow; import java.lang.ref.WeakReference; import java.util.Timer; import java.util.TimerTask; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.PointF; import android.os.Build; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.FloatMath; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.widget.ImageView; import android.widget.ImageView.ScaleType; /** * ����Ŵ��ImageView */ @SuppressLint("NewApi") public class TouchImageView extends ImageView { // private static final String TAG = "Touch"; // These matrices will be used to move and zoom image Matrix matrix = new Matrix(); Matrix savedMatrix = new Matrix(); static final long DOUBLE_PRESS_INTERVAL = 600; static final float FRICTION = 0.9f; // We can be in one of these 4 states static final int NONE = 0; static final int DRAG = 1; static final int ZOOM = 2; static final int CLICK = 10; int mode = NONE; float redundantXSpace, redundantYSpace; float right, bottom, origWidth, origHeight, bmWidth, bmHeight; float width, height; PointF last = new PointF(); PointF mid = new PointF(); PointF start = new PointF(); float[] m; float matrixX, matrixY; float saveScale = 1f; float minScale = 1f; float maxScale = 3f; float oldDist = 1f; PointF lastDelta = new PointF(0, 0); float velocity = 0; long lastPressTime = 0, lastDragTime = 0; boolean allowInert = false; private Context mContext; private Timer mClickTimer; private OnClickListener mOnClickListener; private Object mScaleDetector; private Handler mTimerHandler = null; // Scale mode on DoubleTap private boolean zoomToOriginalSize = false; public boolean isZoomToOriginalSize() { return this.zoomToOriginalSize; } public void setZoomToOriginalSize(boolean zoomToOriginalSize) { this.zoomToOriginalSize = zoomToOriginalSize; } public boolean onLeftSide = false, onTopSide = false, onRightSide = false, onBottomSide = false; public TouchImageView(Context context) { super(context); super.setClickable(true); this.mContext = context; init(); } public TouchImageView(Context context, AttributeSet attrs) { super(context, attrs); super.setClickable(true); this.mContext = context; init(); } protected void init() { mTimerHandler = new TimeHandler(this); matrix.setTranslate(1f, 1f); m = new float[9]; setImageMatrix(matrix); setScaleType(ScaleType.MATRIX); if (Build.VERSION.SDK_INT >= 8) { mScaleDetector = new ScaleGestureDetector(mContext, new ScaleListener()); } setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent rawEvent) { WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent); if (mScaleDetector != null) { ((ScaleGestureDetector) mScaleDetector) .onTouchEvent(rawEvent); } fillMatrixXY(); PointF curr = new PointF(event.getX(), event.getY()); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: allowInert = false; savedMatrix.set(matrix); last.set(event.getX(), event.getY()); start.set(last); mode = DRAG; break; case MotionEvent.ACTION_POINTER_DOWN: oldDist = spacing(event); // Log.d(TAG, "oldDist=" + oldDist); if (oldDist > 10f) { savedMatrix.set(matrix); midPoint(mid, event); mode = ZOOM; // Log.d(TAG, "mode=ZOOM"); } break; case MotionEvent.ACTION_UP: allowInert = true; mode = NONE; int xDiff = (int) Math.abs(event.getX() - start.x); int yDiff = (int) Math.abs(event.getY() - start.y); if (xDiff < CLICK && yDiff < CLICK) { // Perform scale on double click long pressTime = System.currentTimeMillis(); if (pressTime - lastPressTime <= DOUBLE_PRESS_INTERVAL) { if (mClickTimer != null) mClickTimer.cancel(); if (saveScale == 1) { final float targetScale = maxScale / saveScale; matrix.postScale(targetScale, targetScale, start.x, start.y); saveScale = maxScale; } else { matrix.postScale(minScale / saveScale, minScale / saveScale, width / 2, height / 2); saveScale = minScale; } calcPadding(); checkAndSetTranslate(0, 0); lastPressTime = 0; } else { lastPressTime = pressTime; mClickTimer = new Timer(); mClickTimer.schedule(new Task(), 300); } if (saveScale == minScale) { scaleMatrixToBounds(); } } break; case MotionEvent.ACTION_POINTER_UP: mode = NONE; velocity = 0; savedMatrix.set(matrix); oldDist = spacing(event); // Log.d(TAG, "mode=NONE"); break; case MotionEvent.ACTION_MOVE: allowInert = false; if (mode == DRAG) { float deltaX = curr.x - last.x; float deltaY = curr.y - last.y; long dragTime = System.currentTimeMillis(); velocity = (float) distanceBetween(curr, last) / (dragTime - lastDragTime) * FRICTION; lastDragTime = dragTime; checkAndSetTranslate(deltaX, deltaY); lastDelta.set(deltaX, deltaY); last.set(curr.x, curr.y); } else if (mScaleDetector == null && mode == ZOOM) { float newDist = spacing(event); if (rawEvent.getPointerCount() < 2) break; // There is one serious trouble: when you scaling with // two fingers, then pick up first finger of gesture, // ACTION_MOVE being called. // Magic number 50 for this case if (10 > Math.abs(oldDist - newDist) || Math.abs(oldDist - newDist) > 50) break; float mScaleFactor = newDist / oldDist; oldDist = newDist; float origScale = saveScale; saveScale *= mScaleFactor; if (saveScale > maxScale) { saveScale = maxScale; mScaleFactor = maxScale / origScale; } else if (saveScale < minScale) { saveScale = minScale; mScaleFactor = minScale / origScale; } calcPadding(); if (origWidth * saveScale <= width || origHeight * saveScale <= height) { matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2); if (mScaleFactor < 1) { fillMatrixXY(); if (mScaleFactor < 1) { scaleMatrixToBounds(); } } } else { PointF mid = midPointF(event); matrix.postScale(mScaleFactor, mScaleFactor, mid.x, mid.y); fillMatrixXY(); if (mScaleFactor < 1) { if (matrixX < -right) matrix.postTranslate(-(matrixX + right), 0); else if (matrixX > 0) matrix.postTranslate(-matrixX, 0); if (matrixY < -bottom) matrix.postTranslate(0, -(matrixY + bottom)); else if (matrixY > 0) matrix.postTranslate(0, -matrixY); } } checkSiding(); } break; } setImageMatrix(matrix); invalidate(); return false; } }); } public void resetScale() { fillMatrixXY(); matrix.postScale(minScale / saveScale, minScale / saveScale, width / 2, height / 2); saveScale = minScale; calcPadding(); checkAndSetTranslate(0, 0); scaleMatrixToBounds(); setImageMatrix(matrix); invalidate(); } public boolean pagerCanScroll() { if (mode != NONE) return false; return saveScale == minScale; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!allowInert) return; final float deltaX = lastDelta.x * velocity, deltaY = lastDelta.y * velocity; if (deltaX > width || deltaY > height) { return; } velocity *= FRICTION; if (Math.abs(deltaX) < 0.1 && Math.abs(deltaY) < 0.1) return; checkAndSetTranslate(deltaX, deltaY); setImageMatrix(matrix); } private void checkAndSetTranslate(float deltaX, float deltaY) { float scaleWidth = Math.round(origWidth * saveScale); float scaleHeight = Math.round(origHeight * saveScale); fillMatrixXY(); if (scaleWidth < width) { deltaX = 0; if (matrixY + deltaY > 0) deltaY = -matrixY; else if (matrixY + deltaY < -bottom) deltaY = -(matrixY + bottom); } else if (scaleHeight < height) { deltaY = 0; if (matrixX + deltaX > 0) deltaX = -matrixX; else if (matrixX + deltaX < -right) deltaX = -(matrixX + right); } else { if (matrixX + deltaX > 0) deltaX = -matrixX; else if (matrixX + deltaX < -right) deltaX = -(matrixX + right); if (matrixY + deltaY > 0) deltaY = -matrixY; else if (matrixY + deltaY < -bottom) deltaY = -(matrixY + bottom); } matrix.postTranslate(deltaX, deltaY); checkSiding(); } private void checkSiding() { fillMatrixXY(); // Log.d(TAG, "x: " + matrixX + " y: " + matrixY + " left: " + right / 2 // + " top:" + bottom / 2); float scaleWidth = Math.round(origWidth * saveScale); float scaleHeight = Math.round(origHeight * saveScale); onLeftSide = onRightSide = onTopSide = onBottomSide = false; if (-matrixX < 10.0f) onLeftSide = true; // Log.d("GalleryViewPager", // String.format("ScaleW: %f; W: %f, MatrixX: %f", scaleWidth, width, // matrixX)); if ((scaleWidth >= width && (matrixX + scaleWidth - width) < 10) || (scaleWidth <= width && -matrixX + scaleWidth <= width)) onRightSide = true; if (-matrixY < 10.0f) onTopSide = true; if (Math.abs(-matrixY + height - scaleHeight) < 10.0f) onBottomSide = true; } private void calcPadding() { right = width * saveScale - width - (2 * redundantXSpace * saveScale); bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); } private void fillMatrixXY() { matrix.getValues(m); matrixX = m[Matrix.MTRANS_X]; matrixY = m[Matrix.MTRANS_Y]; } private void scaleMatrixToBounds() { if (Math.abs(matrixX + right / 2) > 0.5f) matrix.postTranslate(-(matrixX + right / 2), 0); if (Math.abs(matrixY + bottom / 2) > 0.5f) matrix.postTranslate(0, -(matrixY + bottom / 2)); } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); bmWidth = bm.getWidth(); bmHeight = bm.getHeight(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); // Fit to screen. float scale; float scaleX = width / bmWidth; float scaleY = height / bmHeight; scale = Math.min(scaleX, scaleY); matrix.setScale(scale, scale); // minScale = scale; setImageMatrix(matrix); saveScale = 1f; // Center the image redundantYSpace = height - (scale * bmHeight); redundantXSpace = width - (scale * bmWidth); redundantYSpace /= (float) 2; redundantXSpace /= (float) 2; matrix.postTranslate(redundantXSpace, redundantYSpace); origWidth = width - 2 * redundantXSpace; origHeight = height - 2 * redundantYSpace; calcPadding(); setImageMatrix(matrix); } private double distanceBetween(PointF left, PointF right) { return Math.sqrt(Math.pow(left.x - right.x, 2) + Math.pow(left.y - right.y, 2)); } /** Determine the space between the first two fingers */ private float spacing(WrapMotionEvent event) { // ... float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return FloatMath.sqrt(x * x + y * y); } /** Calculate the mid point of the first two fingers */ private void midPoint(PointF point, WrapMotionEvent event) { // ... float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); point.set(x / 2, y / 2); } private PointF midPointF(WrapMotionEvent event) { // ... float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); return new PointF(x / 2, y / 2); } @Override public void setOnClickListener(OnClickListener l) { mOnClickListener = l; } private class Task extends TimerTask { public void run() { mTimerHandler.sendEmptyMessage(0); } } @SuppressLint("NewApi") private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { mode = ZOOM; return true; } @Override public boolean onScale(ScaleGestureDetector detector) { float mScaleFactor = (float) Math.min( Math.max(.95f, detector.getScaleFactor()), 1.05); float origScale = saveScale; saveScale *= mScaleFactor; if (saveScale > maxScale) { saveScale = maxScale; mScaleFactor = maxScale / origScale; } else if (saveScale < minScale) { saveScale = minScale; mScaleFactor = minScale / origScale; } right = width * saveScale - width - (2 * redundantXSpace * saveScale); bottom = height * saveScale - height - (2 * redundantYSpace * saveScale); if (origWidth * saveScale <= width || origHeight * saveScale <= height) { matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2); if (mScaleFactor < 1) { matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; if (mScaleFactor < 1) { if (Math.round(origWidth * saveScale) < width) { if (y < -bottom) matrix.postTranslate(0, -(y + bottom)); else if (y > 0) matrix.postTranslate(0, -y); } else { if (x < -right) matrix.postTranslate(-(x + right), 0); else if (x > 0) matrix.postTranslate(-x, 0); } } } } else { matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY()); matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; if (mScaleFactor < 1) { if (x < -right) matrix.postTranslate(-(x + right), 0); else if (x > 0) matrix.postTranslate(-x, 0); if (y < -bottom) matrix.postTranslate(0, -(y + bottom)); else if (y > 0) matrix.postTranslate(0, -y); } } return true; } } static class TimeHandler extends Handler { private final WeakReference<TouchImageView> mService; TimeHandler(TouchImageView view) { mService = new WeakReference<TouchImageView>(view); } @Override public void handleMessage(Message msg) { mService.get().performClick(); if (mService.get().mOnClickListener != null) mService.get().mOnClickListener.onClick(mService.get()); } } // public interface SetOnToch { // public void onSetOnTochLeft(); // public void onSetOnTochRight(); // public void onSetOnTochCentre(); // } }