package com.luckymonkey.widget;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
public class ZoomableImageView extends ImageView {
private int pos = 0;
private int count = 0;
private float maxScale = 3f;
private float minScale = 1f;
private enum State {
INIT, DRAG, ZOOM
}
private State state;
private Matrix matrix;
private float[] finalTransformation = new float[9];
private PointF last = new PointF();
private float currentScale = 1f;
private int viewWidth;
private int viewHeight;
private float afterScaleDrawableWidth;
private float afterScaleDrawableHeight;
private ScaleGestureDetector scaleDetector;
private GestureDetector doubleTapDetecture;
public ZoomableImageView(Context context) {
super(context);
setUp(context);
}
public ZoomableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setUp(context);
}
public ZoomableImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setUp(context);
}
public void setPos(int pos, int count) {
this.pos = pos;
this.count = count;
}
public float getCurrentScale() {
return currentScale;
}
public void setCurrentScale(float currentScale) {
this.currentScale = currentScale;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
// Set up drawable at first load
if (hasDrawable()) {
currentScale = 1f;
resetImage();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
scaleDetector.onTouchEvent(event);
doubleTapDetecture.onTouchEvent(event);
PointF current = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(current);
state = State.DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (state == State.DRAG) {
drag(current);
last.set(current);
}
break;
case MotionEvent.ACTION_UP:
state = State.INIT;
break;
case MotionEvent.ACTION_POINTER_UP:
state = State.INIT;
break;
}
setImageMatrix(matrix);
invalidate();
return true;
}
/**
* Set up the class. Method called by constructors.
*
* @param context
*/
private void setUp(Context context) {
super.setClickable(false);
matrix = new Matrix();
state = State.INIT;
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
doubleTapDetecture = new GestureDetector(context, new GestureListener());
setScaleType(ScaleType.MATRIX);
}
private void resetImage() {
// Scale Image
float scale = getScaleForDrawable();
matrix.setScale(scale, scale);
// Center Image
float marginY = ((float) viewHeight - (scale * getDrawable()
.getIntrinsicHeight())) / 2;
float marginX = ((float) viewWidth - (scale * getDrawable()
.getIntrinsicWidth())) / 2;
matrix.postTranslate(marginX, marginY);
afterScaleDrawableWidth = (float) viewWidth - 2 * marginX;
afterScaleDrawableHeight = (float) viewHeight - 2 * marginY;
setImageMatrix(matrix);
}
/**
* Getter and setter for max/min scale. Default are min=1 and max=3
*/
public float getMaxScale() {
return maxScale;
}
public void setMaxScale(float maxScale) {
this.maxScale = maxScale;
}
public float getMinScale() {
return minScale;
}
public void setMinScale(float minScale) {
this.minScale = minScale;
}
/**
* Drag method
*
* @param current
* Current point to drag to.
*/
private void drag(PointF current) {
float deltaX = getMoveDraggingDelta(current.x - last.x, viewWidth,
afterScaleDrawableWidth * currentScale);
float deltaY = getMoveDraggingDelta(current.y - last.y, viewHeight,
afterScaleDrawableHeight * currentScale);
Log.i("zoom_img", "drag deltaX=" + deltaX + ";deltaY=" + deltaY);
matrix.postTranslate(deltaX, deltaY);
limitDrag();
}
/**
* Scale method for zooming
*
* @param focusX
* X of center of scale
* @param focusY
* Y of center of scale
* @param scaleFactor
* scale factor to zoom in/out
*/
private void scale(float focusX, float focusY, float scaleFactor) {
float lastScale = currentScale;
float newScale = lastScale * scaleFactor;
// Calculate next scale with resetting to max or min if required
if (newScale > maxScale) {
currentScale = maxScale;
scaleFactor = maxScale / lastScale;
} else if (newScale < minScale) {
currentScale = minScale;
scaleFactor = minScale / lastScale;
} else {
currentScale = newScale;
}
// Do scale
if (requireCentering()) {
matrix.postScale(scaleFactor, scaleFactor, (float) viewWidth / 2,
(float) viewHeight / 2);
} else
matrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
limitDrag();
}
/**
* This method permits to keep drag and zoom inside the drawable. It makes
* sure the drag is staying in bound.
*/
private void limitDrag() {
matrix.getValues(finalTransformation);
float finalXTransformation = finalTransformation[Matrix.MTRANS_X];
float finalYTransformation = finalTransformation[Matrix.MTRANS_Y];
float deltaX = getScaleDraggingDelta(finalXTransformation, viewWidth,
afterScaleDrawableWidth * currentScale);
float deltaY = getScaleDraggingDelta(finalYTransformation, viewHeight,
afterScaleDrawableHeight * currentScale);
Log.i("zoom_img", "limitDrag deltaX=" + deltaX + ";deltaY=" + deltaY);
if (isZoom()) {
if (deltaX != 0) {
if (deltaX < 0) {// deltaX<0 表示已经到了左边界,再滑动deltaX值为负数
if (pos > 0) {
HackyViewPager.setLocked(false);
} else {
HackyViewPager.setLocked(true);
}
} else if (deltaX > 0) {// deltaX>0 表示已经到了右边界,再滑动deltaX值为正数
if (pos < count - 1) {
HackyViewPager.setLocked(false);
} else {
HackyViewPager.setLocked(true);
}
}
} else if (deltaY != 0) {
// deltaY<0 表示已经到了上边界,再滑动deltaY值为负数
// deltaY>0 表示已经到了下边界,再滑动deltaY值为正数
} else {
HackyViewPager.setLocked(true);
}
} else {
HackyViewPager.setLocked(false);
}
matrix.postTranslate(deltaX, deltaY);
}
private float getScaleDraggingDelta(float delta, float viewSize,
float contentSize) {
float minTrans = 0;
float maxTrans = 0;
if (contentSize <= viewSize) {
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
}
if (delta < minTrans)
return minTrans - delta;
else if (delta > maxTrans)
return maxTrans - delta;
else
return 0;
}
// Check if dragging is still possible if so return delta otherwise return 0
// (do nothing)
private float getMoveDraggingDelta(float delta, float viewSize,
float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
private float getScaleForDrawable() {
float scaleX = (float) viewWidth
/ (float) getDrawable().getIntrinsicWidth();
float scaleY = (float) viewHeight
/ (float) getDrawable().getIntrinsicHeight();
return Math.min(scaleX, scaleY);
}
private boolean hasDrawable() {
return getDrawable() != null && getDrawable().getIntrinsicWidth() != 0
&& getDrawable().getIntrinsicHeight() != 0;
}
private boolean requireCentering() {
return afterScaleDrawableWidth * currentScale <= (float) viewWidth
|| afterScaleDrawableHeight * currentScale <= (float) viewHeight;
}
private boolean isZoom() {
return currentScale != 1f;
}
/**
* Listener for detecting scale.
*
* @author jmartinez
*/
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
state = State.ZOOM;
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
scale(detector.getFocusX(), detector.getFocusY(),
detector.getScaleFactor());
return true;
}
}
/**
* Listener for double tap detection
*
* @author jmartinez
*/
private class GestureListener extends
GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO 若注册单击点击处理事件则处理并返回
if (mSingleClickListener != null) {
return mSingleClickListener.onSingleClick(e);
}
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (isZoom()) {
resetImage();
currentScale = 1f;
state = State.INIT;
} else {
scale(e.getX(), e.getY(), maxScale);
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
if (mSingleClickListener != null) {
mSingleClickListener.onLongClick(e);
}
super.onLongPress(e);
}
}
public interface OnSingleClickListener {
boolean onSingleClick(MotionEvent e);
boolean onLongClick(MotionEvent e);
}
private OnSingleClickListener mSingleClickListener;
/**
* 注册单击监听器处理事件
*
* @param mSingleClickListener
* 单击监听器
*/
public void setOnSingleClickListener(
OnSingleClickListener mSingleClickListener) {
this.mSingleClickListener = mSingleClickListener;
}
}