content: package com.allenxuan.xuanyihuang.xuanimageview;
  
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Matrix;
  import android.graphics.RectF;
  import android.graphics.drawable.Drawable;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.GestureDetector;
  import android.view.MotionEvent;
  import android.view.ScaleGestureDetector;
  
  import com.allenxuan.xuanyihuang.xuanimageview.GestureDetectors.RotationGestureDetector;
  import com.allenxuan.xuanyihuang.xuanimageview.constants.XuanImageViewSettings;
  
  /**
   * Created by xuanyihuang on 8/30/16.
   */
  
  public class XuanImageView extends android.support.v7.widget.AppCompatImageView{
      private int XuanImageViewWidth;
      private int XuanImageViewHeight;
      private int XuanImageViewCenterX;
      private int XuanImageViewCenterY;
      private int ImageCenterX;
      private int ImageCenterY;
      private int mOrientation;   //1 for portrait, 2 for landscape
      private int mAutoRotateCategory;
      private float mInitScale;   //for landscape
      private float mInitPortraitScale;
      private float mTempInitPortraitScale;
      private float mMaxScale;
      private float mPortraitMaxScale;
      private float mTempPortraitMaxScale;
      private boolean mRotationToggle;
      private float mMaxScaleMultiple;
      private float mDoubleTabScaleMultiple;
      private float mDoubleTabScale;
      private float mPortraitDoubleTabScale;
      private float mTempPortraitDoubleTabScale;
      private Matrix mScaleMatrix;
      private ScaleGestureDetector mScaleGestureDetector;
      private GestureDetector mGestureDetector;
      private float mLastScaleFocusX;
      private float mLastScaleFocusY;
      private boolean isAutoScale;
      private float mSpringBackGradientScaleUpLevel;
      private float mSpringBackGradientScaleDownLevel;
      private float mDoubleTapGradientScaleUpLevel;
      private float mDoubleTapGradientScaleDownLevel;
      private int mLastPointerCount;
      private float mLastX;
      private float mLastY;
      private float mAngle;
      private float mPreviousAngle;
      private RotationGestureDetector mRotateGestureDetector;
      private boolean isAutoRotated;
      private double allowableFloatError;
      private double allowablePortraitFloatError;
      private float currentScaleLevel;
      private float currentAbsScaleLevel;
      private float autoRotationTrigger;
      private int autoRotationRunnableDelay;
      private int autoRotationRunnableTimes;
      private int doubleTabScaleRunnableDelay;
      private int springBackRunnableDelay;
      private boolean hasDrawable;
      private boolean knowViewSize;
  
      public XuanImageView(Context context) {
          this(context, null);
      }
  
      public XuanImageView(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public XuanImageView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          initialize(context, attrs);
      }
  
      private void initialize(Context context, AttributeSet attrs) {
          setScaleType(ScaleType.MATRIX);
          mScaleMatrix = new Matrix();
  
          mScaleGestureDetector = new ScaleGestureDetector(context, constructOnScaleGestureListener());
          mGestureDetector = new GestureDetector(context, constructOnGestureListener());
  
          initCustomAttrs(context, attrs);
  
          mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;   //1 for portrait, 2 for landscape
          isAutoScale = false;
          mLastPointerCount = 0;
          mAngle = 0;
          mPreviousAngle = 0;
          currentScaleLevel = 1;
      }
  
      private ScaleGestureDetector.OnScaleGestureListener constructOnScaleGestureListener() {
          return new ScaleGestureDetector.OnScaleGestureListener() {
              @Override
              public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
                  float scaleFactor = scaleGestureDetector.getScaleFactor();
  
                  currentScaleLevel = getCurrentScaleLevel();
                  currentAbsScaleLevel = Math.abs(currentScaleLevel);
                  Log.d("CurrentAbsScaleLevel", "" + currentAbsScaleLevel);
  
                  boolean isRotating = false;
                  boolean justScale = false;
  
                  if(mRotateGestureDetector.IsRotated()){
                      // is rotating
                      isRotating = true;
                  }
                  else{
                      // not rotating, just scaling
                      if(mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION){
                          if((currentScaleLevel <= mMaxScale && scaleFactor > 1.0f) || (currentScaleLevel >= mInitScale && scaleFactor < 1.0f))
                              justScale = true;
  
                      }
                      else if(mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM){
                          if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
                              if ((currentAbsScaleLevel <= mMaxScale && scaleFactor > 1.0f) || (currentAbsScaleLevel >= mInitScale && scaleFactor < 1.0f))
                                  justScale = true;
                          } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
                              if ((currentAbsScaleLevel <= mTempPortraitMaxScale && scaleFactor > 1.0f) || (currentAbsScaleLevel >= mTempInitPortraitScale && scaleFactor < 1.0f))
                                  justScale = true;
                          }
                      }
                  }
  
  
                  if(isRotating) {
                      mScaleMatrix.postScale(scaleFactor, scaleFactor, mRotateGestureDetector.getPivotX(), mRotateGestureDetector.getPivotY());
                  }
                  else if(justScale)
                  {
                      mScaleMatrix.postScale(scaleFactor, scaleFactor, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY());
                      checkBorderAndCenterWhenScale();
                  }
  
  
                  setImageMatrix(mScaleMatrix);
                  mLastScaleFocusX = scaleGestureDetector.getFocusX();
                  mLastScaleFocusY = scaleGestureDetector.getFocusY();
  
                  return true;
              }
  
              @Override
              public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
                  Log.d("onScaleBegin-->", "");
                  return true;
              }
  
              @Override
              public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
                  Log.d("onScaleEnd-->", "");
              }
          };
      }
  
      private GestureDetector.OnGestureListener constructOnGestureListener() {
          return new GestureDetector.SimpleOnGestureListener() {
              @Override
              public boolean onDoubleTap(MotionEvent e) {
                  currentScaleLevel = getCurrentScaleLevel();
                  currentAbsScaleLevel = Math.abs(currentScaleLevel);
                  float x = e.getX();
                  float y = e.getY();
  
                  if (isAutoScale)
                      return true;
  
  
                  if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
                      if (currentScaleLevel < mDoubleTabScale) {
                          postDelayed(new AutoScaleRunnable(mDoubleTabScale, x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
                          isAutoScale = true;
                      } else {
                          postDelayed(new AutoScaleRunnable(mInitScale, x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
                          isAutoScale = true;
                      }
                  } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
                      if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
                          if (currentAbsScaleLevel < mDoubleTabScale) {
                              postDelayed(new AutoScaleRunnable(mDoubleTabScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
                              isAutoScale = true;
                          } else {
                              postDelayed(new AutoScaleRunnable(mInitScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
                              isAutoScale = true;
                          }
                      } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
                          if (currentAbsScaleLevel < mTempPortraitDoubleTabScale) {
                              postDelayed(new AutoScaleRunnable(mTempPortraitDoubleTabScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
                              isAutoScale = true;
                          } else {
                              postDelayed(new AutoScaleRunnable(mTempInitPortraitScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
                              isAutoScale = true;
                          }
                      }
                  }
  
                  return true;
              }
          };
      }
  
  
      private RotationGestureDetector.OnRotationGestureListener constructOnRotationGestureListener(){
          return new RotationGestureDetector.OnRotationGestureListener() {
              @Override
              public boolean OnRotate(RotationGestureDetector rotationGestureDetector) {
                  mAngle = rotationGestureDetector.getAngle();
                  mPreviousAngle = rotationGestureDetector.getPreviousAngle();
                  mScaleMatrix.postRotate(mAngle - mPreviousAngle, rotationGestureDetector.getPivotX(), rotationGestureDetector.getPivotY());
  
                  setImageMatrix(mScaleMatrix);
  
                  return true;
              }
  
              @Override
              public boolean StopRotate(RotationGestureDetector rotationGestureDetector) {
                  if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION)
                      AutoRotateRestoration(rotationGestureDetector);
                  else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM)
                      AutoRotateMagnetism(rotationGestureDetector);
  
                  return true;
              }
          };
      }
  
      private void initCustomAttrs(Context context, AttributeSet attrs){
          TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.xuanimageview);
          mRotationToggle = a.getBoolean(R.styleable.xuanimageview_RotationToggle, true);
          mAutoRotateCategory = a.getInteger(R.styleable.xuanimageview_AutoRotateCategory, XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION);
          mMaxScaleMultiple = a.getFloat(R.styleable.xuanimageview_MaxScaleMultiple, 4);
          mDoubleTabScaleMultiple = a.getFloat(R.styleable.xuanimageview_DoubleTabScaleMultiple, 2);
          mSpringBackGradientScaleUpLevel = a.getFloat(R.styleable.xuanimageview_SpringBackGradientScaleUpLevel, 1.01f);
          mSpringBackGradientScaleDownLevel = a.getFloat(R.styleable.xuanimageview_SpringBackGradientScaleDownLevel, 0.99f);
          mDoubleTapGradientScaleUpLevel = a.getFloat(R.styleable.xuanimageview_DoubleTapGradientScaleUpLevel, 1.05f);
          mDoubleTapGradientScaleDownLevel = a.getFloat(R.styleable.xuanimageview_DoubleTapGradientScaleDownLevel, 0.95f);
          autoRotationTrigger = a.getFloat(R.styleable.xuanimageview_AutoRotationTrigger, 60);
          springBackRunnableDelay = a.getInteger(R.styleable.xuanimageview_SpringBackRunnableDelay, 10);
          doubleTabScaleRunnableDelay = a.getInteger(R.styleable.xuanimageview_DoubleTapScaleRunnableDelay, 10);
          autoRotationRunnableDelay = a.getInteger(R.styleable.xuanimageview_AutoRotationRunnableDelay, 5);
          autoRotationRunnableTimes = a.getInteger(R.styleable.xuanimageview_AutoRotationRunnableTimes, 10);
          try {
              allowableFloatError = Double.parseDouble(a.getString(R.styleable.xuanimageview_AllowableFloatError));
          }catch (Exception e){
              allowableFloatError = 1E-6;   // for normal display aspect ratio
  //        allowableFloatError = 3E-3;   // for Galaxy S8
          }
          try {
              allowablePortraitFloatError = Double.parseDouble(a.getString(R.styleable.xuanimageview_AllowablePortraitFloatError));
          }catch (Exception e){
              allowablePortraitFloatError = 1E-12;  // for normal display aspect ratio
  //        allowablePortraitFloatError = 5E-8;   //for Galaxy S8
          }
          a.recycle();
      }
  
      @Override
      public void setImageDrawable(Drawable drawable) {
          super.setImageDrawable(drawable);
  
          if (drawable == null) {
              hasDrawable = false;
              return;
          }
  
          if (!drawableHasSize(drawable))
              return;
  
          hasDrawable = true;
          initDrawableMatrix();
      }
  
      @Override
      public void setImageResource(int resId) {
          Drawable drawable = null;
          try {
              drawable = getResources().getDrawable(resId);
          } catch (Exception e) {
              e.printStackTrace();
          }
          setImageDrawable(drawable);
      }
  
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          super.onSizeChanged(w, h, oldw, oldh);
  
          knowViewSize = true;
          initDrawableMatrix();
      }
  
      private boolean drawableHasSize(Drawable drawable) {
          if ((drawable.getIntrinsicHeight() <= 0 || drawable.getIntrinsicWidth() <= 0)
                  && (drawable.getMinimumHeight() <= 0 || drawable.getMinimumWidth() <= 0)
                  && (drawable.getBounds().height() <= 0 || drawable.getBounds().width() <= 0))
              return false;
          else
              return true;
      }
  
      private void initDrawableMatrix() {
          if (!hasDrawable)
              return;
          if (!knowViewSize)
              return;
  
          if (mScaleMatrix == null)
              mScaleMatrix = new Matrix();
          else
              mScaleMatrix.reset();
  
          // get width and height of XuanImageView
          XuanImageViewWidth = getWidth();
          XuanImageViewHeight = getHeight();
  
          //get the center point of XuanImageView
          XuanImageViewCenterX = XuanImageViewWidth / 2;
          XuanImageViewCenterY = XuanImageViewHeight / 2;
  
          // instantiate mRotationGestureDetector after dimension of XuanImageView is got.
          mRotateGestureDetector = new RotationGestureDetector(constructOnRotationGestureListener(), XuanImageViewWidth);
  
          //get width and height of the image
          Drawable imageDrawable = getDrawable();
          if (imageDrawable == null)
              return;
          int imageWidth = imageDrawable.getIntrinsicWidth();
          int imageHeight = imageDrawable.getIntrinsicHeight();
  
          //image is scaled to fit the size of XuanImageView at the very beginning.
          float scale = Math.min(XuanImageViewWidth * 1.0f / imageWidth, XuanImageViewHeight * 1.0f / imageHeight);
          float portraitscale = Math.min(XuanImageViewWidth * 1.0f / imageHeight, XuanImageViewHeight * 1.0f / imageWidth);
  
          mInitScale = scale;
          mInitPortraitScale = portraitscale;
          mMaxScale = mMaxScaleMultiple * scale;
          mPortraitMaxScale = mMaxScaleMultiple * portraitscale;
          mDoubleTabScale = mDoubleTabScaleMultiple * scale;
          mPortraitDoubleTabScale = mDoubleTabScaleMultiple * portraitscale;
  
          //center of image overlaps with that of XuanImageView
          int deltaX = XuanImageViewWidth / 2 - imageWidth / 2;
          int deltaY = XuanImageViewHeight / 2 - imageHeight / 2;
  
          mScaleMatrix.postTranslate(deltaX, deltaY);
          mScaleMatrix.postScale(scale, scale, XuanImageViewWidth / 2, XuanImageViewHeight / 2);
          setImageMatrix(mScaleMatrix);
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent motionEvent) {
          if (mRotateGestureDetector == null)
              return true;
  
          boolean parentDisallowInterceptTouchEventFlag = true;
  
          // for DoubleTap gesture
          mGestureDetector.onTouchEvent(motionEvent);
  
          // for Scale gesture
          mScaleGestureDetector.onTouchEvent(motionEvent);
  
  
          currentScaleLevel = getCurrentScaleLevel();
          currentAbsScaleLevel = Math.abs(currentScaleLevel);
          if (mRotationToggle) {
              if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
                  if (mRotateGestureDetector.IsRotated() || Math.abs(currentScaleLevel - mInitScale) < allowableFloatError
                          || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)
                          || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP)) {
                      if (!mRotateGestureDetector.IsRotated())
                          parentDisallowInterceptTouchEventFlag = false;
  
                      // for Rotation gesture
                      mRotateGestureDetector.onTouchEvent(motionEvent);
                  }
              } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
                  if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
                      if (mRotateGestureDetector.IsRotated() || Math.abs(currentAbsScaleLevel - mInitScale) < allowableFloatError
                              || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)
                              || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP)) {
                          if (!mRotateGestureDetector.IsRotated())
                              parentDisallowInterceptTouchEventFlag = false;
  
                          // for Rotation gesture
                          mRotateGestureDetector.onTouchEvent(motionEvent);
  
                      }
                  } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
                      if (mRotateGestureDetector.IsRotated() || Math.abs(currentAbsScaleLevel - mTempInitPortraitScale) < allowablePortraitFloatError
                              || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)
                              || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP)) {
                          if (!mRotateGestureDetector.IsRotated())
                              parentDisallowInterceptTouchEventFlag = false;
  
                          // for Rotation gesture
                          mRotateGestureDetector.onTouchEvent(motionEvent);
                      }
                  }
              }
          }
  
          //pointerCount won't be 0
          int pointerCount = motionEvent.getPointerCount();
          float pivotX = 0;
          float pivotY = 0;
          for (int i = 0; i < pointerCount; i++) {
              pivotX += motionEvent.getX(i);
              pivotY += motionEvent.getY(i);
          }
          pivotX /= pointerCount;   //get integer result of the division
          pivotY /= pointerCount;
  
          // when image is being dragged, generally, pointCount == mLastCount holds, so old state is not saved.
          if (pointerCount != mLastPointerCount) {
              mLastX = pivotX;
              mLastY = pivotY;
              mLastPointerCount = pointerCount;
          }
  
          RectF rectF = getMatrixRectF();
  
          switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
              case MotionEvent.ACTION_DOWN:
                  if (getParent() != null)
                      getParent().requestDisallowInterceptTouchEvent(parentDisallowInterceptTouchEventFlag);
                  break;
              case MotionEvent.ACTION_MOVE:
                  if (getParent() != null)
                      getParent().requestDisallowInterceptTouchEvent(parentDisallowInterceptTouchEventFlag);
                  float deltaX = pivotX - mLastX;
                  float deltaY = pivotY - mLastY;
  
                  if (getDrawable() != null) {
                      if (!mRotateGestureDetector.IsRotated()) {
                          if (rectF.width() <= XuanImageViewWidth)
                              deltaX = 0;
                          if (rectF.height() <= XuanImageViewHeight)
                              deltaY = 0;
                      }
                      mScaleMatrix.postTranslate(deltaX, deltaY);
  
                      if (!mRotateGestureDetector.IsRotated())
                          checkBorderAndCenterWhenTranslate();
                      setImageMatrix(mScaleMatrix);
                  }
                  mLastX = pivotX;
                  mLastY = pivotY;
                  break;
              case MotionEvent.ACTION_POINTER_UP:
                  //one finger loosening (multi-touch)
                  if (pointerCount - 1 < 2) {
                      if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
                          //spring back to mInitScale or mMaxScale
                          if ((currentScaleLevel < mInitScale) && !isAutoRotated) {
                              postDelayed(new AutoScaleRunnable(mInitScale, mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
                              isAutoScale = true;
                          } else if ((currentScaleLevel > mMaxScale) && !isAutoRotated) {
                              postDelayed(new AutoScaleRunnable(mMaxScale, mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
                              isAutoScale = true;
                          }
                      } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
                          if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
                              if (currentAbsScaleLevel < mInitScale && !isAutoRotated) {
                                  postDelayed(new AutoScaleRunnable(mInitScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
                                  isAutoScale = true;
                              } else if ((currentAbsScaleLevel > mMaxScale) && !isAutoRotated) {
                                  postDelayed(new AutoScaleRunnable(mMaxScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
                                  isAutoScale = true;
                              }
                          } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
                              if (currentAbsScaleLevel < mTempInitPortraitScale && !isAutoRotated) {
                                  postDelayed(new AutoScaleRunnable(mTempInitPortraitScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
                                  isAutoScale = true;
                              } else if ((currentAbsScaleLevel > mTempPortraitMaxScale) && !isAutoRotated) {
                                  postDelayed(new AutoScaleRunnable(mTempPortraitMaxScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
                                  isAutoScale = true;
                              }
                          }
                      }
                  }
                  break;
              case MotionEvent.ACTION_UP:
                  mLastPointerCount = 0;
                  break;
              case MotionEvent.ACTION_CANCEL:
                  mLastPointerCount = 0;
          }
  
  
          return true;
      }
  
      private void checkBorderAndCenterWhenScale() {
  
          RectF rectF = getMatrixRectF();
          float deltaX = 0;
          float deltaY = 0;
  
          /**
           * If width or height of image is bigger than that of XuanImageView,
           * should prevent image's edge being far away from XuanImageView's edge.
           */
          if (rectF.width() >= XuanImageViewWidth) {
              if (rectF.left > 0)
                  deltaX = -rectF.left;
              if (rectF.right < XuanImageViewWidth)
                  deltaX = XuanImageViewWidth - rectF.right;
  
          }
          if (rectF.height() >= XuanImageViewHeight) {
              if (rectF.top > 0)
                  deltaY = -rectF.top;
              if (rectF.bottom < XuanImageViewHeight)
                  deltaY = XuanImageViewHeight - rectF.bottom;
          }
  
          /**
           * If width or height of image is smaller than that of XuanImageView,
           * make sure the image, in width dimension or height dimension, is centered.
           */
          if (rectF.width() < XuanImageViewWidth) {
              deltaX = XuanImageViewWidth / 2.0f - rectF.left - rectF.width() / 2.0f;
          }
          if (rectF.height() < XuanImageViewHeight) {
              deltaY = XuanImageViewHeight / 2.0f - rectF.top - rectF.height() / 2.0f;
  
          }
  
          mScaleMatrix.postTranslate(deltaX, deltaY);
      }
  
      private void checkBorderAndCenterWhenTranslate() {
          /**
           *  No need to check Center here because it has been handled before checkBorderAndCenterWhenTranslate() is invoked.
           *  See "case MotionEvent.ACTION_MOVE: " in  onTouch(View view, MotionEvent motionEvent).
           */
          RectF rectF = getMatrixRectF();
  
          float deltaX = 0;
          float deltaY = 0;
  
          /**
           * If width or height of image is bigger than that of XuanImageView,
           * should prevent image's edge being far away from XuanImageView's edge.
           */
          if (rectF.width() >= XuanImageViewWidth) {
              if (rectF.left > 0)
                  deltaX = -rectF.left;
              if (rectF.right < XuanImageViewWidth)
                  deltaX = XuanImageViewWidth - rectF.right;
  
          }
          if (rectF.height() >= XuanImageViewHeight) {
              if (rectF.top > 0)
                  deltaY = -rectF.top;
              if (rectF.bottom < XuanImageViewHeight)
                  deltaY = XuanImageViewHeight - rectF.bottom;
          }
  
          mScaleMatrix.postTranslate(deltaX, deltaY);
      }
  
  
      private float getCurrentScaleLevel() {
          float matrixArray[] = new float[9];
          mScaleMatrix.getValues(matrixArray);
  
          return matrixArray[Matrix.MSCALE_X];
      }
  
      private RectF getMatrixRectF() {
          Matrix matrix = mScaleMatrix;
          RectF rectF = new RectF();
          Drawable image = getDrawable();
          if (image != null) {
              rectF.set(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
              matrix.mapRect(rectF);
          }
  
          return rectF;
      }
  
      private boolean calculateImageCenterCoordinates() {
          RectF rectF = getMatrixRectF();
          ImageCenterX = (int) ((rectF.left + rectF.right) / 2);
          ImageCenterY = (int) ((rectF.top + rectF.bottom) / 2);
  
          return true;
      }
  
      public void AutoRotateMagnetism(RotationGestureDetector rotationGestureDetector) {
          mAngle = rotationGestureDetector.getAngle();//mAngle's range: [-180 degress, 180 degress]
          float autoRotateAngle;
          int quotient;
          float remainder;
  
          quotient = ((int) mAngle) / 90;
          remainder = mAngle % 90;
  
          if (remainder >= autoRotationTrigger) {
              autoRotateAngle = 90 - remainder;
              if ((quotient + 1) % 2 == 0)
                  mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;
              else
                  mOrientation = XuanImageViewSettings.ORIENTATION_PORTRAIT;
          } else if (remainder <= -autoRotationTrigger) {
              autoRotateAngle = -90 - remainder;
              if ((quotient - 1) % 2 == 0)
                  mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;
              else
                  mOrientation = XuanImageViewSettings.ORIENTATION_PORTRAIT;
          } else {
              autoRotateAngle = -remainder;
              if (quotient % 2 == 0)
                  mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;
              else
                  mOrientation = XuanImageViewSettings.ORIENTATION_PORTRAIT;
          }
  
          postDelayed(new AutoRotateRunnable(autoRotateAngle, getCurrentScaleLevel() / (float) Math.cos(Math.toRadians(mAngle)), autoRotationRunnableTimes), autoRotationRunnableDelay);
          isAutoRotated = true;
  
          rotationGestureDetector.setAngle(mAngle + autoRotateAngle);
          rotationGestureDetector.setPreviousAngle(mAngle + autoRotateAngle);
      }
  
      public void AutoRotateRestoration(RotationGestureDetector rotationGestureDetector) {
          mAngle = rotationGestureDetector.getAngle();//mAngle's range: [-180 degress, 180 degress]
          float autoRotateAngle;
  
          if (mAngle >= autoRotationTrigger)
              autoRotateAngle = 360 - mAngle;
          else if (mAngle <= -autoRotationTrigger)
              autoRotateAngle = -360 - mAngle;
          else
              autoRotateAngle = -mAngle;
  
          postDelayed(new AutoRotateRunnable(autoRotateAngle, getCurrentScaleLevel() / (float) Math.cos(Math.toRadians(mAngle)), autoRotationRunnableTimes), autoRotationRunnableDelay);
          isAutoRotated = true;
  
          rotationGestureDetector.setAngle(0.0f);
          rotationGestureDetector.setPreviousAngle(0.0f);
      }
  
      private class AutoScaleRunnable implements Runnable {
          float targetScale;
          float targetAbsScale;
          float FocusX;
          float FocusY;
          float scaleFactor;
  
          AutoScaleRunnable(float targetScale, float FocusX, float FocusY, float GradientScaleUp, float GradientScaleDown) {
              this.targetScale = targetScale;
              this.targetAbsScale = Math.abs(targetScale);
              this.FocusX = FocusX;
              this.FocusY = FocusY;
              currentScaleLevel = getCurrentScaleLevel();
              currentAbsScaleLevel = Math.abs(currentScaleLevel);
              if (currentAbsScaleLevel < targetAbsScale)
                  scaleFactor = GradientScaleUp;
              else if (currentAbsScaleLevel > targetAbsScale)
                  scaleFactor = GradientScaleDown;
              else if (currentAbsScaleLevel == targetAbsScale)
                  scaleFactor = 1.0f;
          }
  
          @Override
          public void run() {
              mScaleMatrix.postScale(scaleFactor, scaleFactor, FocusX, FocusY);
              checkBorderAndCenterWhenScale();
              setImageMatrix(mScaleMatrix);
  
              currentScaleLevel = getCurrentScaleLevel();
              currentAbsScaleLevel = Math.abs(currentScaleLevel);
              if ((scaleFactor < 1.0f && currentAbsScaleLevel > targetAbsScale)
                      || (scaleFactor > 1.0f && currentAbsScaleLevel < targetAbsScale)) {
                  postDelayed(this, springBackRunnableDelay);
              } else {
                  scaleFactor = targetScale / currentScaleLevel;
  
                  mScaleMatrix.postScale(scaleFactor, scaleFactor, FocusX, FocusY);
                  checkBorderAndCenterWhenScale();
                  setImageMatrix(mScaleMatrix);
  
                  isAutoScale = false;
              }
  
          }
      }
  
  
      private class AutoRotateRunnable implements Runnable {
          float targetRotateAngle;
          long TotalRotateTimes;
          float AnglePerTime;
          float AccumulativeRotateTimes;
          float AccumulativeRotateAngles;
          float initScaleLevel;
          double ScalePerTime;
  
          AutoRotateRunnable(float targetRotateAngle, float initScaleLevel, long TotalRotateTimes) {
              this.targetRotateAngle = targetRotateAngle;
              this.TotalRotateTimes = TotalRotateTimes;
              AnglePerTime = targetRotateAngle / this.TotalRotateTimes;
              AccumulativeRotateTimes = 0;
              AccumulativeRotateAngles = 0.0f;
              this.initScaleLevel = initScaleLevel;
              if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
                  ScalePerTime = Math.pow(mInitScale / initScaleLevel, 1.0 / TotalRotateTimes);
              } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
                  if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE)
                      ScalePerTime = Math.pow(mInitScale / initScaleLevel, 1.0 / TotalRotateTimes);
                  else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT)
                      ScalePerTime = Math.pow(mInitPortraitScale / initScaleLevel, 1.0 / TotalRotateTimes);
              }
  
          }
  
          @Override
          public void run() {
              calculateImageCenterCoordinates();
  
              mScaleMatrix.postRotate(AnglePerTime, ImageCenterX, ImageCenterY);
              mScaleMatrix.postScale((float) ScalePerTime, (float) ScalePerTime, ImageCenterX, ImageCenterY);
              mScaleMatrix.postTranslate((XuanImageViewCenterX - ImageCenterX) / (TotalRotateTimes - AccumulativeRotateTimes), (XuanImageViewCenterY - ImageCenterY) / (TotalRotateTimes - AccumulativeRotateTimes));
              setImageMatrix(mScaleMatrix);
              AccumulativeRotateTimes++;
              AccumulativeRotateAngles += AnglePerTime;
  
  
              if (AccumulativeRotateTimes < TotalRotateTimes) {
                  postDelayed(this, autoRotationRunnableDelay);
              } else {
                  currentScaleLevel = getCurrentScaleLevel();
                  currentAbsScaleLevel = Math.abs(currentScaleLevel);
                  calculateImageCenterCoordinates();
                  mScaleMatrix.postRotate(targetRotateAngle - AccumulativeRotateAngles, ImageCenterX, ImageCenterY);
                  if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
                      mScaleMatrix.postScale(Math.abs(mInitScale / currentScaleLevel), Math.abs(mInitScale / currentScaleLevel), ImageCenterX, ImageCenterY);
                  } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
                      if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
                          mScaleMatrix.postScale(Math.abs(mInitScale / currentScaleLevel), Math.abs(mInitScale / currentScaleLevel), ImageCenterX, ImageCenterY);
                      } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
  //                        mScaleMatrix.postScale(Math.abs(mInitPortraitScale / getCurrentScaleLevel()), Math.abs(mInitPortraitScale / getCurrentScaleLevel()), ImageCenterX, ImageCenterY);
                          mTempInitPortraitScale = Math.abs(currentScaleLevel);
                          mTempPortraitMaxScale = mTempInitPortraitScale * mMaxScaleMultiple;
                          mTempPortraitDoubleTabScale = mTempInitPortraitScale * mDoubleTabScaleMultiple;
                      }
                  }
                  mScaleMatrix.postTranslate(XuanImageViewCenterX - ImageCenterX, XuanImageViewCenterY - ImageCenterY);
  
                  checkBorderAndCenterWhenScale();
  
                  setImageMatrix(mScaleMatrix);
                  isAutoRotated = false;
              }
  
  
          }
      }
  
      /**
       * Set a boolean value to determine whether rotation function is turned on.
       *
       * @param toggle determine whether rotation function is turned on
       */
      public void setRotationToggle(boolean toggle) {
          mRotationToggle = toggle;
      }
  
      /**
       * @return current RotationToggle
       */
      public boolean getRotationToggle() {
          return mRotationToggle;
      }
  
      /**
       * Set AutoRotateCategory, there are two alternative values of it : XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION, XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM.
       *
       * @param category
       */
      public void setAutoRotateCategory(int category) {
          if (category == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION || category == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM)
              mAutoRotateCategory = category;
          else
              mAutoRotateCategory = XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION;
      }
  
      /**
       * @return current AutoRotateCategory
       */
      public int getAutoRotateCategory() {
          return mAutoRotateCategory;
      }
  
      /**
       * An image is scaled to an InitScale to fit the size of XuanImageView at the very beginning.
       * MaxScale = MaxScaleMultiple * InitScale holds.
       *
       * @param maxScaleMultiple
       */
      public void setMaxScaleMultiple(float maxScaleMultiple) {
          mMaxScaleMultiple = maxScaleMultiple;
      }
  
      /**
       * @return current MaxScaleMultiple
       */
      public float getMaxScaleMultiple() {
          return mMaxScaleMultiple;
      }
  
      /**
       * When image's current scale level is smaller than DoubleTabScale, the image will scale up to DoubleTapScale if an double-tap gesture is detected.
       * DoubleTapScale = DoubleTabScaleMultiple * InitScale holds.
       *
       * @param doubleTabScaleMultiple
       */
      public void setDoubleTabScaleMultiple(float doubleTabScaleMultiple) {
          mDoubleTabScaleMultiple = doubleTabScaleMultiple;
      }
  
      /**
       * @return current DoubleTabScaleMultiple
       */
      public float getDoubleTabScaleMultiple() {
          return mDoubleTabScaleMultiple;
      }
  
      /**
       * If current scale level is smaller than InitScale and image is not in rotation state,
       * the image will scale up to InitScale with SpringBackGradientScaleUpLevel step by step.
       * Default springBackGradientScaleUpLevel is  1.01f.
       *
       * @param springBackGradientScaleUpLevel
       */
      public void setSpringBackGradientScaleUpLevel(float springBackGradientScaleUpLevel) {
          mSpringBackGradientScaleUpLevel = springBackGradientScaleUpLevel;
      }
  
      /**
       * @return current SpringBackGradientScaleUpLevel
       */
      public float getSpringBackGradientScaleUpLevel() {
          return mSpringBackGradientScaleUpLevel;
      }
  
      /**
       * If current scale level is bigger than MaxScale and image is not in rotation state,
       * the image will scale down to MaxScale with SpringBackGradientScaleDownLevel step by step.
       * Default springBackGradientScaleDownLevel is 0.99f.
       *
       * @param springBackGradientScaleDownLevel
       */
      public void setSpringBackGradientScaleDownLevel(float springBackGradientScaleDownLevel) {
          mSpringBackGradientScaleDownLevel = springBackGradientScaleDownLevel;
      }
  
      /**
       * @return current SpringBackGradientScaleDownLevel
       */
      public float getSpringBackGradientScaleDownLevel() {
          return mSpringBackGradientScaleDownLevel;
      }
  
      /**
       * When image's current scale level is smaller than DoubleTabScale,
       * the image will scale up to DoubleTapScale with DoubleTapGradientScaleUpLevel step by step if a double-tap gesture is detected.
       * Default doubleTalGradientScaleUpLevel is 1.05f.
       *
       * @param doubleTapGradientScaleUpLevel
       */
      public void setDoubleTapGradientScaleUpLevel(float doubleTapGradientScaleUpLevel) {
          mDoubleTapGradientScaleUpLevel = doubleTapGradientScaleUpLevel;
      }
  
      /**
       * @return current DoubleTapGradientScaleUpLevel
       */
      public float getDoubleTapGradientScaleUpLevel() {
          return mDoubleTapGradientScaleUpLevel;
      }
  
      /**
       * When image's current scale level is bigger than DoubleTabScale,
       * the image will scale down to InitScale with DoubleTapGradientScaleDownLevel step by step if a double-tap gesture is detected.
       * Default doubleTabGradientScaleDownLevel is 0.95f.
       *
       * @param doubleTapGradientScaleDownLevel
       */
      public void setDoubleTabGradientScaleDownLevel(float doubleTapGradientScaleDownLevel) {
          mDoubleTapGradientScaleDownLevel = doubleTapGradientScaleDownLevel;
      }
  
      /**
       * @return current DoubleTapGradientScaleDownLevel
       */
      public float getDoubleTapGradientScaleDownLevel() {
          return mDoubleTapGradientScaleDownLevel;
      }
  
      /**
       * When image's current rotation angle is bigger than AutoRotationTrigger, the image will rotate in the same direction and scale back to it's initial state if rotation gesture is released.
       * When image's current rotation angle is smaller than AutoRotationTrigger, the image will rotate in the opposite direction and scale back to it's initial state if rotation gesture is released.
       * Default AutoRotationTrigger is 60 (degrees).
       *
       * @param autoRotationTrigger
       */
      public void setAutoRotationTrigger(float autoRotationTrigger) {
          this.autoRotationTrigger = autoRotationTrigger;
      }
  
      /**
       * @return current AutoRotationTrigger
       */
      public float getAutoRotationTrigger() {
          return autoRotationTrigger;
      }
  
      /**
       * Default SpringBackRunnableDelay is 10 (milliseconds).
       *
       * @param delay
       */
      public void setSpringBackRunnableDelay(int delay) {
          springBackRunnableDelay = delay;
      }
  
      /**
       * @return current SpringBackRunnableDelay
       */
      public int getSpringBackRunnableDelay() {
          return springBackRunnableDelay;
      }
  
      /**
       * Default DoubleTapRunnableDelay is 10 (milliseconds).
       *
       * @param delay
       */
      public void setDoubleTapScaleRunnableDelay(int delay) {
          doubleTabScaleRunnableDelay = delay;
      }
  
      /**
       * @return current DoubleTabScaleRunnableDelay
       */
      public int getDoubleTabScaleRunnableDelay() {
          return doubleTabScaleRunnableDelay;
      }
  
      /**
       * Default AutoRotationRunnableDelay is 5 (milliseconds).
       *
       * @param delay
       */
      public void setAutoRotationRunnableDelay(int delay) {
          autoRotationRunnableDelay = delay;
      }
  
      /**
       * @return current AutoRotationRunnableDelay
       */
      public int getAutoRotationRunnalbleDelay() {
          return autoRotationRunnableDelay;
      }
  
      /**
       * Default AutoRotationRunnableTimes is 10 (times).
       *
       * @param times
       */
      public void setAutoRotationRunnableTimes(int times) {
          autoRotationRunnableTimes = times;
      }
  
      /**
       * @return current AutoRotationRunnableTimes
       */
      public int getAutoRotationRunnableTimes() {
          return autoRotationRunnableTimes;
      }
  
  
      /**
       * Notice the Image can only start to be rotated when it's in initial state. But the image may be scaled up or down a little bit by ScaleGestureDetector
       * in advance when you try to rotate it, hence, currentScaleLevel is not precisely equal to initScaleLevel. Here, an AllowableFloatError is existed to handle
       * this situation. When Math.abs(currentScaleLevel - initScaleLevel) is smaller than allowableFloatError, RotateGestureDetector.onTouchEvent() can be invoked.
       * Default allowableFloatError is 1E-6, it should be compatible with most of devices. For devices whose display resolution and aspect ratio is not normal, allowableFloatError may
       * need to be tuned. eg., for Galaxy S8, 3E-3 works well. Of course, 3E-3 also works for most of devices because 1E-6 is smaller than 3E-3.
       * @param allowableFloatError
       */
      public void setAllowableFloatError(double allowableFloatError){
          this.allowableFloatError = allowableFloatError;
      }
  
      public double getAllowableFloatError(){
          return allowableFloatError;
      }
  
      /**
       * In  AUTO_ROTATE_CATEGORY_MAGNETISM mode, the image may be showed under a fixed rotation angle like 90 degrees, 270 degrees,
       * 450 degrees, ect., then allowablePortraitFloatError should handle the situation when currentPortraitScaleLevel is not precisely
       * equal to initPortraitScaleLevel.
       * Default allowablePortraitFloatError is 1E-12, it should be compatible with most of devices. For devices whose display resolution and aspect ratio is not normal, allowablePortraitFloatError may
       * need to be tuned. eg., for Galaxy S8, 5E-8 works well. Of course, 5E-8 also works for most of devices because 1E-12 is smaller than 5E-8.
       * @see XuanImageView#setAllowableFloatError(double)
       *
       * @param allowablePortraitFloatError
       */
      public void setAllowablePortraitFloatError(double allowablePortraitFloatError){
          this.allowablePortraitFloatError = allowablePortraitFloatError;
      }
  
      public double getAllowablePortraitFloatError(){
          return allowablePortraitFloatError;
      }
  
  }
  