@Override
  public void onDrag(float dx, float dy) {
    if (mScaleDragDetector.isScaling()) {
      return; // Do not drag if we are already scaling
    }

    if (DEBUG) {
      LogManager.getLogger().d(LOG_TAG, String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
    }

    ImageView imageView = getImageView();
    mSuppMatrix.postTranslate(dx, dy);
    checkAndDisplayMatrix();

    /**
     * Here we decide whether to let the ImageView's parent to start taking over the touch event.
     *
     * <p>First we check whether this function is enabled. We never want the parent to take over if
     * we're scaling. We then check the edge we're on, and the direction of the scroll (i.e. if
     * we're pulling against the edge, aka 'overscrolling', let the parent take over).
     */
    ViewParent parent = imageView.getParent();
    if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
      if (mScrollEdge == EDGE_BOTH
          || (mScrollEdge == EDGE_LEFT && dx >= 1f)
          || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
        if (null != parent) parent.requestDisallowInterceptTouchEvent(false);
      }
    } else {
      if (null != parent) {
        parent.requestDisallowInterceptTouchEvent(true);
      }
    }
  }
  @SuppressLint("ClickableViewAccessibility")
  @Override
  public boolean onTouch(View v, MotionEvent ev) {
    boolean handled = false;

    if (mZoomEnabled && hasDrawable((ImageView) v)) {
      ViewParent parent = v.getParent();
      switch (ev.getAction()) {
        case ACTION_DOWN:
          // First, disable the Parent from intercepting the touch
          // event
          if (null != parent) {
            parent.requestDisallowInterceptTouchEvent(true);
          } else {
            LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null");
          }

          // If we're flinging, and the user presses down, cancel
          // fling
          cancelFling();
          break;

        case ACTION_CANCEL:
        case ACTION_UP:
          // If the user has zoomed less than min scale, zoom back
          // to min scale
          if (getScale() < mMinScale) {
            RectF rect = getDisplayRect();
            if (null != rect) {
              v.post(
                  new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY()));
              handled = true;
            }
          }
          break;
      }

      // Try the Scale/Drag detector
      if (null != mScaleDragDetector) {
        boolean wasScaling = mScaleDragDetector.isScaling();
        boolean wasDragging = mScaleDragDetector.isDragging();

        handled = mScaleDragDetector.onTouchEvent(ev);

        boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
        boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();

        mBlockParentIntercept = didntScale && didntDrag;
      }

      // Check to see if the user double tapped
      if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
        handled = true;
      }
    }

    return handled;
  }
    public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
      final RectF rect = getDisplayRect();
      if (null == rect) {
        return;
      }

      final int startX = Math.round(-rect.left);
      final int minX, maxX, minY, maxY;

      if (viewWidth < rect.width()) {
        minX = 0;
        maxX = Math.round(rect.width() - viewWidth);
      } else {
        minX = maxX = startX;
      }

      final int startY = Math.round(-rect.top);
      if (viewHeight < rect.height()) {
        minY = 0;
        maxY = Math.round(rect.height() - viewHeight);
      } else {
        minY = maxY = startY;
      }

      mCurrentX = startX;
      mCurrentY = startY;

      if (DEBUG) {
        LogManager.getLogger()
            .d(
                LOG_TAG,
                "fling. StartX:"
                    + startX
                    + " StartY:"
                    + startY
                    + " MaxX:"
                    + maxX
                    + " MaxY:"
                    + maxY);
      }

      // If we actually can move, fling the scroller
      if (startX != maxX || startY != maxY) {
        mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
      }
    }
  @Override
  public void onScale(float scaleFactor, float focusX, float focusY) {
    if (DEBUG) {
      LogManager.getLogger()
          .d(
              LOG_TAG,
              String.format(
                  "onScale: scale: %.2f. fX: %.2f. fY: %.2f", scaleFactor, focusX, focusY));
    }

    if (getScale() < mMaxScale || scaleFactor < 1f) {
      if (null != mScaleChangeListener) {
        mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
      }
      mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
      checkAndDisplayMatrix();
    }
  }
  public ImageView getImageView() {
    ImageView imageView = null;

    if (null != mImageView) {
      imageView = mImageView.get();
    }

    // If we don't have an ImageView, call cleanup()
    if (null == imageView) {
      cleanup();
      LogManager.getLogger()
          .i(
              LOG_TAG,
              "ImageView no longer exists. You should not use this PhotoViewAttacher any more.");
    }

    return imageView;
  }
  @Override
  public void setScale(float scale, float focalX, float focalY, boolean animate) {
    ImageView imageView = getImageView();

    if (null != imageView) {
      // Check to see if the scale is within bounds
      if (scale < mMinScale || scale > mMaxScale) {
        LogManager.getLogger()
            .i(LOG_TAG, "Scale must be within the range of minScale and maxScale");
        return;
      }

      if (animate) {
        imageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY));
      } else {
        mSuppMatrix.setScale(scale, scale, focalX, focalY);
        checkAndDisplayMatrix();
      }
    }
  }
 @Override
 public void onFling(float startX, float startY, float velocityX, float velocityY) {
   if (DEBUG) {
     LogManager.getLogger()
         .d(
             LOG_TAG,
             "onFling. sX: "
                 + startX
                 + " sY: "
                 + startY
                 + " Vx: "
                 + velocityX
                 + " Vy: "
                 + velocityY);
   }
   ImageView imageView = getImageView();
   mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
   mCurrentFlingRunnable.fling(
       getImageViewWidth(imageView),
       getImageViewHeight(imageView),
       (int) velocityX,
       (int) velocityY);
   imageView.post(mCurrentFlingRunnable);
 }
    @Override
    public void run() {
      if (mScroller.isFinished()) {
        return; // remaining post that should not be handled
      }

      ImageView imageView = getImageView();
      if (null != imageView && mScroller.computeScrollOffset()) {

        final int newX = mScroller.getCurrX();
        final int newY = mScroller.getCurrY();

        if (DEBUG) {
          LogManager.getLogger()
              .d(
                  LOG_TAG,
                  "fling run(). CurrentX:"
                      + mCurrentX
                      + " CurrentY:"
                      + mCurrentY
                      + " NewX:"
                      + newX
                      + " NewY:"
                      + newY);
        }

        mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
        setImageViewMatrix(getDrawMatrix());

        mCurrentX = newX;
        mCurrentY = newY;

        // Post On animation
        Compat.postOnAnimation(imageView, this);
      }
    }
 public void cancelFling() {
   if (DEBUG) {
     LogManager.getLogger().d(LOG_TAG, "Cancel Fling");
   }
   mScroller.forceFinished(true);
 }