https://github.com/chrisbanes/PhotoView/tree/master/library

这个就是项目地址,相信很多人都用过,我依然不去讲怎么使用。只讲他的原理和具体实现。

具体会讲到:

1.如何实现pinch手势 放大缩小图片。

2.如何实现的拖动图片。

3.如何实现的惯性拖动。

4.如何控制与父view的 事件监听

主要就是这三点。

具体的调用方法 主要是下面这样:

   ImageView mImageView = (ImageView) findViewById(R.id.iv_photo);
         mCurrMatrixTv = (TextView) findViewById(R.id.tv_current_matrix);

         Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper);
         mImageView.setImageDrawable(bitmap);

         //将imageview和PhotoViewAttacher 这个控制器关联起来
         mAttacher = new PhotoViewAttacher(mImageView);

可以看出来 主要的工作都是在这个PhotoViewAttacher里做的。

我们来跟着他的构造函数 看

  //默认构造函数是可以被放大缩小的 zoomable 为true
     public PhotoViewAttacher(ImageView imageView) {
         this(imageView, true);
     }

     public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
         //这个是防止内存泄露的一个技巧,注意你在activity里的imageview 对象如果把引用传进来的话
         //那这里的mImageView 也是指向外层的ImageView那个对象,那么就相当于有2个引用指向同一块地址!
         //当你外层的imageview对象被销毁的时候,因为这里还有一个引用指向那个对象,所以实际上对象不会被销毁
         //只是最外层的那个imageview对象的引用成了null而已,但是如果你在这里用了弱引用的话,当外层的强引用
         //为NUll的话 imageview对象会立刻被销毁掉。
         mImageView = new WeakReference<>(imageView);

         //這個draswingcache 我們做截屏的時候會經常用到,只需要理解成我們可以通過getDrawingCache拿到view里的內容(這個內容被轉成了bitmap)
         imageView.setDrawingCacheEnabled(true);
         imageView.setOnTouchListener(this);

         //这里就是监听imageview的 layout变化用的 imageview发生变化就会调用这个回调接口
         ViewTreeObserver observer = imageView.getViewTreeObserver();
         if (null != observer)
             observer.addOnGlobalLayoutListener(this);

         // 设置绘制时这个imageview 可以随着matrix矩阵进行变换
         setImageViewScaleTypeMatrix(imageView);
         //这个是让你在可视化界面能看到预览效果的,大家自定义控件时 也可以使用这个技巧
         if (imageView.isInEditMode()) {
             return ;
         }
         // Create Gesture Detectors...
         //根据版本不同 取得需要的mScaleDragDetector 主要就是监听pinch手势的
         mScaleDragDetector = VersionedGestureDetector.newInstance(
                 imageView.getContext(), this);

         //这个dectecor 就是用来监听双击和长按事件的
         mGestureDetector = new GestureDetector(imageView.getContext(),
                 new GestureDetector.SimpleOnGestureListener() {

                     // forward long click listener
                     @Override
                     public void onLongPress(MotionEvent e) {
                         if (null != mLongClickListener) {
                             mLongClickListener.onLongClick(getImageView());
                         }
                     }
                 });
         //监听双击手势的
         mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));

         // Finally, update the UI so that we're zoomable
         setZoomable(zoomable);

我们先看19-21行 这个里面加了一个监听layout变化的一个回调,我们来看看这个回调做了什么:

  @Override
     public void onGlobalLayout() {
         ImageView imageView = getImageView();

         if (null != imageView) {
             if (mZoomEnabled) {
                 //这个地方要注意imageview的 四个坐标点是永远不会变化的。
                 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;
                 }
             } else {
                 updateBaseMatrix(imageView.getDrawable());
             }
         }
     }

然后跟进去发现是这个函数:

 /**
      * 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 = getImageViewWidth(imageView);
         final float viewHeight = getImageViewHeight(imageView);
         //这个是取原始图片大小的 永远不会变化的
         final int drawableWidth = d.getIntrinsicWidth();
         final int drawableHeight = d.getIntrinsicHeight();

         mBaseMatrix.reset();

         final float widthScale = viewWidth / drawableWidth;
         final float heightScale = viewHeight / drawableHeight;

         //根据传进去的scaletype的值来确定 基础的matrix大小
         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();
     }

其实这边代码很容易懂,大家都知道 imageview里面是一个图片对吧,你这个图片有大有小,那你是怎么放置在imageview里面就有讲究了,你在imageview里是可以设置的他的scaletype的属性的,

那当然了你设置完毕以后 肯定相对于原图片来说 你已经做了一次matrix变换了,所以你要记录这次matrix的值。这里你只要记住对于一个imageview 来说 他本身容器的大小是固定的,

容器里面的drawable的原图大小也是固定的,但是现实效果是通过matrix来控制的,所以我们要记录每一次图片发生变化的时候matrix的值,这是很关键的。

然后回到我们的构造函数看31-32行。这里构造了一个

uk.co.senab.photoview.gestures.GestureDetector mScaleDragDetector

我们也继续看看这个是如何构造出来的
 public final class VersionedGestureDetector {

     public static GestureDetector newInstance(Context context,
                                               OnGestureListener listener) {
         final int sdkVersion = Build.VERSION.SDK_INT;
         GestureDetector detector;
         if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
             detector = new CupcakeGestureDetector(context);
         } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
             detector = new EclairGestureDetector(context);
         } else {
             //现在大部分 都是调用的这个
             detector = new FroyoGestureDetector(context);
         }

         detector.setOnGestureListener(listener);

         return detector;
     }

 }

我们发现这个地方是一个单例,实际上这边代码就是根据sdk的版本号不同 提供不一样的功能,比如说pinch手势 在4.0以下就没有支持 那当然了,我们现在百分之95以上的机器都是4.0以上的,

我们只要分析12-13行就可以。

 /*******************************************************************************
  * Copyright 2011, 2012 Chris Banes.
  * <p>
  * 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
  * <p>
  * http://www.apache.org/licenses/LICENSE-2.0
  * <p>
  * 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 uk.co.senab.photoview.gestures;

 import android.annotation.TargetApi;
 import android.content.Context;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;

 @TargetApi(8)
 //能看出来 低于这个版本的 都不支持pinch 放大缩小功能
 public class FroyoGestureDetector extends EclairGestureDetector {

     //用于检测缩放的手势
     protected final ScaleGestureDetector mDetector;

     public FroyoGestureDetector(Context context) {
         super(context);

         ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {

             @Override
             public boolean onScale(ScaleGestureDetector detector) {
                 float scaleFactor = detector.getScaleFactor();

                 if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
                     return false;

                 mListener.onScale(scaleFactor,
                         detector.getFocusX(), detector.getFocusY());
                 return true;
             }

             //这个函数返回true的时候 onScale函数才会真正调用
             @Override
             public boolean onScaleBegin(ScaleGestureDetector detector) {
                 return true;
             }

             @Override
             public void onScaleEnd(ScaleGestureDetector detector) {
                 // NO-OP
             }
         };
         mDetector = new ScaleGestureDetector(context, mScaleListener);
     }

     @Override
     public boolean isScaling() {
         return mDetector.isInProgress();
     }

     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         mDetector.onTouchEvent(ev);
         return super.onTouchEvent(ev);
     }

 }

33行-58行 才是真正系统提供给我们的监听pinch手势的地方。就是我们印象中 两指放大缩小图片的那个手势的监听。看42-43行

这里发现使用了一个回调,回过头去看我们的构造函数 我们就知道这个回调 实际上就是在控制器里他自己实现的。

 public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener,
         OnGestureListener,
         ViewTreeObserver.OnGlobalLayoutListener {

所以我们就到控制器里去找这个手势监听的实现代码:

 //这个就是处理pinch 手势的,放大 缩小图片的处理函数
     @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) {
             //这个回调接口 你可以不用set的,通常来说 我们很少实现这个接口,有需要的可以自己去看注释这接口的意思
             if (null != mScaleChangeListener) {
                 mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
             }
             //这个地方要注意 这个放大 并不是固定的以图片中心放大的。他是以你两个手指做pinch手势的
             //的时候 取点来放大的,这么做的一个好的地方是 你可以放大图片中某一部分,而不是只能从
             //从图片中间部分开始放大缩小。但是你可以想一下,这么做的弊端就是 很容易因为你的放大缩小
             //因为点不在中间,所以图片很有可能就不在imageview这个控件的中间,会让imageview边缘或者其他地方
             //有留白
             mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
             //而这个函数就是解决上述弊端的,
             checkAndDisplayMatrix();
         }
     }
  /**
      * Helper method that simply checks the Matrix, and then displays the result
      */
     private void checkAndDisplayMatrix() {
         //实际上在matrix里就解决了上述的弊端
         if (checkMatrixBounds()) {
             setImageViewMatrix(getDrawMatrix());
         }
     }
  //检查当前显示范围是否在边界上  然後對圖片進行平移(垂直或水平方向) 防止出現留白的現象
     private boolean checkMatrixBounds() {
         final ImageView imageView = getImageView();
         if (null == imageView) {
             return false;
         }

         final RectF rect = getDisplayRect(getDrawMatrix());
         if (null == rect) {
             return false;
         }

         final float height = rect.height(), width = rect.width();
         float deltaX = 0, deltaY = 0;

         final int viewHeight = getImageViewHeight(imageView);
         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 = getImageViewWidth(imageView);
         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);
         return true;
     }

这个地方有的人可能会对最后那个检测是否在边界的那个函数不太明白,其实还是挺好理解的,对于容器imageview来说 他的范围是固定的。里面的drawable是不断的变化的,

但是这个drawable 可以和 RectF来关联起来,这个rectF 就是描述出一个矩形,这个矩形就恰好是drawable的大小范围。他有四个值 分别是top left right和bottom。

其中2个值表示矩形的左上面ed点的坐标 另外2个表示右下角的坐标。一个矩形由这2个点即可确定位置以及大小。我用下图来表示:

所以那个函数你要想理解的话 就是自己去画个图。就能知道如何判断是否到边缘了!实际上就是drawbl---matrix---rectF的一个转换。另外一定要自己画图 才能真正理解 里面的逻辑 很简单 并不难!

那这个onscale 手势我们过了一遍以后 看看这个函数是怎么被调用的很简单 还是通过onTouch事件
  @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) {
                         //阻止父层的View截获touch事件
                         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:
                     //放大缩小都得有一个度,这个地方就是说 如果你缩的太小了,比如我们定义的是缩小到原图的百分之25
                     //如果你缩小到百分之10了,那么当你手指松开的时候 就要自动将图片还原到百分之25,当然这个过程
                     //你得使用动画慢慢从10回复到25,因为一下子回复到25 实在是太难看了
                     //在下一帧绘制前,系统会执行该 Runnable,这样我们就可以在 runnable 中更新 UI 状态.
                     //原理上类似一个递归调用,每次 UI 绘制前更新 UI 状态,并指定下次 UI 更新前再执行自己.
                     //这种写法 与 使用循环或 Handler 每隔 16ms 刷新一次 UI 基本等价,但是更为方便快捷
                     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();
                 //这行代码是最终交给pinch手势监听的代码
                 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;
     }

好 到这个地方 相信大家对 pinch事件就理解的差不多了 包括是怎么被调用的这个过程 以及中间的缺陷处理 都明白了。我们继续看drag 也就是拖动是怎么处理的。

 /能看出来 低于这个版本的 都不支持pinch 放大缩小功能
 public class FroyoGestureDetector extends EclairGestureDetector {

看的到 他是继承自这个类的

 @TargetApi(5)
 public class EclairGestureDetector extends CupcakeGestureDetector {
 /*******************************************************************************
  * Copyright 2011, 2012 Chris Banes.
  * <p>
  * 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
  * <p>
  * http://www.apache.org/licenses/LICENSE-2.0
  * <p>
  * 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 uk.co.senab.photoview.gestures;

 import android.content.Context;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;

 import uk.co.senab.photoview.log.LogManager;

 public class CupcakeGestureDetector implements GestureDetector {

     //惯性滑动拖动处理
     protected OnGestureListener mListener;
     private static final String LOG_TAG = "CupcakeGestureDetector";
     float mLastTouchX;
     float mLastTouchY;
     final float mTouchSlop;
     final float mMinimumVelocity;

     @Override
     public void setOnGestureListener(OnGestureListener listener) {
         this.mListener = listener;
     }

     public CupcakeGestureDetector(Context context) {
         final ViewConfiguration configuration = ViewConfiguration
                 .get(context);
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mTouchSlop = configuration.getScaledTouchSlop();
     }

     private VelocityTracker mVelocityTracker;
     private boolean mIsDragging;

     float getActiveX(MotionEvent ev) {
         return ev.getX();
     }

     float getActiveY(MotionEvent ev) {
         return ev.getY();
     }

     public boolean isScaling() {
         return false;
     }

     public boolean isDragging() {
         return mIsDragging;
     }

     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
                 mVelocityTracker = VelocityTracker.obtain();
                 if (null != mVelocityTracker) {
                     mVelocityTracker.addMovement(ev);
                 } else {
                     LogManager.getLogger().i(LOG_TAG, "Velocity tracker is null");
                 }

                 mLastTouchX = getActiveX(ev);
                 mLastTouchY = getActiveY(ev);
                 mIsDragging = false;
                 break;
             }

             case MotionEvent.ACTION_MOVE: {
                 //这个拖动事件其实也很好理解,就是确定你的手指在滑动的时候坐标点的变化
                 //这个变化要理解好 就是你可以把屏幕左上角的点 想象成一个坐标系的原点。
                 //那你如果要计算某个坐标点和这个原点的直线距离 实际上就是 这个坐标点的
                 // x*x+y*y 然后把这个值开根号即可,初中数学问题!只要你每次这个直线距离
                 //有变化 那就肯定是拖动事件了
                 final float x = getActiveX(ev);
                 final float y = getActiveY(ev);
                 final float dx = x - mLastTouchX, dy = y - mLastTouchY;

                 if (!mIsDragging) {
                     // Use Pythagoras to see if drag length is larger than
                     // touch slop
                     mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
                 }

                 if (mIsDragging) {
                     //同样的在确定要滑动的时候,也是通过回调来实现的
                     mListener.onDrag(dx, dy);
                     mLastTouchX = x;
                     mLastTouchY = y;

                     if (null != mVelocityTracker) {
                         mVelocityTracker.addMovement(ev);
                     }
                 }
                 break;
             }

             case MotionEvent.ACTION_CANCEL: {
                 // Recycle Velocity Tracker
                 if (null != mVelocityTracker) {
                     mVelocityTracker.recycle();
                     mVelocityTracker = null;
                 }
                 break;
             }

             case MotionEvent.ACTION_UP: {
                 if (mIsDragging) {
                     if (null != mVelocityTracker) {
                         mLastTouchX = getActiveX(ev);
                         mLastTouchY = getActiveY(ev);

                         // Compute velocity within the last 1000ms
                         mVelocityTracker.addMovement(ev);
                         //每秒移动多少个像素点
                         mVelocityTracker.computeCurrentVelocity(1000);

                         //算移动速率的
                         final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
                                 .getYVelocity();

                         // If the velocity is greater than minVelocity, call
                         // listener
                         if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
                             //回调实现惯性
                             mListener.onFling(mLastTouchX, mLastTouchY, -vX,
                                     -vY);
                         }
                     }
                 }

                 // Recycle Velocity Tracker
                 if (null != mVelocityTracker) {
                     mVelocityTracker.recycle();
                     mVelocityTracker = null;
                 }
                 break;
             }
         }

         return true;
     }
 }

应该都能明白,我们看看那个接口到底是啥

 /*******************************************************************************
  * 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 uk.co.senab.photoview.gestures;

 public interface OnGestureListener {

     public void onDrag(float dx, float dy);

     public void onFling(float startX, float startY, float velocityX,
                         float velocityY);

     public void onScale(float scaleFactor, float focusX, float focusY);

 }

最后 再看看控制器里这个接口是怎么实现onDrag的,onFling就不分析了 差不多其实

  @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();

         //这个地方就是做了一个巧妙的判断,他要实现的功能就是:
         //如果你拖拽到了边缘,还继续拖拽的话 那就交给父view来处理,如果没有到边缘 那我们就继续自己处理 继续拖拽图片了
         //想象一下我们把photoview放到viewpager的时候 就是这样处理的
         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);
             }
         }
     }
到这我们的这篇博客就基本结束了,其实这个开源photoview真的挺值得大家好好分析的,如果分析的好,你对android里面 各种手势监听 matrix rectF等等应该都能了如指掌了,以后遇到类似的问题 应该不会没有头绪了!

Android 开源项目PhotoView源码分析的更多相关文章

  1. Android 网络流量监听开源项目-ConnectionClass源码分析

    很多App要做到极致的话,对网络状态的监听是很有必要的,比如在网络差的时候加载质量一般的小图,缩略图,在网络好的时候,加载高清大图,脸书的android 客户端就是这么做的, 当然伟大的脸书也把这部分 ...

  2. 10个经典的Android开源项目(附源码包)

    最近在抽空学习Android系统开发,对Android学习也比较感兴趣,刚开始学就试着在网上找几个项目源码研究看下,以下就将找到的Android项目源码列出,希望对正在或准备学习Android系统开发 ...

  3. go开源项目influxdb-relay源码分析(一)

    influxdb-relay项目地址: https://github.com/influxdata/influxdb-relay,主要作为负载均衡节点,写入多个influxdb节点,起到高可用效果. ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  5. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  7. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  8. Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

  9. android高级----&gt;AsyncTask的源码分析

    在Android中实现异步任务机制有两种方式,Handler和AsyncTask,它在子线程更新UI的例子可以参见我的博客(android基础---->子线程更新UI).今天我们通过一个小的案例 ...

随机推荐

  1. iOS 自定义方法 - UIView扩展

    示例代码 //#import <UIKit/UIKit.h>@interface UIView (LPCView)/** 上 */@property CGFloat top;/** 下 * ...

  2. JM8.6学习

    1. vs2010 设置参数 编译运行JM8.6 (参考http://bbs.chinavideo.org/forum.php?mod=viewthread&tid=15695&hig ...

  3. /usr/include/gnu/stubs.h:7:27: error: gnu/stubs-32.h:No such file or directory的解决办法

    在编译32位HDecode时出现如题所示的错误,原因时没有安装32位glibc库导致的: ubuntu: sudo apt-get install libc6-dev-i386 CentOS:yum ...

  4. Topology的构建

    public class BlackListBolt extends BaseRichBolt{ private static Logger logger = Logger.getLogger(Bla ...

  5. css内边距与外边距的区别

    你真的了解margin吗?你知道margin有什么特性吗?你知道什么是垂直外边距合并?margin在块元素.内联元素中的区别?什么时候该用 padding而不是margin?你知道负margin吗?你 ...

  6. Ambiguous mapping found. Cannot map &#39;xxxxController&#39; bean method

    1.背景 今天要做一个demo,从github上clone一个springmvc mybatis的工程(https://github.com/komamitsu/Spring-MVC-sample-u ...

  7. 斐波那契数列_java版本

    package 斐波那契数列; public class fbnq { public static void main(String[] args){ System.out.println(fibon ...

  8. Spring各种注解标签作用详解

    @Autowired和@Resource等注解是将Spring容器中的bean注入到属性,而@Component等注解是将bean放入Spring容器中管理. @Autowired spring2.1 ...

  9. jQuery dataTables 列不对齐的原因

    如果把 jQuery dataTables 用在初始化时为隐藏的区域中,会发现表头和内容的列是不对齐的. 解决方案: 如果是折叠的,可以加上: $('#myCollapsible').on('show ...

  10. DK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME

    根据提示,我们可以新建一个项目或者以前自己使用过没问题的工程,从中把local.properties文件copy到我们从github中想要导入的工程中,我自己就是这样的,然后这个问题就解决了. ndk ...