| /******************************************************************************* | 
|  * Copyright 2011, 2012 Chris Banes. | 
|  * | 
|  * 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. | 
|  *******************************************************************************/ | 
| package com.doumee.lib_coremodel.view.zoomview; | 
|   | 
| import android.annotation.SuppressLint; | 
| import android.content.Context; | 
| import android.graphics.Matrix; | 
| import android.graphics.Matrix.ScaleToFit; | 
| import android.graphics.RectF; | 
| import android.graphics.drawable.Drawable; | 
| import android.os.Build.VERSION; | 
| import android.os.Build.VERSION_CODES; | 
| import android.util.Log; | 
| import android.view.GestureDetector; | 
| import android.view.MotionEvent; | 
| import android.view.View; | 
| import android.view.View.OnLongClickListener; | 
| import android.view.ViewTreeObserver; | 
| import android.widget.ImageView; | 
| import android.widget.ImageView.ScaleType; | 
|   | 
| import java.lang.ref.WeakReference; | 
|   | 
| public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener, | 
|         VersionedGestureDetector.OnGestureListener, | 
|         GestureDetector.OnDoubleTapListener, | 
|         ViewTreeObserver.OnGlobalLayoutListener { | 
|   | 
|     static final String LOG_TAG = "PhotoViewAttacher"; | 
|   | 
|     // let debug flag be dynamic, but still Proguard can be used to remove from | 
|     // release builds | 
|     static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); | 
|   | 
|     static final int EDGE_NONE = -1; | 
|     static final int EDGE_LEFT = 0; | 
|     static final int EDGE_RIGHT = 1; | 
|     static final int EDGE_BOTH = 2; | 
|   | 
|     public static final float DEFAULT_MAX_SCALE = 3.0f; | 
|     public static final float DEFAULT_MID_SCALE = 1.75f; | 
|     public static final float DEFAULT_MIN_SCALE = 1.0f; | 
|   | 
|     private float mMinScale = DEFAULT_MIN_SCALE; | 
|     private float mMidScale = DEFAULT_MID_SCALE; | 
|     private float mMaxScale = DEFAULT_MAX_SCALE; | 
|   | 
|     private boolean mAllowParentInterceptOnEdge = true; | 
|   | 
|     private static void checkZoomLevels(float minZoom, float midZoom, | 
|             float maxZoom) { | 
|         if (minZoom >= midZoom) { | 
|             throw new IllegalArgumentException( | 
|                     "MinZoom should be less than MidZoom"); | 
|         } else if (midZoom >= maxZoom) { | 
|             throw new IllegalArgumentException( | 
|                     "MidZoom should be less than MaxZoom"); | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * @return true if the ImageView exists, and it's Drawable existss | 
|      */ | 
|     private static boolean hasDrawable(ImageView imageView) { | 
|         return null != imageView && null != imageView.getDrawable(); | 
|     } | 
|   | 
|     /** | 
|      * @return true if the ScaleType is supported. | 
|      */ | 
|     private static boolean isSupportedScaleType(final ScaleType scaleType) { | 
|         if (null == scaleType) { | 
|             return false; | 
|         } | 
|   | 
|         switch (scaleType) { | 
|         case MATRIX: | 
|             throw new IllegalArgumentException(scaleType.name() | 
|                     + " is not supported in PhotoView"); | 
|   | 
|         default: | 
|             return true; | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * Set's the ImageView's ScaleType to Matrix. | 
|      */ | 
|     private static void setImageViewScaleTypeMatrix(ImageView imageView) { | 
|         if (null != imageView) { | 
|             if (imageView instanceof PhotoView) { | 
|                 /** | 
|                  * PhotoView sets it's own ScaleType to Matrix, then diverts all | 
|                  * calls setScaleType to this.setScaleType. Basically we don't | 
|                  * need to do anything here | 
|                  */ | 
|             } else { | 
|                 imageView.setScaleType(ScaleType.MATRIX); | 
|             } | 
|         } | 
|     } | 
|   | 
|     private WeakReference<ImageView> mImageView; | 
|     private ViewTreeObserver mViewTreeObserver; | 
|   | 
|     // Gesture Detectors | 
|     private GestureDetector mGestureDetector; | 
|     private VersionedGestureDetector mScaleDragDetector; | 
|   | 
|     // These are set so we don't keep allocating them on the heap | 
|     private final Matrix mBaseMatrix = new Matrix(); | 
|     private final Matrix mDrawMatrix = new Matrix(); | 
|     private final Matrix mSuppMatrix = new Matrix(); | 
|     private final RectF mDisplayRect = new RectF(); | 
|     private final float[] mMatrixValues = new float[9]; | 
|   | 
|     // Listeners | 
|     private OnMatrixChangedListener mMatrixChangeListener; | 
|     private OnPhotoTapListener mPhotoTapListener; | 
|     private OnViewTapListener mViewTapListener; | 
|     private OnLongClickListener mLongClickListener; | 
|   | 
|     private int mIvTop, mIvRight, mIvBottom, mIvLeft; | 
|     private FlingRunnable mCurrentFlingRunnable; | 
|     private int mScrollEdge = EDGE_BOTH; | 
|   | 
|     private boolean mZoomEnabled; | 
|     private ScaleType mScaleType = ScaleType.FIT_CENTER; | 
|   | 
|     public PhotoViewAttacher(ImageView imageView) { | 
|         mImageView = new WeakReference<ImageView>(imageView); | 
|   | 
|         imageView.setOnTouchListener(this); | 
|   | 
|         mViewTreeObserver = imageView.getViewTreeObserver(); | 
|         mViewTreeObserver.addOnGlobalLayoutListener(this); | 
|   | 
|         // Make sure we using MATRIX Scale Type | 
|         setImageViewScaleTypeMatrix(imageView); | 
|   | 
|         if (!imageView.isInEditMode()) { | 
|             // Create Gesture Detectors... | 
|             mScaleDragDetector = VersionedGestureDetector.newInstance( | 
|                     imageView.getContext(), this); | 
|   | 
|             mGestureDetector = new GestureDetector(imageView.getContext(), | 
|                     new GestureDetector.SimpleOnGestureListener() { | 
|   | 
|                         // forward long click listener | 
|                         @Override | 
|                         public void onLongPress(MotionEvent e) { | 
|                             if (null != mLongClickListener) { | 
|                                 mLongClickListener.onLongClick(mImageView.get()); | 
|                             } | 
|                         } | 
|                     }); | 
|   | 
|             mGestureDetector.setOnDoubleTapListener(this); | 
|   | 
|             // Finally, update the UI so that we're zoomable | 
|             setZoomable(true); | 
|         } | 
|     } | 
|   | 
|     @Override | 
|     public final boolean canZoom() { | 
|         return mZoomEnabled; | 
|     } | 
|   | 
|     /** | 
|      * Clean-up the resources attached to this object. This needs to be called | 
|      * when the ImageView is no longer used. A good example is from | 
|      * {@link View#onDetachedFromWindow()} or from | 
|      * {@link android.app.Activity#onDestroy()}. This is automatically called if | 
|      * you are using {@link uk.co.senab.photoview.PhotoView}. | 
|      */ | 
|     @SuppressLint("NewApi") | 
|     @SuppressWarnings("deprecation") | 
|     public final void cleanup() { | 
|         if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { | 
|             if (null != mImageView) { | 
|                 mImageView.get().getViewTreeObserver() | 
|                         .removeOnGlobalLayoutListener(this); | 
|             } | 
|   | 
|             if (null != mViewTreeObserver && mViewTreeObserver.isAlive()) { | 
|                 mViewTreeObserver.removeOnGlobalLayoutListener(this); | 
|   | 
|                 mViewTreeObserver = null; | 
|   | 
|                 // Clear listeners too | 
|                 mMatrixChangeListener = null; | 
|                 mPhotoTapListener = null; | 
|                 mViewTapListener = null; | 
|                 // Finally, clear ImageView | 
|                 mImageView = null; | 
|             } | 
|   | 
|         } else { | 
|             if (null != mImageView) { | 
|                 mImageView.get().getViewTreeObserver() | 
|                         .removeGlobalOnLayoutListener(this); | 
|             } | 
|   | 
|             if (null != mViewTreeObserver && mViewTreeObserver.isAlive()) { | 
|                 mViewTreeObserver.removeGlobalOnLayoutListener(this); | 
|   | 
|                 mViewTreeObserver = null; | 
|   | 
|                 // Clear listeners too | 
|                 mMatrixChangeListener = null; | 
|                 mPhotoTapListener = null; | 
|                 mViewTapListener = null; | 
|                 // Finally, clear ImageView | 
|                 mImageView = null; | 
|             } | 
|         } | 
|     } | 
|   | 
|     @Override | 
|     public final RectF getDisplayRect() { | 
|         checkMatrixBounds(); | 
|         return getDisplayRect(getDisplayMatrix()); | 
|     } | 
|   | 
|     public final ImageView getImageView() { | 
|         ImageView imageView = null; | 
|   | 
|         if (null != mImageView) { | 
|             imageView = mImageView.get(); | 
|         } | 
|   | 
|         // If we don't have an ImageView, call cleanup() | 
|         if (null == imageView) { | 
|             cleanup(); | 
|             throw new IllegalStateException( | 
|                     "ImageView no longer exists. You should not use this PhotoViewAttacher any more."); | 
|         } | 
|   | 
|         return imageView; | 
|     } | 
|   | 
|     @Override | 
|     public float getMinScale() { | 
|         return mMinScale; | 
|     } | 
|   | 
|     @Override | 
|     public float getMidScale() { | 
|         return mMidScale; | 
|     } | 
|   | 
|     @Override | 
|     public float getMaxScale() { | 
|         return mMaxScale; | 
|     } | 
|   | 
|     @Override | 
|     public final float getScale() { | 
|         return getValue(mSuppMatrix, Matrix.MSCALE_X); | 
|     } | 
|   | 
|     @Override | 
|     public final ScaleType getScaleType() { | 
|         return mScaleType; | 
|     } | 
|   | 
|     public final boolean onDoubleTap(MotionEvent ev) { | 
|         try { | 
|             float scale = getScale(); | 
|             float x = ev.getX(); | 
|             float y = ev.getY(); | 
|   | 
|             if (scale < mMidScale) { | 
|                 zoomTo(mMidScale, x, y); | 
|             } else if (scale >= mMidScale && scale < mMaxScale) { | 
|                 zoomTo(mMaxScale, x, y); | 
|             } else { | 
|                 zoomTo(mMinScale, x, y); | 
|             } | 
|         } catch (ArrayIndexOutOfBoundsException e) { | 
|             // Can sometimes happen when getX() and getY() is called | 
|         } | 
|   | 
|         return true; | 
|     } | 
|   | 
|     public final boolean onDoubleTapEvent(MotionEvent e) { | 
|         // Wait for the confirmed onDoubleTap() instead | 
|         return false; | 
|     } | 
|   | 
|     public final void onDrag(float dx, float dy) { | 
| //        if (true) { | 
| //            Log.d("mylog", String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy)); | 
| //        } | 
|   | 
|         ImageView imageView = getImageView(); | 
|   | 
|         if (null != imageView && hasDrawable(imageView)) { | 
|             mSuppMatrix.postTranslate(dx, dy); | 
|             checkAndDisplayMatrix(); | 
|   | 
|             /** | 
|              * Here we decide whether to let the ImageView's parent to start | 
|              * taking over the touch event. | 
|              *  | 
|              * 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). | 
|              */ | 
|             if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling()) { | 
|                 if (mScrollEdge == EDGE_BOTH | 
|                         || (mScrollEdge == EDGE_LEFT && dx >= 1f) | 
|                         || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) { | 
|                     imageView.getParent().requestDisallowInterceptTouchEvent( | 
|                             false); | 
|                 } | 
|             } | 
|         } | 
|     } | 
|   | 
|     @Override | 
|     public final void onFling(float startX, float startY, float velocityX, | 
|             float velocityY) { | 
|         if (DEBUG) { | 
|             Log.d(LOG_TAG, "onFling. sX: " + startX + " sY: " + startY | 
|                     + " Vx: " + velocityX + " Vy: " + velocityY); | 
|         } | 
|   | 
|         ImageView imageView = getImageView(); | 
|         if (hasDrawable(imageView)) { | 
|             mCurrentFlingRunnable = new FlingRunnable(imageView.getContext()); | 
|             mCurrentFlingRunnable.fling(imageView.getWidth(), | 
|                     imageView.getHeight(), (int) velocityX, (int) velocityY); | 
|             imageView.post(mCurrentFlingRunnable); | 
|         } | 
|     } | 
|   | 
|     @Override | 
|     public final void onGlobalLayout() { | 
|         ImageView imageView = getImageView(); | 
|   | 
|         if (null != imageView && mZoomEnabled) { | 
|             final int top = imageView.getTop(); | 
|             final int right = imageView.getRight(); | 
|             final int bottom = imageView.getBottom(); | 
|             final int left = imageView.getLeft(); | 
|   | 
|             /** | 
|              * We need to check whether the ImageView's bounds have changed. | 
|              * This would be easier if we targeted API 11+ as we could just use | 
|              * View.OnLayoutChangeListener. Instead we have to replicate the | 
|              * work, keeping track of the ImageView's bounds and then checking | 
|              * if the values change. | 
|              */ | 
|             if (top != mIvTop || bottom != mIvBottom || left != mIvLeft | 
|                     || right != mIvRight) { | 
|                 // Update our base matrix, as the bounds have changed | 
|                 updateBaseMatrix(imageView.getDrawable()); | 
|   | 
|                 // Update values as something has changed | 
|                 mIvTop = top; | 
|                 mIvRight = right; | 
|                 mIvBottom = bottom; | 
|                 mIvLeft = left; | 
|             } | 
|         } | 
|     } | 
|   | 
|     public final void onScale(float scaleFactor, float focusX, float focusY) { | 
|         if (DEBUG) { | 
|             Log.d(LOG_TAG, String.format( | 
|                     "onScale: scale: %.2f. fX: %.2f. fY: %.2f", scaleFactor, | 
|                     focusX, focusY)); | 
|         } | 
|   | 
|         if (hasDrawable(getImageView()) | 
|                 && (getScale() < mMaxScale || scaleFactor < 1f)) { | 
|             mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); | 
|             checkAndDisplayMatrix(); | 
|         } | 
|     } | 
|   | 
|     public final boolean onSingleTapConfirmed(MotionEvent e) { | 
|         ImageView imageView = getImageView(); | 
|   | 
|         if (null != imageView) { | 
|             if (null != mPhotoTapListener) { | 
|                 final RectF displayRect = getDisplayRect(); | 
|   | 
|                 if (null != displayRect) { | 
|                     final float x = e.getX(), y = e.getY(); | 
|   | 
|                     // Check to see if the user tapped on the photo | 
|                     if (displayRect.contains(x, y)) { | 
|   | 
|                         float xResult = (x - displayRect.left) | 
|                                 / displayRect.width(); | 
|                         float yResult = (y - displayRect.top) | 
|                                 / displayRect.height(); | 
|   | 
|                         mPhotoTapListener.onPhotoTap(imageView, xResult, | 
|                                 yResult); | 
|                         return true; | 
|                     } | 
|                 } | 
|             } | 
|             if (null != mViewTapListener) { | 
|                 mViewTapListener.onViewTap(imageView, e.getX(), e.getY()); | 
|             } | 
|         } | 
|   | 
|         return false; | 
|     } | 
|   | 
|     float sx,sy,ex,ey; | 
|     @Override | 
|     public final boolean onTouch(View v, MotionEvent ev) { | 
|         boolean handled = false; | 
|          | 
|         if (mZoomEnabled) { | 
|             switch (ev.getAction()) { | 
|             case MotionEvent.ACTION_DOWN: | 
|                 // First, disable the Parent from intercepting the touch | 
|                 // event | 
|                 v.getParent().requestDisallowInterceptTouchEvent(true); | 
|   | 
|                 // If we're flinging, and the user presses down, cancel | 
|                 // fling | 
|                 cancelFling(); | 
|                  | 
|                 sx = ev.getX(); | 
|                 sy = ev.getY(); | 
|                  | 
|                 break; | 
|   | 
|             case MotionEvent.ACTION_CANCEL: | 
|             case MotionEvent.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; | 
|                     } | 
|                 } | 
|                  | 
|                 ex = ev.getX(); | 
|                 ey = ev.getY(); | 
|                  | 
|                 float dis = ey - sy; | 
|                 float xdis = ex - sx; | 
|                 try { | 
|                     final RectF rect = getDisplayRect(getDisplayMatrix()); | 
|                     if(dis > 100 && rect.width() == getImageView().getWidth()){ | 
|                         if(tou != null){ | 
|                             tou.onTouchEventCallback(TouchEventCallback.DOWN); | 
|                         } | 
|                     }else if(dis < -100 && rect.width() == getImageView().getWidth()){ | 
|                         if(tou != null){ | 
|                             tou.onTouchEventCallback(TouchEventCallback.UP); | 
|                         } | 
|                     }else if(xdis*xdis < 100 && dis*dis < 64) { | 
|                         if(tou != null){ | 
|                             tou.onTouchEventCallback(TouchEventCallback.CLICK); | 
|                         } | 
|                     } | 
|                 }catch (NullPointerException e){ | 
|   | 
|                 }catch (Exception e){ | 
|   | 
|                 } | 
|                 break; | 
|             } | 
|   | 
|             // Check to see if the user double tapped | 
|             if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) { | 
|                 handled = true; | 
|             } | 
|   | 
|             // Finally, try the Scale/Drag detector | 
|             if (null != mScaleDragDetector | 
|                     && mScaleDragDetector.onTouchEvent(ev)) { | 
|                 handled = true; | 
|             } | 
|         } | 
|   | 
|         return handled; | 
|     } | 
|   | 
|     @Override | 
|     public void setAllowParentInterceptOnEdge(boolean allow) { | 
|         mAllowParentInterceptOnEdge = allow; | 
|     } | 
|   | 
|     @Override | 
|     public void setMinScale(float minScale) { | 
|         checkZoomLevels(minScale, mMidScale, mMaxScale); | 
|         mMinScale = minScale; | 
|     } | 
|   | 
|     @Override | 
|     public void setMidScale(float midScale) { | 
|         checkZoomLevels(mMinScale, midScale, mMaxScale); | 
|         mMidScale = midScale; | 
|     } | 
|   | 
|     @Override | 
|     public void setMaxScale(float maxScale) { | 
|         checkZoomLevels(mMinScale, mMidScale, maxScale); | 
|         mMaxScale = maxScale; | 
|     } | 
|   | 
|     @Override | 
|     public final void setOnLongClickListener(OnLongClickListener listener) { | 
|         mLongClickListener = listener; | 
|     } | 
|   | 
|     @Override | 
|     public final void setOnMatrixChangeListener(OnMatrixChangedListener listener) { | 
|         mMatrixChangeListener = listener; | 
|     } | 
|   | 
|     @Override | 
|     public final void setOnPhotoTapListener(OnPhotoTapListener listener) { | 
|         mPhotoTapListener = listener; | 
|     } | 
|   | 
|     @Override | 
|     public final void setOnViewTapListener(OnViewTapListener listener) { | 
|         mViewTapListener = listener; | 
|     } | 
|   | 
|     @Override | 
|     public final void setScaleType(ScaleType scaleType) { | 
|         if (isSupportedScaleType(scaleType) && scaleType != mScaleType) { | 
|             mScaleType = scaleType; | 
|   | 
|             // Finally update | 
|             update(); | 
|         } | 
|     } | 
|   | 
|     @Override | 
|     public final void setZoomable(boolean zoomable) { | 
|         mZoomEnabled = zoomable; | 
|         update(); | 
|     } | 
|   | 
|     public final void update() { | 
|         ImageView imageView = getImageView(); | 
|   | 
|         if (null != imageView) { | 
|             if (mZoomEnabled) { | 
|                 // Make sure we using MATRIX Scale Type | 
|                 setImageViewScaleTypeMatrix(imageView); | 
|   | 
|                 // Update the base matrix using the current drawable | 
|                 updateBaseMatrix(imageView.getDrawable()); | 
|             } else { | 
|                 // Reset the Matrix... | 
|                 resetMatrix(); | 
|             } | 
|         } | 
|     } | 
|   | 
|     @Override | 
|     public final void zoomTo(float scale, float focalX, float focalY) { | 
|         ImageView imageView = getImageView(); | 
|   | 
|         if (null != imageView) { | 
|             imageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, | 
|                     focalY)); | 
|         } | 
|     } | 
|   | 
|     protected Matrix getDisplayMatrix() { | 
|         mDrawMatrix.set(mBaseMatrix); | 
|         mDrawMatrix.postConcat(mSuppMatrix); | 
|         return mDrawMatrix; | 
|     } | 
|   | 
|     private void cancelFling() { | 
|         if (null != mCurrentFlingRunnable) { | 
|             mCurrentFlingRunnable.cancelFling(); | 
|             mCurrentFlingRunnable = null; | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * Helper method that simply checks the Matrix, and then displays the result | 
|      */ | 
|     private void checkAndDisplayMatrix() { | 
|         checkMatrixBounds(); | 
|         setImageViewMatrix(getDisplayMatrix()); | 
|     } | 
|   | 
|     private void checkImageViewScaleType() { | 
|         ImageView imageView = getImageView(); | 
|   | 
|         /** | 
|          * PhotoView's getScaleType() will just divert to this.getScaleType() so | 
|          * only call if we're not attached to a PhotoView. | 
|          */ | 
|         if (null != imageView && !(imageView instanceof PhotoView)) { | 
|             if (imageView.getScaleType() != ScaleType.MATRIX) { | 
|                 throw new IllegalStateException( | 
|                         "The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher"); | 
|             } | 
|         } | 
|     } | 
|   | 
|     private void checkMatrixBounds() { | 
|         final ImageView imageView = getImageView(); | 
|         if (null == imageView) { | 
|             return; | 
|         } | 
|   | 
|         final RectF rect = getDisplayRect(getDisplayMatrix()); | 
|         if (null == rect) { | 
|             return; | 
|         } | 
|   | 
|         final float height = rect.height(), width = rect.width(); | 
|         float deltaX = 0, deltaY = 0; | 
|   | 
|         final int viewHeight = imageView.getHeight(); | 
|         if (height <= viewHeight) { | 
|             switch (mScaleType) { | 
|             case FIT_START: | 
|                 deltaY = -rect.top; | 
|                 break; | 
|             case FIT_END: | 
|                 deltaY = viewHeight - height - rect.top; | 
|                 break; | 
|             default: | 
|                 deltaY = (viewHeight - height) / 2 - rect.top; | 
|                 break; | 
|             } | 
|         } else if (rect.top > 0) { | 
|             deltaY = -rect.top; | 
|         } else if (rect.bottom < viewHeight) { | 
|             deltaY = viewHeight - rect.bottom; | 
|         } | 
|   | 
|         final int viewWidth = imageView.getWidth(); | 
|         if (width <= viewWidth) { | 
|             switch (mScaleType) { | 
|             case FIT_START: | 
|                 deltaX = -rect.left; | 
|                 break; | 
|             case FIT_END: | 
|                 deltaX = viewWidth - width - rect.left; | 
|                 break; | 
|             default: | 
|                 deltaX = (viewWidth - width) / 2 - rect.left; | 
|                 break; | 
|             } | 
|             mScrollEdge = EDGE_BOTH; | 
|         } else if (rect.left > 0) { | 
|             mScrollEdge = EDGE_LEFT; | 
|             deltaX = -rect.left; | 
|         } else if (rect.right < viewWidth) { | 
|             deltaX = viewWidth - rect.right; | 
|             mScrollEdge = EDGE_RIGHT; | 
|         } else { | 
|             mScrollEdge = EDGE_NONE; | 
|         } | 
|   | 
|         // Finally actually translate the matrix | 
|         mSuppMatrix.postTranslate(deltaX, deltaY); | 
|     } | 
|   | 
|     /** | 
|      * Helper method that maps the supplied Matrix to the current Drawable | 
|      *  | 
|      * @param matrix | 
|      *            - Matrix to map Drawable against | 
|      * @return RectF - Displayed Rectangle | 
|      */ | 
|     private RectF getDisplayRect(Matrix matrix) { | 
|         ImageView imageView = getImageView(); | 
|   | 
|         if (null != imageView) { | 
|             Drawable d = imageView.getDrawable(); | 
|             if (null != d) { | 
|                 mDisplayRect.set(0, 0, d.getIntrinsicWidth(), | 
|                         d.getIntrinsicHeight()); | 
|                 matrix.mapRect(mDisplayRect); | 
|                 return mDisplayRect; | 
|             } | 
|         } | 
|         return null; | 
|     } | 
|   | 
|     /** | 
|      * Helper method that 'unpacks' a Matrix and returns the required value | 
|      *  | 
|      * @param matrix | 
|      *            - Matrix to unpack | 
|      * @param whichValue | 
|      *            - Which value from Matrix.M* to return | 
|      * @return float - returned value | 
|      */ | 
|     private float getValue(Matrix matrix, int whichValue) { | 
|         matrix.getValues(mMatrixValues); | 
|         return mMatrixValues[whichValue]; | 
|     } | 
|   | 
|     /** | 
|      * Resets the Matrix back to FIT_CENTER, and then displays it.s | 
|      */ | 
|     private void resetMatrix() { | 
|         mSuppMatrix.reset(); | 
|         setImageViewMatrix(getDisplayMatrix()); | 
|         checkMatrixBounds(); | 
|     } | 
|   | 
|     private void setImageViewMatrix(Matrix matrix) { | 
|         ImageView imageView = getImageView(); | 
|         if (null != imageView) { | 
|   | 
|             checkImageViewScaleType(); | 
|             imageView.setImageMatrix(matrix); | 
|   | 
|             // Call MatrixChangedListener if needed | 
|             if (null != mMatrixChangeListener) { | 
|                 RectF displayRect = getDisplayRect(matrix); | 
|                 if (null != displayRect) { | 
|                     mMatrixChangeListener.onMatrixChanged(displayRect); | 
|                 } | 
|             } | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * Calculate Matrix for FIT_CENTER | 
|      *  | 
|      * @param d | 
|      *            - Drawable being displayed | 
|      */ | 
|     private void updateBaseMatrix(Drawable d) { | 
|         ImageView imageView = getImageView(); | 
|         if (null == imageView || null == d) { | 
|             return; | 
|         } | 
|   | 
|         final float viewWidth = imageView.getWidth(); | 
|         final float viewHeight = imageView.getHeight(); | 
|         final int drawableWidth = d.getIntrinsicWidth(); | 
|         final int drawableHeight = d.getIntrinsicHeight(); | 
|   | 
|         mBaseMatrix.reset(); | 
|   | 
|         final float widthScale = viewWidth / drawableWidth; | 
|         final float heightScale = viewHeight / drawableHeight; | 
|   | 
|         if (mScaleType == ScaleType.CENTER) { | 
|             mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, | 
|                     (viewHeight - drawableHeight) / 2F); | 
|   | 
|         } else if (mScaleType == ScaleType.CENTER_CROP) { | 
|             float scale = Math.max(widthScale, heightScale); | 
|             mBaseMatrix.postScale(scale, scale); | 
|             mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, | 
|                     (viewHeight - drawableHeight * scale) / 2F); | 
|   | 
|         } else if (mScaleType == ScaleType.CENTER_INSIDE) { | 
|             float scale = Math.min(1.0f, Math.min(widthScale, heightScale)); | 
|             mBaseMatrix.postScale(scale, scale); | 
|             mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, | 
|                     (viewHeight - drawableHeight * scale) / 2F); | 
|   | 
|         } else { | 
|             RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight); | 
|             RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight); | 
|   | 
|             switch (mScaleType) { | 
|             case FIT_CENTER: | 
|                 mBaseMatrix | 
|                         .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER); | 
|                 break; | 
|   | 
|             case FIT_START: | 
|                 mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START); | 
|                 break; | 
|   | 
|             case FIT_END: | 
|                 mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END); | 
|                 break; | 
|   | 
|             case FIT_XY: | 
|                 mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL); | 
|                 break; | 
|   | 
|             default: | 
|                 break; | 
|             } | 
|         } | 
|   | 
|         resetMatrix(); | 
|     } | 
|   | 
|     /** | 
|      * Interface definition for a callback to be invoked when the internal | 
|      * Matrix has changed for this View. | 
|      *  | 
|      * @author Chris Banes | 
|      */ | 
|     public static interface OnMatrixChangedListener { | 
|         /** | 
|          * Callback for when the Matrix displaying the Drawable has changed. | 
|          * This could be because the View's bounds have changed, or the user has | 
|          * zoomed. | 
|          *  | 
|          * @param rect | 
|          *            - Rectangle displaying the Drawable's new bounds. | 
|          */ | 
|         void onMatrixChanged(RectF rect); | 
|     } | 
|   | 
|     /** | 
|      * Interface definition for a callback to be invoked when the Photo is | 
|      * tapped with a single tap. | 
|      *  | 
|      * @author Chris Banes | 
|      */ | 
|     public static interface OnPhotoTapListener { | 
|   | 
|         /** | 
|          * A callback to receive where the user taps on a photo. You will only | 
|          * receive a callback if the user taps on the actual photo, tapping on | 
|          * 'whitespace' will be ignored. | 
|          *  | 
|          * @param view | 
|          *            - View the user tapped. | 
|          * @param x | 
|          *            - where the user tapped from the of the Drawable, as | 
|          *            percentage of the Drawable width. | 
|          * @param y | 
|          *            - where the user tapped from the top of the Drawable, as | 
|          *            percentage of the Drawable height. | 
|          */ | 
|         void onPhotoTap(View view, float x, float y); | 
|     } | 
|   | 
|     /** | 
|      * Interface definition for a callback to be invoked when the ImageView is | 
|      * tapped with a single tap. | 
|      *  | 
|      * @author Chris Banes | 
|      */ | 
|     public static interface OnViewTapListener { | 
|   | 
|         /** | 
|          * A callback to receive where the user taps on a ImageView. You will | 
|          * receive a callback if the user taps anywhere on the view, tapping on | 
|          * 'whitespace' will not be ignored. | 
|          *  | 
|          * @param view | 
|          *            - View the user tapped. | 
|          * @param x | 
|          *            - where the user tapped from the left of the View. | 
|          * @param y | 
|          *            - where the user tapped from the top of the View. | 
|          */ | 
|         void onViewTap(View view, float x, float y); | 
|     } | 
|   | 
|     private class AnimatedZoomRunnable implements Runnable { | 
|   | 
|         // These are 'postScale' values, means they're compounded each iteration | 
|         static final float ANIMATION_SCALE_PER_ITERATION_IN = 1.07f; | 
|         static final float ANIMATION_SCALE_PER_ITERATION_OUT = 0.93f; | 
|   | 
|         private final float mFocalX, mFocalY; | 
|         private final float mTargetZoom; | 
|         private final float mDeltaScale; | 
|   | 
|         public AnimatedZoomRunnable(final float currentZoom, | 
|                 final float targetZoom, final float focalX, final float focalY) { | 
|             mTargetZoom = targetZoom; | 
|             mFocalX = focalX; | 
|             mFocalY = focalY; | 
|   | 
|             if (currentZoom < targetZoom) { | 
|                 mDeltaScale = ANIMATION_SCALE_PER_ITERATION_IN; | 
|             } else { | 
|                 mDeltaScale = ANIMATION_SCALE_PER_ITERATION_OUT; | 
|             } | 
|         } | 
|   | 
|         public void run() { | 
|             ImageView imageView = getImageView(); | 
|   | 
|             if (null != imageView) { | 
|                 mSuppMatrix.postScale(mDeltaScale, mDeltaScale, mFocalX, | 
|                         mFocalY); | 
|                 checkAndDisplayMatrix(); | 
|   | 
|                 final float currentScale = getScale(); | 
|   | 
|                 if ((mDeltaScale > 1f && currentScale < mTargetZoom) | 
|                         || (mDeltaScale < 1f && mTargetZoom < currentScale)) { | 
|                     // We haven't hit our target scale yet, so post ourselves | 
|                     // again | 
|                     Compat.postOnAnimation(imageView, this); | 
|   | 
|                 } else { | 
|                     // We've scaled past our target zoom, so calculate the | 
|                     // necessary scale so we're back at target zoom | 
|                     final float delta = mTargetZoom / currentScale; | 
|                     mSuppMatrix.postScale(delta, delta, mFocalX, mFocalY); | 
|                     checkAndDisplayMatrix(); | 
|                 } | 
|             } | 
|         } | 
|     } | 
|   | 
|     private class FlingRunnable implements Runnable { | 
|   | 
|         private final ScrollerProxy mScroller; | 
|         private int mCurrentX, mCurrentY; | 
|   | 
|         public FlingRunnable(Context context) { | 
|             mScroller = ScrollerProxy.getScroller(context); | 
|         } | 
|   | 
|         public void cancelFling() { | 
|             if (DEBUG) { | 
|                 Log.d(LOG_TAG, "Cancel Fling"); | 
|             } | 
|             mScroller.forceFinished(true); | 
|         } | 
|   | 
|         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) { | 
|                 Log.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 run() { | 
|             ImageView imageView = getImageView(); | 
|             if (null != imageView && mScroller.computeScrollOffset()) { | 
|   | 
|                 final int newX = mScroller.getCurrX(); | 
|                 final int newY = mScroller.getCurrY(); | 
|   | 
|                 if (DEBUG) { | 
|                     Log.d(LOG_TAG, "fling run(). CurrentX:" + mCurrentX | 
|                             + " CurrentY:" + mCurrentY + " NewX:" + newX | 
|                             + " NewY:" + newY); | 
|                 } | 
|   | 
|                 mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY); | 
|                 setImageViewMatrix(getDisplayMatrix()); | 
|   | 
|                 mCurrentX = newX; | 
|                 mCurrentY = newY; | 
|   | 
|                 // Post On animation | 
|                 Compat.postOnAnimation(imageView, this); | 
|             } | 
|         } | 
|     } | 
|      | 
|     //�Զ��廬���¼��ص��ӿڡ� | 
|     public interface TouchEventCallback { | 
|         public static final byte DOWN = 1; | 
|         public static final byte UP = 2; | 
|         public static final byte CLICK = 3; | 
|          | 
|         void onTouchEventCallback(byte mode); | 
|     } | 
|     public static TouchEventCallback tou; | 
|     public static void setTouchCallback(TouchEventCallback tou){ | 
|         PhotoViewAttacher.tou = tou; | 
|     } | 
|      | 
| } |