package com.datdo.mobilib.widget;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.Animator.AnimatorListener;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener;
public class MblTouchImageView extends ImageView {
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private static final int CLICK = 3;
private Matrix mMatrix;
private int mMode = NONE;
private PointF mLast = new PointF();
private PointF mStart = new PointF();
private float mMinScale = 1f;
private float mMaxScale = 3f;
private float[] mMatrixValues;
private float mCurrentScale = 1f;
private float mOriginWidth, mOriginHeight;
private int mLeftDragPadding;
private int mTopDragPadding;
private int mRightDragPadding;
private int mBottomDragPadding;
private ScaleGestureDetector mScaleDetector;
private GestureDetector mGestureDetector;
private OnTouchListener mExtraTouchListener;
public MblTouchImageView(Context context) {
super(context);
init(context);
}
public MblTouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MblTouchImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
super.setClickable(true);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
MblTouchImageView.this.onDoubleTap();
return true;
}
});
mMatrix = new Matrix();
mMatrixValues = new float[9];
super.setImageMatrix(mMatrix);
setScaleType(ScaleType.MATRIX);
super.setOnTouchListener(new OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mExtraTouchListener != null) mExtraTouchListener.onTouch(v, event);
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLast.set(curr);
mStart.set(mLast);
mMode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mMode == DRAG) {
float deltaX = curr.x - mLast.x;
float deltaY = curr.y - mLast.y;
if (hasDragPaddings()) {
mMatrix.postTranslate(deltaX, deltaY);
} else {
float fixTransX = getFixDragTranslation(deltaX, getWidth(), mOriginWidth * mCurrentScale);
float fixTransY = getFixDragTranslation(deltaY, getHeight(), mOriginHeight * mCurrentScale);
mMatrix.postTranslate(fixTransX, fixTransY);
}
fixTranslations();
mLast.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
mMode = NONE;
int xDiff = (int) Math.abs(curr.x - mStart.x);
int yDiff = (int) Math.abs(curr.y - mStart.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mMode = NONE;
break;
}
MblTouchImageView.super.setImageMatrix(mMatrix);
invalidate();
return true; // indicate event was handled
}
});
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
applyOptionsIfReady();
}
});
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mMode = ZOOM;
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = mCurrentScale;
mCurrentScale *= mScaleFactor;
if (mCurrentScale > mMaxScale) {
mCurrentScale = mMaxScale;
mScaleFactor = mMaxScale / origScale;
} else if (mCurrentScale < mMinScale) {
mCurrentScale = mMinScale;
mScaleFactor = mMinScale / origScale;
}
if (mOriginWidth * mCurrentScale <= getWidth() || mOriginHeight * mCurrentScale <= getHeight())
mMatrix.postScale(mScaleFactor, mScaleFactor, getWidth() / 2, getHeight() / 2);
else
mMatrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
fixTranslations();
return true;
}
}
private void fixTranslations() {
mMatrix.getValues(mMatrixValues);
float transX = mMatrixValues[Matrix.MTRANS_X];
float transY = mMatrixValues[Matrix.MTRANS_Y];
float fixTransX = getFixTranslation(transX, getWidth(), mOriginWidth * mCurrentScale, mLeftDragPadding, mRightDragPadding);
float fixTransY = getFixTranslation(transY, getHeight(), mOriginHeight * mCurrentScale, mTopDragPadding, mBottomDragPadding);
if (fixTransX != 0 || fixTransY != 0) {
mMatrix.postTranslate(fixTransX, fixTransY);
}
}
private float getFixTranslation(float trans, float viewSize, float contentSize, int dragPaddingFrom, int dragPaddingTo) {
float minTrans, maxTrans;
if (hasDragPaddings()) {
minTrans = (viewSize - contentSize) - dragPaddingFrom;
maxTrans = dragPaddingTo;
} else {
if (contentSize <= viewSize) {
minTrans = 0;
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
maxTrans = 0;
}
}
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
}
private float getFixDragTranslation(float delta, float viewSize, float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
private boolean hasDragPaddings() {
return
mLeftDragPadding != 0 ||
mTopDragPadding != 0 ||
mRightDragPadding != 0 ||
mBottomDragPadding != 0;
}
public float[] getMatrixValues() {
float[] ret = new float[9];
mMatrix.getValues(ret);
return ret;
}
public void setOptions(
float minScale,
float maxScale,
float currentScale,
int leftDragPadding,
int topDragPadding,
int rightDragPadding,
int bottomDragPadding) {
// save min zoom, max zoom and current zoom
mMinScale = minScale;
mMaxScale = maxScale;
mCurrentScale = currentScale;
// save drag padding
mLeftDragPadding = leftDragPadding;
mTopDragPadding = topDragPadding;
mRightDragPadding = rightDragPadding;
mBottomDragPadding = bottomDragPadding;
// config if ready
applyOptionsIfReady();
}
private void applyOptionsIfReady() {
if (isReady()) {
applyOptions();
}
}
private void applyOptions() {
// get bitmap sizes
Drawable drawable = getDrawable();
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
// create new matrix
mMatrix = new Matrix();
if (mCurrentScale <= 0) {
mCurrentScale = getJustifiedScale(Math.min(1.0f * getWidth() / mOriginWidth, 1.0f * getHeight() / mOriginHeight));
}
mMatrix.postScale(mCurrentScale, mCurrentScale, 0, 0);
// save original sizes
mOriginWidth = bmWidth;
mOriginHeight = bmHeight;
// center the image
float redundantXSpace = (getWidth() - mCurrentScale * bmWidth) / 2;
float redundantYSpace = (getHeight() - mCurrentScale * bmHeight) / 2;
mMatrix.postTranslate(redundantXSpace, redundantYSpace);
// transform image
super.setImageMatrix(mMatrix);
fixTranslations();
}
private boolean isReady() {
Drawable drawable;
return
getWidth() > 0 &&
getHeight() > 0 &&
(drawable = getDrawable()) != null &&
drawable.getIntrinsicWidth() > 0 &&
drawable.getIntrinsicHeight() > 0;
}
@Override
public void setScaleType(ScaleType scaleType) {
// only accept matrix scaletype
if (scaleType == ScaleType.MATRIX) {
super.setScaleType(scaleType);
}
}
@Override
public void setImageMatrix(Matrix matrix) {
// not allow
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
applyOptionsIfReady();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
applyOptionsIfReady();
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
applyOptionsIfReady();
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
applyOptionsIfReady();
}
@Override
public void setOnTouchListener(OnTouchListener l) {
mExtraTouchListener = l;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void onDoubleTap() {
final float currentScale = mCurrentScale;
final float targetScale = getJustifiedScale(Math.min(1.0f * getWidth() / mOriginWidth, 1.0f * getHeight() / mOriginHeight));
mMatrix.getValues(mMatrixValues);
final float currentTransX = mMatrixValues[Matrix.MTRANS_X];
final float currentTransY = mMatrixValues[Matrix.MTRANS_Y];
final float targetTransX = (getWidth() - targetScale * mOriginWidth) / 2;
final float targetTransY = (getHeight() - targetScale * mOriginHeight) / 2;
ValueAnimator anim = null;
if (currentScale != targetScale) {
anim = ValueAnimator.ofFloat(currentScale, targetScale);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator anim) {
float scale = (Float) anim.getAnimatedValue();
mMatrix.setScale(scale, scale);
float transX = targetTransX + (currentTransX - targetTransX) * (scale - targetScale) / (currentScale - targetScale);
float transY = targetTransY + (currentTransY - targetTransY) * (scale - targetScale) / (currentScale - targetScale);
mMatrix.postTranslate(transX, transY);
MblTouchImageView.super.setImageMatrix(mMatrix);
}
});
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator anim) {}
@Override
public void onAnimationRepeat(Animator anim) {}
@Override
public void onAnimationEnd(Animator anim) {
mCurrentScale = targetScale;
}
@Override
public void onAnimationCancel(Animator anim) {}
});
} else if (currentTransX != targetTransX) {
anim = ValueAnimator.ofFloat(currentTransX, targetTransX);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator anim) {
mMatrix.setScale(targetScale, targetScale);
float transX = (Float) anim.getAnimatedValue();
float transY = targetTransY + (currentTransY - targetTransY) * (transX - targetTransX) / (currentTransX - targetTransX);
mMatrix.postTranslate(transX, transY);
MblTouchImageView.super.setImageMatrix(mMatrix);
}
});
} else if (currentTransY != targetTransY) {
anim = ValueAnimator.ofFloat(currentTransY, targetTransY);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator anim) {
mMatrix.setScale(targetScale, targetScale);
float transY = (Float) anim.getAnimatedValue();
float transX = targetTransX + (currentTransX - targetTransX) * (transY - targetTransY) / (currentTransY - targetTransY);
mMatrix.postTranslate(transX, transY);
MblTouchImageView.super.setImageMatrix(mMatrix);
}
});
}
if (anim != null) {
anim.setDuration(500);
anim.start();
}
}
private float getJustifiedScale(float scale) {
return Math.min(Math.max(mMinScale, scale), mMaxScale);
}
}