Java tutorial
/* * Copyright (C) 2017 guodongAndroid * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Last modified 2017-05-04 15:07:09 * * GitHub: https://github.com/guodongAndroid * Website: http://www.sunxiaoduo.com * Email: sun33919135@gmail.com * QQ: 33919135 */ package com.guodong.sun.guodong.widget; import android.animation.Animator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewTreeObserver; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageView; /** * 2d?ImageView * ?:ScaleType==CENTER_CROPImageView??ScaleType= * FIT_CENTERImageView?? (?) */ public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, ViewTreeObserver.OnGlobalLayoutListener, View.OnTouchListener { private static final int STATE_NORMAL = 0; private static final int STATE_TRANSFORM_IN = 1; private static final int STATE_TRANSFORM_OUT = 2; private int mOriginalWidth; private int mOriginalHeight; private int mOriginalLocationX; private int mOriginalLocationY; private int mState = STATE_NORMAL; private Matrix mSmoothMatrix; private Bitmap mBitmap; private boolean mTransformStart = false; private Transfrom mTransfrom; private final int mBgColor = 0xFF000000; private int mBgAlpha = 0; private Paint mPaint; private boolean mOnce = true; /** * ? */ private float mMinScale; /** * ? */ private float mMidScale; /** * */ private float mMaxScale; private Matrix mScaleMatrix; /** * ? */ private ScaleGestureDetector mScaleGestureDetector = null; /** * ? */ private int mLastPointerCount; private float mLastX; private float mLastY; private int mTouchSlop; private boolean isCanDrag; private boolean isCheckLeftAndRight = true; private boolean isCheckTopAndBottom = true; /** * ?? */ private GestureDetector mGestureDetector; private boolean isAutoScale; public ZoomImageView(Context context) { this(context, null); } public ZoomImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZoomImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { mSmoothMatrix = new Matrix(); mPaint = new Paint(); mPaint.setColor(mBgColor); mPaint.setStyle(Style.FILL); // setBackgroundColor(mBgColor); mScaleMatrix = new Matrix(); setScaleType(ScaleType.MATRIX); mScaleGestureDetector = new ScaleGestureDetector(context, this); setOnTouchListener(this); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { public boolean onDoubleTap(MotionEvent e) { if (isAutoScale == true) { return true; } float X = e.getX(); float Y = e.getY(); if (getScale() < mMidScale) { postDelayed(new AutoScaleRunnable(mMidScale, X, Y), 16); isAutoScale = true; } else if ((getScale() >= mMidScale) && (getScale() < mMaxScale)) { postDelayed(new AutoScaleRunnable(mMaxScale, X, Y), 16); isAutoScale = true; } else { postDelayed(new AutoScaleRunnable(mMinScale, X, Y), 5); isAutoScale = true; } return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (mClickListener != null) mClickListener.onClick(ZoomImageView.this); return super.onSingleTapUp(e); } @Override public void onLongPress(MotionEvent e) { if (mLongClickListener != null) mLongClickListener.onLongClick(ZoomImageView.this); } }); } /** * ? * * @author Sun */ private class AutoScaleRunnable implements Runnable { /** * */ private float mTargetScale; // private float X; private float Y; private final float BIGGER = 1.07f; private final float SMALL = 0.93f; private float tmpScale; /** * ??? * * @param */ public AutoScaleRunnable(float mTargetScale, float x, float y) { this.mTargetScale = mTargetScale; this.X = x; this.Y = y; if (getScale() < mTargetScale) { tmpScale = BIGGER; } if (getScale() > mTargetScale) { tmpScale = SMALL; } } public void run() { // mScaleMatrix.postScale(tmpScale, tmpScale, X, Y); checkBorderAndCenterWhenScale(); setImageMatrix(mScaleMatrix); float currentScale = getScale(); // ? if (((tmpScale > 1.0f) && (currentScale < mTargetScale)) || ((tmpScale < 1.0f) && (currentScale > mTargetScale))) { postDelayed(this, 16); } else { // float scale = mTargetScale / currentScale; mScaleMatrix.postScale(scale, scale, X, Y); checkBorderAndCenterWhenScale(); setImageMatrix(mScaleMatrix); isAutoScale = false; } } } /** * onGlobalLayout? */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } /** * onGlobalLayout? */ @SuppressWarnings("deprecation") @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); // getViewTreeObserver().removeOnGlobalLayoutListener(this); getViewTreeObserver().removeGlobalOnLayoutListener(this); } /** * ?ImageView? */ @Override public void onGlobalLayout() { if (mOnce) { // ? Drawable d = getDrawable(); if (d == null) { return; } // int width = getWidth(); int height = getHeight(); int dWidth = d.getIntrinsicWidth(); int dHeight = d.getIntrinsicHeight(); float scale = 1.0f; // ?? if (dWidth > width && dHeight <= height) { scale = width * 1.0f / dWidth; } // ?? if (dHeight > height && dWidth <= width) { scale = height * 1.0f / dHeight; } // ? // ?? if ((dWidth > width && dHeight > height) || (dWidth < width && dHeight < height)) { scale = Math.min(width * 1.0f / dWidth, height * 1.0f / dHeight); } // ? mMinScale = scale; mMidScale = mMinScale * 2f; mMaxScale = mMinScale * 4f; // ?X?Y? int dX = (width - dWidth) / 2; int dY = (height - dHeight) / 2; // mScaleMatrix.postTranslate(dX, dY); mScaleMatrix.postScale(mMinScale, mMinScale, width / 2, height / 2); setImageMatrix(mScaleMatrix); mOnce = false; } } /** * ? * * @return */ public float getScale() { float[] values = new float[9]; mScaleMatrix.getValues(values); return values[Matrix.MSCALE_X]; } @Override public boolean onScale(ScaleGestureDetector detector) { // mMinScale~mMaxScale float scale = getScale(); float scaleFactor = detector.getScaleFactor(); if (getDrawable() == null) return true; // if ((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mMinScale && scaleFactor < 1.0f)) { if (scale * scaleFactor < mMinScale) { scaleFactor = mMinScale / scale; } if (scale * scaleFactor > mMaxScale) { scale = mMaxScale / scale; } // mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); checkBorderAndCenterWhenScale(); setImageMatrix(mScaleMatrix); } return true; } /** * ???l,r,t,b * * @return */ private RectF getMatrixRectF() { Matrix matrix = mScaleMatrix; RectF rectF = new RectF(); Drawable d = getDrawable(); if (d != null) { rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(rectF); } return rectF; } /** * ? */ private void checkBorderAndCenterWhenScale() { RectF rectF = getMatrixRectF(); float delatX = 0; float delatY = 0; int width = getWidth(); int height = getHeight(); // if (rectF.width() >= width) { if (rectF.left > 0) { delatX = -rectF.left; } if (rectF.right < width) { delatX = width - rectF.right; } } if (rectF.height() >= height) { if (rectF.top > 0) { delatY = -rectF.top; } if (rectF.bottom < height) { delatY = height - rectF.bottom; } } // ? if (rectF.width() < width) { delatX = width * 0.5f - rectF.right + rectF.width() * 0.5f; } if (rectF.height() < height) { delatY = height * 0.5f - rectF.bottom + rectF.height() * 0.5f; } mScaleMatrix.postTranslate(delatX, delatY); } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } @Override public boolean onTouch(View v, MotionEvent event) { if (mGestureDetector.onTouchEvent(event)) return true; mScaleGestureDetector.onTouchEvent(event); float x = 0; float y = 0; // ? final int mPointerCount = event.getPointerCount(); for (int i = 0; i < mPointerCount; i++) { x += event.getX(i); y += event.getY(i); } x = x / mPointerCount; y = y / mPointerCount; /** * ????mLasX , mLastY */ if (mLastPointerCount != mPointerCount) { isCanDrag = false; mLastX = x; mLastY = y; } mLastPointerCount = mPointerCount; RectF rectF = getMatrixRectF(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (rectF.width() > getWidth() + 0.01 || rectF.height() > getHeight() + 0.01) { if (getParent() instanceof ViewPager) getParent().requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (rectF.width() > getWidth() + 0.01 || rectF.height() > getHeight() + 0.01) { if (getParent() instanceof ViewPager) getParent().requestDisallowInterceptTouchEvent(true); } float dX = x - mLastX; float dY = y - mLastY; if (!isCanDrag) { isCanDrag = isMoveAction(dX, dY); } if (isCanDrag) { if (getDrawable() != null) { isCheckLeftAndRight = isCheckTopAndBottom = true; // ???? if (rectF.width() < getWidth()) { dX = 0; isCheckLeftAndRight = false; } // ???? if (rectF.height() < getHeight()) { dY = 0; isCheckTopAndBottom = false; } mScaleMatrix.postTranslate(dX, dY); checkBorderWhenTranslate(); setImageMatrix(mScaleMatrix); } } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mLastPointerCount = 0; break; } return true; } /** * */ private void checkBorderWhenTranslate() { RectF rectF = getMatrixRectF(); float deltaX = 0; float deltaY = 0; int width = getWidth(); int height = getHeight(); if (rectF.top > 0 && isCheckTopAndBottom) { deltaY = -rectF.top; } if (rectF.bottom < height && isCheckTopAndBottom) { deltaY = height - rectF.bottom; } if (rectF.left > 0 && isCheckLeftAndRight) { deltaX = -rectF.left; } if (rectF.right < width && isCheckLeftAndRight) { deltaX = width - rectF.right; } mScaleMatrix.postTranslate(deltaX, deltaY); // setImageMatrix(mScaleMatrix); } /** * ?move * * @param dX * @param dY * @return */ private boolean isMoveAction(float dX, float dY) { return Math.sqrt((dX * dX) + (dY * dY)) >= mTouchSlop; } public void setOriginalInfo(int width, int height, int locationX, int locationY) { mOriginalWidth = width; mOriginalHeight = height; mOriginalLocationX = locationX; mOriginalLocationY = locationY; // ???????MATCH_PARENT???,??????mOriginalLocationXmOriginalLocationY mOriginalLocationY = mOriginalLocationY - getStatusBarHeight(getContext()); } /** * ??? * * @return */ private int getStatusBarHeight(Context context) { Class<?> c = null; Object obj = null; java.lang.reflect.Field field = null; int x = 0; int statusBarHeight = 0; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); statusBarHeight = context.getResources().getDimensionPixelSize(x); return statusBarHeight; } catch (Exception e) { e.printStackTrace(); } return statusBarHeight; } /** * ??setOriginalInfo */ public void transformIn() { mState = STATE_TRANSFORM_IN; mTransformStart = true; invalidate(); } /** * ??setOriginalInfo */ public void transformOut() { mState = STATE_TRANSFORM_OUT; mTransformStart = true; invalidate(); } private class Transfrom { float startScale;// float endScale;// ? float scale;// ValueAnimator? LocationSizeF startRect;// LocationSizeF endRect;// ? LocationSizeF rect;// ValueAnimator? void initStartIn() { scale = startScale; try { rect = (LocationSizeF) startRect.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } void initStartOut() { scale = endScale; try { rect = (LocationSizeF) endRect.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } /** * ???? */ private void initTransform() { if (getDrawable() == null) { return; } if (mBitmap == null || mBitmap.isRecycled()) { mBitmap = ((BitmapDrawable) getDrawable()).getBitmap(); } //mTransfrom????? if (mTransfrom != null) { return; } if (getWidth() == 0 || getHeight() == 0) { return; } mTransfrom = new Transfrom(); /** ? */ /* ??CENTR_CROP???1??1 */ float xSScale = mOriginalWidth / ((float) mBitmap.getWidth()); float ySScale = mOriginalHeight / ((float) mBitmap.getHeight()); float startScale = xSScale > ySScale ? xSScale : ySScale; mTransfrom.startScale = startScale; /* ???FIT_CENTER???1??1? */ float xEScale = getWidth() / ((float) mBitmap.getWidth()); float yEScale = getHeight() / ((float) mBitmap.getHeight()); float endScale = xEScale < yEScale ? xEScale : yEScale; mTransfrom.endScale = endScale; /** * ?Canvas Clip???? * ?CENTER_CROP * FIT_CENTERLocationSizeF * ???Canvas??Rect. */ /* */ mTransfrom.startRect = new LocationSizeF(); mTransfrom.startRect.left = mOriginalLocationX; mTransfrom.startRect.top = mOriginalLocationY; mTransfrom.startRect.width = mOriginalWidth; mTransfrom.startRect.height = mOriginalHeight; /* ? */ mTransfrom.endRect = new LocationSizeF(); float bitmapEndWidth = mBitmap.getWidth() * mTransfrom.endScale;// float bitmapEndHeight = mBitmap.getHeight() * mTransfrom.endScale;// mTransfrom.endRect.left = (getWidth() - bitmapEndWidth) / 2; mTransfrom.endRect.top = (getHeight() - bitmapEndHeight) / 2; mTransfrom.endRect.width = bitmapEndWidth; mTransfrom.endRect.height = bitmapEndHeight; mTransfrom.rect = new LocationSizeF(); } private class LocationSizeF implements Cloneable { float left; float top; float width; float height; @Override public String toString() { return "[left:" + left + " top:" + top + " width:" + width + " height:" + height + "]"; } @Override public Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub return super.clone(); } } /* ?CENTER_CROP Matrix?? */ private void getCenterCropMatrix() { if (getDrawable() == null) { return; } if (mBitmap == null || mBitmap.isRecycled()) { mBitmap = ((BitmapDrawable) getDrawable()).getBitmap(); } /* ?CENTER_CROP */ float xScale = mOriginalWidth / ((float) mBitmap.getWidth()); float yScale = mOriginalHeight / ((float) mBitmap.getHeight()); float scale = xScale > yScale ? xScale : yScale; mSmoothMatrix.reset(); mSmoothMatrix.setScale(scale, scale); mSmoothMatrix.postTranslate(-(scale * mBitmap.getWidth() / 2 - mOriginalWidth / 2), -(scale * mBitmap.getHeight() / 2 - mOriginalHeight / 2)); } private void getBmpMatrix() { if (getDrawable() == null) { return; } if (mTransfrom == null) { return; } if (mBitmap == null || mBitmap.isRecycled()) { mBitmap = ((BitmapDrawable) getDrawable()).getBitmap(); } /* ?CENTER_CROP */ mSmoothMatrix.setScale(mTransfrom.scale, mTransfrom.scale); mSmoothMatrix.postTranslate(-(mTransfrom.scale * mBitmap.getWidth() / 2 - mTransfrom.rect.width / 2), -(mTransfrom.scale * mBitmap.getHeight() / 2 - mTransfrom.rect.height / 2)); } @Override protected void onDraw(Canvas canvas) { if (getDrawable() == null) { return; // couldn't resolve the URI } if (mState == STATE_TRANSFORM_IN || mState == STATE_TRANSFORM_OUT) { if (mTransformStart) { initTransform(); } if (mTransfrom == null) { super.onDraw(canvas); return; } if (mTransformStart) { if (mState == STATE_TRANSFORM_IN) { mTransfrom.initStartIn(); } else { mTransfrom.initStartOut(); } } if (mTransformStart) { Log.d("Dean", "mTransfrom.startScale:" + mTransfrom.startScale); Log.d("Dean", "mTransfrom.startScale:" + mTransfrom.endScale); Log.d("Dean", "mTransfrom.scale:" + mTransfrom.scale); Log.d("Dean", "mTransfrom.startRect:" + mTransfrom.startRect.toString()); Log.d("Dean", "mTransfrom.endRect:" + mTransfrom.endRect.toString()); Log.d("Dean", "mTransfrom.rect:" + mTransfrom.rect.toString()); } mPaint.setAlpha(mBgAlpha); canvas.drawPaint(mPaint); int saveCount = canvas.getSaveCount(); canvas.save(); // ?Matrix getBmpMatrix(); canvas.translate(mTransfrom.rect.left, mTransfrom.rect.top); canvas.clipRect(0, 0, mTransfrom.rect.width, mTransfrom.rect.height); canvas.concat(mSmoothMatrix); getDrawable().draw(canvas); canvas.restoreToCount(saveCount); if (mTransformStart) { mTransformStart = false; startTransform(mState); } } else { //Transform In???Activity?? mPaint.setAlpha(255); canvas.drawPaint(mPaint); super.onDraw(canvas); } } private void startTransform(final int state) { if (mTransfrom == null) { return; } ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setDuration(300); valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); if (state == STATE_TRANSFORM_IN) { PropertyValuesHolder scaleHolder = PropertyValuesHolder.ofFloat("scale", mTransfrom.startScale, mTransfrom.endScale); PropertyValuesHolder leftHolder = PropertyValuesHolder.ofFloat("left", mTransfrom.startRect.left, mTransfrom.endRect.left); PropertyValuesHolder topHolder = PropertyValuesHolder.ofFloat("top", mTransfrom.startRect.top, mTransfrom.endRect.top); PropertyValuesHolder widthHolder = PropertyValuesHolder.ofFloat("width", mTransfrom.startRect.width, mTransfrom.endRect.width); PropertyValuesHolder heightHolder = PropertyValuesHolder.ofFloat("height", mTransfrom.startRect.height, mTransfrom.endRect.height); PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 0, 255); valueAnimator.setValues(scaleHolder, leftHolder, topHolder, widthHolder, heightHolder, alphaHolder); } else { PropertyValuesHolder scaleHolder = PropertyValuesHolder.ofFloat("scale", mTransfrom.endScale, mTransfrom.startScale); PropertyValuesHolder leftHolder = PropertyValuesHolder.ofFloat("left", mTransfrom.endRect.left, mTransfrom.startRect.left); PropertyValuesHolder topHolder = PropertyValuesHolder.ofFloat("top", mTransfrom.endRect.top, mTransfrom.startRect.top); PropertyValuesHolder widthHolder = PropertyValuesHolder.ofFloat("width", mTransfrom.endRect.width, mTransfrom.startRect.width); PropertyValuesHolder heightHolder = PropertyValuesHolder.ofFloat("height", mTransfrom.endRect.height, mTransfrom.startRect.height); PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0); valueAnimator.setValues(scaleHolder, leftHolder, topHolder, widthHolder, heightHolder, alphaHolder); } valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public synchronized void onAnimationUpdate(ValueAnimator animation) { mTransfrom.scale = (Float) animation.getAnimatedValue("scale"); mTransfrom.rect.left = (Float) animation.getAnimatedValue("left"); mTransfrom.rect.top = (Float) animation.getAnimatedValue("top"); mTransfrom.rect.width = (Float) animation.getAnimatedValue("width"); mTransfrom.rect.height = (Float) animation.getAnimatedValue("height"); mBgAlpha = (Integer) animation.getAnimatedValue("alpha"); invalidate(); ((Activity) getContext()).getWindow().getDecorView().invalidate(); } }); valueAnimator.addListener(new ValueAnimator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { /* * ???center_cropout??center_crop? * ???out???Normal???bug */ // TODO ??? if (state == STATE_TRANSFORM_IN) { mState = STATE_NORMAL; } if (mTransformListener != null) { mTransformListener.onTransformComplete(state); } } @Override public void onAnimationCancel(Animator animation) { } }); valueAnimator.start(); } public void setOnTransformListener(TransformListener listener) { mTransformListener = listener; } private TransformListener mTransformListener; public static interface TransformListener { /** * @param mode STATE_TRANSFORM_IN 1 ,STATE_TRANSFORM_OUT 2 */ void onTransformComplete(int mode);// mode 1 } public interface OnClickListener { void onClick(View v); } private OnClickListener mClickListener; public void setOnClickListener(@Nullable OnClickListener l) { this.mClickListener = l; } public interface OnLongClickListener { void onLongClick(View v); } private OnLongClickListener mLongClickListener; public void setOnLongClickListener(@Nullable OnLongClickListener l) { mLongClickListener = l; } }