package com.yuyh.sprintnba.widget.photodraweeview; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.widget.ScrollerCompat; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewParent; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.view.DraweeView; import java.lang.ref.WeakReference; public class Attacher implements IAttacher, View.OnTouchListener, OnScaleDragGestureListener { private static final int EDGE_NONE = -1; private static final int EDGE_LEFT = 0; private static final int EDGE_RIGHT = 1; private static final int EDGE_BOTH = 2; private final float[] mMatrixValues = new float[9]; private final RectF mDisplayRect = new RectF(); private final Interpolator mZoomInterpolator = new AccelerateDecelerateInterpolator(); private float mMinScale = IAttacher.DEFAULT_MIN_SCALE; private float mMidScale = IAttacher.DEFAULT_MID_SCALE; private float mMaxScale = IAttacher.DEFAULT_MAX_SCALE; private long mZoomDuration = IAttacher.ZOOM_DURATION; private ScaleDragDetector mScaleDragDetector; private GestureDetectorCompat mGestureDetector; private boolean mBlockParentIntercept = false; private boolean mAllowParentInterceptOnEdge = true; private int mScrollEdge = EDGE_BOTH; private final Matrix mMatrix = new Matrix(); private int mImageInfoHeight = -1, mImageInfoWidth = -1; private FlingRunnable mCurrentFlingRunnable; private WeakReference<DraweeView<GenericDraweeHierarchy>> mDraweeView; private OnPhotoTapListener mPhotoTapListener; private OnViewTapListener mViewTapListener; private View.OnLongClickListener mLongClickListener; private OnScaleChangeListener mScaleChangeListener; public Attacher(DraweeView<GenericDraweeHierarchy> draweeView) { mDraweeView = new WeakReference<>(draweeView); draweeView.getHierarchy().setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER); draweeView.setOnTouchListener(this); mScaleDragDetector = new ScaleDragDetector(draweeView.getContext(), this); mGestureDetector = new GestureDetectorCompat(draweeView.getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public void onLongPress(MotionEvent e) { super.onLongPress(e); if (null != mLongClickListener) { mLongClickListener.onLongClick(getDraweeView()); } } }); mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); } public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) { if (newOnDoubleTapListener != null) { this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener); } else { this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); } } @Nullable public DraweeView<GenericDraweeHierarchy> getDraweeView() { return mDraweeView.get(); } @Override public float getMinimumScale() { return mMinScale; } @Override public float getMediumScale() { return mMidScale; } @Override public float getMaximumScale() { return mMaxScale; } @Override public void setMaximumScale(float maximumScale) { checkZoomLevels(mMinScale, mMidScale, maximumScale); mMaxScale = maximumScale; } @Override public void setMediumScale(float mediumScale) { checkZoomLevels(mMinScale, mediumScale, mMaxScale); mMidScale = mediumScale; } @Override public void setMinimumScale(float minimumScale) { checkZoomLevels(minimumScale, mMidScale, mMaxScale); mMinScale = minimumScale; } @Override public float getScale() { return (float) Math.sqrt( (float) Math.pow(getMatrixValue(mMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow( getMatrixValue(mMatrix, Matrix.MSKEW_Y), 2)); } @Override public void setScale(float scale) { setScale(scale, false); } @Override public void setScale(float scale, boolean animate) { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView != null) { setScale(scale, (draweeView.getRight()) / 2, (draweeView.getBottom()) / 2, false); } } @Override public void setScale(float scale, float focalX, float focalY, boolean animate) { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView == null || scale < mMinScale || scale > mMaxScale) { return; } if (animate) { draweeView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY)); } else { mMatrix.setScale(scale, scale, focalX, focalY); checkMatrixAndInvalidate(); } } @Override public void setZoomTransitionDuration(long duration) { duration = duration < 0 ? IAttacher.ZOOM_DURATION : duration; mZoomDuration = duration; } @Override public void setAllowParentInterceptOnEdge(boolean allow) { mAllowParentInterceptOnEdge = allow; } @Override public void setOnScaleChangeListener(OnScaleChangeListener listener) { mScaleChangeListener = listener; } @Override public void setOnLongClickListener(View.OnLongClickListener listener) { mLongClickListener = listener; } @Override public void setOnPhotoTapListener(OnPhotoTapListener listener) { mPhotoTapListener = listener; } @Override public void setOnViewTapListener(OnViewTapListener listener) { mViewTapListener = listener; } @Override public OnPhotoTapListener getOnPhotoTapListener() { return mPhotoTapListener; } @Override public OnViewTapListener getOnViewTapListener() { return mViewTapListener; } @Override public void update(int imageInfoWidth, int imageInfoHeight) { mImageInfoWidth = imageInfoWidth; mImageInfoHeight = imageInfoHeight; updateBaseMatrix(); } private static void checkZoomLevels(float minZoom, float midZoom, float maxZoom) { if (minZoom >= midZoom) { throw new IllegalArgumentException("MinZoom has to be less than MidZoom"); } else if (midZoom >= maxZoom) { throw new IllegalArgumentException("MidZoom has to be less than MaxZoom"); } } private int getViewWidth() { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView != null) { return draweeView.getWidth() - draweeView.getPaddingLeft() - draweeView.getPaddingRight(); } return 0; } private int getViewHeight() { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView != null) { return draweeView.getHeight() - draweeView.getPaddingTop() - draweeView.getPaddingBottom(); } return 0; } private float getMatrixValue(Matrix matrix, int whichValue) { matrix.getValues(mMatrixValues); return mMatrixValues[whichValue]; } public Matrix getDrawMatrix() { return mMatrix; } public RectF getDisplayRect() { checkMatrixBounds(); return getDisplayRect(getDrawMatrix()); } public void checkMatrixAndInvalidate() { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView == null) { return; } if (checkMatrixBounds()) { draweeView.invalidate(); } } public boolean checkMatrixBounds() { RectF rect = getDisplayRect(getDrawMatrix()); if (rect == null) { return false; } float height = rect.height(); float width = rect.width(); float deltaX = 0.0F; float deltaY = 0.0F; int viewHeight = getViewHeight(); if (height <= (float) viewHeight) { deltaY = (viewHeight - height) / 2 - rect.top; } else if (rect.top > 0.0F) { deltaY = -rect.top; } else if (rect.bottom < (float) viewHeight) { deltaY = viewHeight - rect.bottom; } int viewWidth = getViewWidth(); if (width <= viewWidth) { deltaX = (viewWidth - width) / 2 - rect.left; mScrollEdge = EDGE_BOTH; } else if (rect.left > 0) { deltaX = -rect.left; mScrollEdge = EDGE_LEFT; } else if (rect.right < viewWidth) { deltaX = viewWidth - rect.right; mScrollEdge = EDGE_RIGHT; } else { mScrollEdge = EDGE_NONE; } mMatrix.postTranslate(deltaX, deltaY); return true; } private RectF getDisplayRect(Matrix matrix) { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView == null || (mImageInfoWidth == -1 && mImageInfoHeight == -1)) { return null; } mDisplayRect.set(0.0F, 0.0F, mImageInfoWidth, mImageInfoHeight); draweeView.getHierarchy().getActualImageBounds(mDisplayRect); matrix.mapRect(mDisplayRect); return mDisplayRect; } private void updateBaseMatrix() { if (mImageInfoWidth == -1 && mImageInfoHeight == -1) { return; } resetMatrix(); } private void resetMatrix() { mMatrix.reset(); checkMatrixBounds(); DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView != null) { draweeView.invalidate(); } } private void checkMinScale() { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView == null) { return; } if (getScale() < mMinScale) { RectF rect = getDisplayRect(); if (null != rect) { draweeView.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY())); } } } @Override public void onScale(float scaleFactor, float focusX, float focusY) { if (getScale() < mMaxScale || scaleFactor < 1.0F) { if (mScaleChangeListener != null) { mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); } mMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); checkMatrixAndInvalidate(); } } @Override public void onScaleEnd() { checkMinScale(); } @Override public void onDrag(float dx, float dy) { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView != null && !mScaleDragDetector.isScaling()) { mMatrix.postTranslate(dx, dy); checkMatrixAndInvalidate(); ViewParent parent = draweeView.getParent(); if (parent == null) { return; } if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) { if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f) || ( mScrollEdge == EDGE_RIGHT && dx <= -1f)) { parent.requestDisallowInterceptTouchEvent(false); } } else { parent.requestDisallowInterceptTouchEvent(true); } } } @Override public void onFling(float startX, float startY, float velocityX, float velocityY) { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView == null) { return; } mCurrentFlingRunnable = new FlingRunnable(draweeView.getContext()); mCurrentFlingRunnable.fling(getViewWidth(), getViewHeight(), (int) velocityX, (int) velocityY); draweeView.post(mCurrentFlingRunnable); } @Override public boolean onTouch(View v, MotionEvent event) { int action = MotionEventCompat.getActionMasked(event); switch (action) { case MotionEvent.ACTION_DOWN: { ViewParent parent = v.getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } cancelFling(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { ViewParent parent = v.getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(false); } } break; } boolean wasScaling = mScaleDragDetector.isScaling(); boolean wasDragging = mScaleDragDetector.isDragging(); boolean handled = mScaleDragDetector.onTouchEvent(event); boolean noScale = !wasScaling && !mScaleDragDetector.isScaling(); boolean noDrag = !wasDragging && !mScaleDragDetector.isDragging(); mBlockParentIntercept = noScale && noDrag; if (mGestureDetector.onTouchEvent(event)) { handled = true; } return handled; } private class AnimatedZoomRunnable implements Runnable { private final float mFocalX, mFocalY; private final long mStartTime; private final float mZoomStart, mZoomEnd; public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) { mFocalX = focalX; mFocalY = focalY; mStartTime = System.currentTimeMillis(); mZoomStart = currentZoom; mZoomEnd = targetZoom; } @Override public void run() { DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView == null) { return; } float t = interpolate(); float scale = mZoomStart + t * (mZoomEnd - mZoomStart); float deltaScale = scale / getScale(); onScale(deltaScale, mFocalX, mFocalY); if (t < 1f) { postOnAnimation(draweeView, this); } } private float interpolate() { float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration; t = Math.min(1f, t); t = mZoomInterpolator.getInterpolation(t); return t; } } private class FlingRunnable implements Runnable { private final ScrollerCompat mScroller; private int mCurrentX, mCurrentY; public FlingRunnable(Context context) { mScroller = ScrollerCompat.create(context); } public void cancelFling() { mScroller.abortAnimation(); } 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 (startX != maxX || startY != maxY) { mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0); } } @Override public void run() { if (mScroller.isFinished()) { return; } DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView(); if (draweeView != null && mScroller.computeScrollOffset()) { final int newX = mScroller.getCurrX(); final int newY = mScroller.getCurrY(); mMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY); draweeView.invalidate(); mCurrentX = newX; mCurrentY = newY; postOnAnimation(draweeView, this); } } } private void cancelFling() { if (mCurrentFlingRunnable != null) { mCurrentFlingRunnable.cancelFling(); mCurrentFlingRunnable = null; } } private void postOnAnimation(View view, Runnable runnable) { if (Build.VERSION.SDK_INT >= 16) { view.postOnAnimation(runnable); } else { view.postDelayed(runnable, 16L); } } protected void onDetachedFromWindow() { cancelFling(); } }