Android 開源專案PhotoView原始碼分析

希爾瓦娜斯女神發表於2015-09-14

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

這個就是專案地址,相信很多人都用過,我依然不去講怎麼使用。只講他的原理和具體實現。

具體會講到:

1.如何實現pinch手勢 放大縮小圖片。

2.如何實現的拖動圖片。

3.如何實現的慣性拖動。

4.如何控制與父view的 事件監聽

主要就是這三點。

具體的呼叫方法 主要是下面這樣:

1   ImageView mImageView = (ImageView) findViewById(R.id.iv_photo);
2         mCurrMatrixTv = (TextView) findViewById(R.id.tv_current_matrix);
3 
4         Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper);
5         mImageView.setImageDrawable(bitmap);
6 
7         //將imageview和PhotoViewAttacher 這個控制器關聯起來
8         mAttacher = new PhotoViewAttacher(mImageView);

可以看出來 主要的工作都是在這個PhotoViewAttacher裡做的。

我們來跟著他的建構函式 看

 1  //預設建構函式是可以被放大縮小的 zoomable 為true
 2     public PhotoViewAttacher(ImageView imageView) {
 3         this(imageView, true);
 4     }
 5 
 6     public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
 7         //這個是防止記憶體洩露的一個技巧,注意你在activity裡的imageview 物件如果把引用傳進來的話
 8         //那這裡的mImageView 也是指向外層的ImageView那個物件,那麼就相當於有2個引用指向同一塊地址!
 9         //當你外層的imageview物件被銷燬的時候,因為這裡還有一個引用指向那個物件,所以實際上物件不會被銷燬
10         //只是最外層的那個imageview物件的引用成了null而已,但是如果你在這裡用了弱引用的話,當外層的強引用
11         //為NUll的話 imageview物件會立刻被銷燬掉。
12         mImageView = new WeakReference<>(imageView);
13 
14         //這個draswingcache 我們做截圖的時候會經常用到,只需要理解成我們可以通過getDrawingCache拿到view裡的內容(這個內容被轉成了bitmap)
15         imageView.setDrawingCacheEnabled(true);
16         imageView.setOnTouchListener(this);
17 
18         //這裡就是監聽imageview的 layout變化用的 imageview發生變化就會呼叫這個回撥介面
19         ViewTreeObserver observer = imageView.getViewTreeObserver();
20         if (null != observer)
21             observer.addOnGlobalLayoutListener(this);
22 
23         // 設定繪製時這個imageview 可以隨著matrix矩陣進行變換
24         setImageViewScaleTypeMatrix(imageView);
25         //這個是讓你在視覺化介面能看到預覽效果的,大家自定義控制元件時 也可以使用這個技巧
26         if (imageView.isInEditMode()) {
27             return ;
28         }
29         // Create Gesture Detectors...
30         //根據版本不同 取得需要的mScaleDragDetector 主要就是監聽pinch手勢的
31         mScaleDragDetector = VersionedGestureDetector.newInstance(
32                 imageView.getContext(), this);
33 
34         //這個dectecor 就是用來監聽雙擊和長按事件的  
35         mGestureDetector = new GestureDetector(imageView.getContext(),
36                 new GestureDetector.SimpleOnGestureListener() {
37 
38                     // forward long click listener
39                     @Override
40                     public void onLongPress(MotionEvent e) {
41                         if (null != mLongClickListener) {
42                             mLongClickListener.onLongClick(getImageView());
43                         }
44                     }
45                 });
46         //監聽雙擊手勢的
47         mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
48 
49         // Finally, update the UI so that we're zoomable
50         setZoomable(zoomable);

我們先看19-21行 這個裡面加了一個監聽layout變化的一個回撥,我們來看看這個回撥做了什麼:

 1  @Override
 2     public void onGlobalLayout() {
 3         ImageView imageView = getImageView();
 4 
 5         if (null != imageView) {
 6             if (mZoomEnabled) {
 7                 //這個地方要注意imageview的 四個座標點是永遠不會變化的。
 8                 final int top = imageView.getTop();
 9                 final int right = imageView.getRight();
10                 final int bottom = imageView.getBottom();
11                 final int left = imageView.getLeft();
12 
13                 /**
14                  * We need to check whether the ImageView's bounds have changed.
15                  * This would be easier if we targeted API 11+ as we could just use
16                  * View.OnLayoutChangeListener. Instead we have to replicate the
17                  * work, keeping track of the ImageView's bounds and then checking
18                  * if the values change.
19                  */
20                 if (top != mIvTop || bottom != mIvBottom || left != mIvLeft
21                         || right != mIvRight) {
22                     // Update our base matrix, as the bounds have changed
23                     updateBaseMatrix(imageView.getDrawable());
24 
25                     // Update values as something has changed
26                     mIvTop = top;
27                     mIvRight = right;
28                     mIvBottom = bottom;
29                     mIvLeft = left;
30                 }
31             } else {
32                 updateBaseMatrix(imageView.getDrawable());
33             }
34         }
35     }

然後跟進去發現是這個函式:

 1 /**
 2      * Calculate Matrix for FIT_CENTER
 3      *
 4      * @param d - Drawable being displayed
 5      */
 6     private void updateBaseMatrix(Drawable d) {
 7         ImageView imageView = getImageView();
 8         if (null == imageView || null == d) {
 9             return;
10         }
11 
12         final float viewWidth = getImageViewWidth(imageView);
13         final float viewHeight = getImageViewHeight(imageView);
14         //這個是取原始圖片大小的 永遠不會變化的
15         final int drawableWidth = d.getIntrinsicWidth();
16         final int drawableHeight = d.getIntrinsicHeight();
17 
18         mBaseMatrix.reset();
19 
20         final float widthScale = viewWidth / drawableWidth;
21         final float heightScale = viewHeight / drawableHeight;
22 
23         //根據傳進去的scaletype的值來確定 基礎的matrix大小
24         if (mScaleType == ScaleType.CENTER) {
25             mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
26                     (viewHeight - drawableHeight) / 2F);
27 
28         } else if (mScaleType == ScaleType.CENTER_CROP) {
29             float scale = Math.max(widthScale, heightScale);
30             mBaseMatrix.postScale(scale, scale);
31             mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
32                     (viewHeight - drawableHeight * scale) / 2F);
33 
34         } else if (mScaleType == ScaleType.CENTER_INSIDE) {
35             float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
36             mBaseMatrix.postScale(scale, scale);
37             mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
38                     (viewHeight - drawableHeight * scale) / 2F);
39 
40         } else {
41             RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
42             RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
43 
44             switch (mScaleType) {
45                 case FIT_CENTER:
46                     mBaseMatrix
47                             .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
48                     break;
49 
50                 case FIT_START:
51                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
52                     break;
53 
54                 case FIT_END:
55                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
56                     break;
57 
58                 case FIT_XY:
59                     mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
60                     break;
61 
62                 default:
63                     break;
64             }
65         }
66 
67         resetMatrix();
68     }

其實這邊程式碼很容易懂,大家都知道 imageview裡面是一個圖片對吧,你這個圖片有大有小,那你是怎麼放置在imageview裡面就有講究了,你在imageview裡是可以設定的他的scaletype的屬性的,

那當然了你設定完畢以後 肯定相對於原圖片來說 你已經做了一次matrix變換了,所以你要記錄這次matrix的值。這裡你只要記住對於一個imageview 來說 他本身容器的大小是固定的,

容器裡面的drawable的原圖大小也是固定的,但是現實效果是通過matrix來控制的,所以我們要記錄每一次圖片發生變化的時候matrix的值,這是很關鍵的。

然後回到我們的建構函式看31-32行。這裡構造了一個

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

我們也繼續看看這個是如何構造出來的
 1 public final class VersionedGestureDetector {
 2 
 3     public static GestureDetector newInstance(Context context,
 4                                               OnGestureListener listener) {
 5         final int sdkVersion = Build.VERSION.SDK_INT;
 6         GestureDetector detector;
 7         if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
 8             detector = new CupcakeGestureDetector(context);
 9         } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
10             detector = new EclairGestureDetector(context);
11         } else {
12             //現在大部分 都是呼叫的這個
13             detector = new FroyoGestureDetector(context);
14         }
15 
16         detector.setOnGestureListener(listener);
17 
18         return detector;
19     }
20 
21 }

我們發現這個地方是一個單例,實際上這邊程式碼就是根據sdk的版本號不同 提供不一樣的功能,比如說pinch手勢 在4.0以下就沒有支援 那當然了,我們現在百分之95以上的機器都是4.0以上的,

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

 

 1 /*******************************************************************************
 2  * Copyright 2011, 2012 Chris Banes.
 3  * <p>
 4  * Licensed under the Apache License, Version 2.0 (the "License");
 5  * you may not use this file except in compliance with the License.
 6  * You may obtain a copy of the License at
 7  * <p>
 8  * http://www.apache.org/licenses/LICENSE-2.0
 9  * <p>
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *******************************************************************************/
16 package uk.co.senab.photoview.gestures;
17 
18 import android.annotation.TargetApi;
19 import android.content.Context;
20 import android.view.MotionEvent;
21 import android.view.ScaleGestureDetector;
22 
23 @TargetApi(8)
24 //能看出來 低於這個版本的 都不支援pinch 放大縮小功能
25 public class FroyoGestureDetector extends EclairGestureDetector {
26 
27     //用於檢測縮放的手勢
28     protected final ScaleGestureDetector mDetector;
29 
30     public FroyoGestureDetector(Context context) {
31         super(context);
32 
33         ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
34 
35             @Override
36             public boolean onScale(ScaleGestureDetector detector) {
37                 float scaleFactor = detector.getScaleFactor();
38 
39                 if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
40                     return false;
41 
42                 mListener.onScale(scaleFactor,
43                         detector.getFocusX(), detector.getFocusY());
44                 return true;
45             }
46 
47             //這個函式返回true的時候 onScale函式才會真正呼叫
48             @Override
49             public boolean onScaleBegin(ScaleGestureDetector detector) {
50                 return true;
51             }
52 
53             @Override
54             public void onScaleEnd(ScaleGestureDetector detector) {
55                 // NO-OP
56             }
57         };
58         mDetector = new ScaleGestureDetector(context, mScaleListener);
59     }
60 
61     @Override
62     public boolean isScaling() {
63         return mDetector.isInProgress();
64     }
65 
66     @Override
67     public boolean onTouchEvent(MotionEvent ev) {
68         mDetector.onTouchEvent(ev);
69         return super.onTouchEvent(ev);
70     }
71 
72 }

33行-58行 才是真正系統提供給我們的監聽pinch手勢的地方。就是我們印象中 兩指放大縮小圖片的那個手勢的監聽。看42-43行

這裡發現使用了一個回撥,回過頭去看我們的建構函式 我們就知道這個回撥 實際上就是在控制器裡他自己實現的。

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

所以我們就到控制器裡去找這個手勢監聽的實現程式碼:

 1 //這個就是處理pinch 手勢的,放大 縮小圖片的處理函式
 2     @Override
 3     public void onScale(float scaleFactor, float focusX, float focusY) {
 4         if (DEBUG) {
 5             LogManager.getLogger().d(
 6                     LOG_TAG,
 7                     String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f",
 8                             scaleFactor, focusX, focusY));
 9         }
10         if (getScale() < mMaxScale || scaleFactor < 1f) {
11             //這個回撥介面 你可以不用set的,通常來說 我們很少實現這個介面,有需要的可以自己去看註釋這介面的意思
12             if (null != mScaleChangeListener) {
13                 mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
14             }
15             //這個地方要注意 這個放大 並不是固定的以圖片中心放大的。他是以你兩個手指做pinch手勢的
16             //的時候 取點來放大的,這麼做的一個好的地方是 你可以放大圖片中某一部分,而不是隻能從
17             //從圖片中間部分開始放大縮小。但是你可以想一下,這麼做的弊端就是 很容易因為你的放大縮小
18             //因為點不在中間,所以圖片很有可能就不在imageview這個控制元件的中間,會讓imageview邊緣或者其他地方
19             //有留白
20             mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
21             //而這個函式就是解決上述弊端的,
22             checkAndDisplayMatrix();
23         }
24     }
1  /**
2      * Helper method that simply checks the Matrix, and then displays the result
3      */
4     private void checkAndDisplayMatrix() {
5         //實際上在matrix裡就解決了上述的弊端
6         if (checkMatrixBounds()) {
7             setImageViewMatrix(getDrawMatrix());
8         }
9     }
 1  //檢查當前顯示範圍是否在邊界上  然後對圖片進行平移(垂直或水平方向) 防止出現留白的現象
 2     private boolean checkMatrixBounds() {
 3         final ImageView imageView = getImageView();
 4         if (null == imageView) {
 5             return false;
 6         }
 7 
 8         final RectF rect = getDisplayRect(getDrawMatrix());
 9         if (null == rect) {
10             return false;
11         }
12 
13         final float height = rect.height(), width = rect.width();
14         float deltaX = 0, deltaY = 0;
15 
16         final int viewHeight = getImageViewHeight(imageView);
17         if (height <= viewHeight) {
18             switch (mScaleType) {
19                 case FIT_START:
20                     deltaY = -rect.top;
21                     break;
22                 case FIT_END:
23                     deltaY = viewHeight - height - rect.top;
24                     break;
25                 default:
26                     deltaY = (viewHeight - height) / 2 - rect.top;
27                     break;
28             }
29         } else if (rect.top > 0) {
30             deltaY = -rect.top;
31         } else if (rect.bottom < viewHeight) {
32             deltaY = viewHeight - rect.bottom;
33         }
34 
35         final int viewWidth = getImageViewWidth(imageView);
36         if (width <= viewWidth) {
37             switch (mScaleType) {
38                 case FIT_START:
39                     deltaX = -rect.left;
40                     break;
41                 case FIT_END:
42                     deltaX = viewWidth - width - rect.left;
43                     break;
44                 default:
45                     deltaX = (viewWidth - width) / 2 - rect.left;
46                     break;
47             }
48             mScrollEdge = EDGE_BOTH;
49         } else if (rect.left > 0) {
50             mScrollEdge = EDGE_LEFT;
51             deltaX = -rect.left;
52         } else if (rect.right < viewWidth) {
53             deltaX = viewWidth - rect.right;
54             mScrollEdge = EDGE_RIGHT;
55         } else {
56             mScrollEdge = EDGE_NONE;
57         }
58 
59         // Finally actually translate the matrix
60         mSuppMatrix.postTranslate(deltaX, deltaY);
61         return true;
62     }

這個地方有的人可能會對最後那個檢測是否在邊界的那個函式不太明白,其實還是挺好理解的,對於容器imageview來說 他的範圍是固定的。裡面的drawable是不斷的變化的,

但是這個drawable 可以和 RectF來關聯起來,這個rectF 就是描述出一個矩形,這個矩形就恰好是drawable的大小範圍。他有四個值 分別是top left right和bottom。

其中2個值表示矩形的左上面ed點的座標 另外2個表示右下角的座標。一個矩形由這2個點即可確定位置以及大小。我用下圖來表示:

 

所以那個函式你要想理解的話 就是自己去畫個圖。就能知道如何判斷是否到邊緣了!實際上就是drawbl---matrix---rectF的一個轉換。
另外一定要自己畫圖 才能真正理解 裡面的邏輯 很簡單 並不難!

那這個onscale 手勢我們過了一遍以後 看看這個函式是怎麼被呼叫的
很簡單 還是通過onTouch事件

 1  @SuppressLint("ClickableViewAccessibility")
 2     @Override
 3     public boolean onTouch(View v, MotionEvent ev) {
 4         boolean handled = false;
 5         if (mZoomEnabled && hasDrawable((ImageView) v)) {
 6             ViewParent parent = v.getParent();
 7             switch (ev.getAction()) {
 8                 case ACTION_DOWN:
 9                     // First, disable the Parent from intercepting the touch
10                     // event
11                     if (null != parent) {
12                         //阻止父層的View截獲touch事件
13                         parent.requestDisallowInterceptTouchEvent(true);
14                     } else {
15                         LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null");
16                     }
17 
18                     // If we're flinging, and the user presses down, cancel
19                     // fling
20                     cancelFling();
21                     break;
22 
23                 case ACTION_CANCEL:
24                 case ACTION_UP:
25                     //放大縮小都得有一個度,這個地方就是說 如果你縮的太小了,比如我們定義的是縮小到原圖的百分之25
26                     //如果你縮小到百分之10了,那麼當你手指鬆開的時候 就要自動將圖片還原到百分之25,當然這個過程
27                     //你得使用動畫慢慢從10回覆到25,因為一下子回覆到25 實在是太難看了
28                     //在下一幀繪製前,系統會執行該 Runnable,這樣我們就可以在 runnable 中更新 UI 狀態.
29                     //原理上類似一個遞迴呼叫,每次 UI 繪製前更新 UI 狀態,並指定下次 UI 更新前再執行自己.
30                     //這種寫法 與 使用迴圈或 Handler 每隔 16ms 重新整理一次 UI 基本等價,但是更為方便快捷
31                     if (getScale() < mMinScale) {
32                         RectF rect = getDisplayRect();
33                         if (null != rect) {
34                             v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
35                                     rect.centerX(), rect.centerY()));
36                             handled = true;
37                         }
38                     }
39                     break;
40             }
41 
42             // Try the Scale/Drag detector
43             if (null != mScaleDragDetector) {
44                 boolean wasScaling = mScaleDragDetector.isScaling();
45                 boolean wasDragging = mScaleDragDetector.isDragging();
46                 //這行程式碼是最終交給pinch手勢監聽的程式碼
47                 handled = mScaleDragDetector.onTouchEvent(ev);
48 
49                 boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
50                 boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
51 
52                 mBlockParentIntercept = didntScale && didntDrag;
53             }
54 
55             // Check to see if the user double tapped
56             if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
57                 handled = true;
58             }
59 
60         }
61         return handled;
62     }

好 到這個地方 相信大家對 pinch事件就理解的差不多了 包括是怎麼被呼叫的這個過程 以及中間的缺陷處理 都明白了。我們繼續看drag 也就是拖動是怎麼處理的。

1 /能看出來 低於這個版本的 都不支援pinch 放大縮小功能
2 public class FroyoGestureDetector extends EclairGestureDetector {

看的到 他是繼承自這個類的

1 @TargetApi(5)
2 public class EclairGestureDetector extends CupcakeGestureDetector {
  1 /*******************************************************************************
  2  * Copyright 2011, 2012 Chris Banes.
  3  * <p>
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  * <p>
  8  * http://www.apache.org/licenses/LICENSE-2.0
  9  * <p>
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  *******************************************************************************/
 16 package uk.co.senab.photoview.gestures;
 17 
 18 import android.content.Context;
 19 import android.view.MotionEvent;
 20 import android.view.VelocityTracker;
 21 import android.view.ViewConfiguration;
 22 
 23 import uk.co.senab.photoview.log.LogManager;
 24 
 25 public class CupcakeGestureDetector implements GestureDetector {
 26 
 27     //慣性滑動拖動處理
 28     protected OnGestureListener mListener;
 29     private static final String LOG_TAG = "CupcakeGestureDetector";
 30     float mLastTouchX;
 31     float mLastTouchY;
 32     final float mTouchSlop;
 33     final float mMinimumVelocity;
 34 
 35     @Override
 36     public void setOnGestureListener(OnGestureListener listener) {
 37         this.mListener = listener;
 38     }
 39 
 40     public CupcakeGestureDetector(Context context) {
 41         final ViewConfiguration configuration = ViewConfiguration
 42                 .get(context);
 43         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
 44         mTouchSlop = configuration.getScaledTouchSlop();
 45     }
 46 
 47     private VelocityTracker mVelocityTracker;
 48     private boolean mIsDragging;
 49 
 50     float getActiveX(MotionEvent ev) {
 51         return ev.getX();
 52     }
 53 
 54     float getActiveY(MotionEvent ev) {
 55         return ev.getY();
 56     }
 57 
 58     public boolean isScaling() {
 59         return false;
 60     }
 61 
 62     public boolean isDragging() {
 63         return mIsDragging;
 64     }
 65 
 66     @Override
 67     public boolean onTouchEvent(MotionEvent ev) {
 68         switch (ev.getAction()) {
 69             case MotionEvent.ACTION_DOWN: {
 70                 mVelocityTracker = VelocityTracker.obtain();
 71                 if (null != mVelocityTracker) {
 72                     mVelocityTracker.addMovement(ev);
 73                 } else {
 74                     LogManager.getLogger().i(LOG_TAG, "Velocity tracker is null");
 75                 }
 76 
 77                 mLastTouchX = getActiveX(ev);
 78                 mLastTouchY = getActiveY(ev);
 79                 mIsDragging = false;
 80                 break;
 81             }
 82 
 83             case MotionEvent.ACTION_MOVE: {
 84                 //這個拖動事件其實也很好理解,就是確定你的手指在滑動的時候座標點的變化
 85                 //這個變化要理解好 就是你可以把螢幕左上角的點 想象成一個座標系的原點。
 86                 //那你如果要計算某個座標點和這個原點的直線距離 實際上就是 這個座標點的
 87                 // x*x+y*y 然後把這個值開根號即可,初中數學問題!只要你每次這個直線距離
 88                 //有變化 那就肯定是拖動事件了
 89                 final float x = getActiveX(ev);
 90                 final float y = getActiveY(ev);
 91                 final float dx = x - mLastTouchX, dy = y - mLastTouchY;
 92 
 93                 if (!mIsDragging) {
 94                     // Use Pythagoras to see if drag length is larger than
 95                     // touch slop
 96                     mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
 97                 }
 98 
 99                 if (mIsDragging) {
100                     //同樣的在確定要滑動的時候,也是通過回撥來實現的
101                     mListener.onDrag(dx, dy);
102                     mLastTouchX = x;
103                     mLastTouchY = y;
104 
105                     if (null != mVelocityTracker) {
106                         mVelocityTracker.addMovement(ev);
107                     }
108                 }
109                 break;
110             }
111 
112             case MotionEvent.ACTION_CANCEL: {
113                 // Recycle Velocity Tracker
114                 if (null != mVelocityTracker) {
115                     mVelocityTracker.recycle();
116                     mVelocityTracker = null;
117                 }
118                 break;
119             }
120 
121             case MotionEvent.ACTION_UP: {
122                 if (mIsDragging) {
123                     if (null != mVelocityTracker) {
124                         mLastTouchX = getActiveX(ev);
125                         mLastTouchY = getActiveY(ev);
126 
127                         // Compute velocity within the last 1000ms
128                         mVelocityTracker.addMovement(ev);
129                         //每秒移動多少個畫素點
130                         mVelocityTracker.computeCurrentVelocity(1000);
131 
132                         //算移動速率的
133                         final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
134                                 .getYVelocity();
135 
136                         // If the velocity is greater than minVelocity, call
137                         // listener
138                         if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
139                             //回撥實現慣性
140                             mListener.onFling(mLastTouchX, mLastTouchY, -vX,
141                                     -vY);
142                         }
143                     }
144                 }
145 
146                 // Recycle Velocity Tracker
147                 if (null != mVelocityTracker) {
148                     mVelocityTracker.recycle();
149                     mVelocityTracker = null;
150                 }
151                 break;
152             }
153         }
154 
155         return true;
156     }
157 }

應該都能明白,我們看看那個介面到底是啥

 1 /*******************************************************************************
 2  * Copyright 2011, 2012 Chris Banes.
 3  *
 4  * Licensed under the Apache License, Version 2.0 (the "License");
 5  * you may not use this file except in compliance with the License.
 6  * You may obtain a copy of the License at
 7  *
 8  * http://www.apache.org/licenses/LICENSE-2.0
 9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *******************************************************************************/
16 package uk.co.senab.photoview.gestures;
17 
18 public interface OnGestureListener {
19 
20     public void onDrag(float dx, float dy);
21 
22     public void onFling(float startX, float startY, float velocityX,
23                         float velocityY);
24 
25     public void onScale(float scaleFactor, float focusX, float focusY);
26 
27 }

最後 再看看控制器裡這個介面是怎麼實現onDrag的,onFling就不分析了 差不多其實

 1  @Override
 2     public void onDrag(float dx, float dy) {
 3         if (mScaleDragDetector.isScaling()) {
 4             return; // Do not drag if we are already scaling
 5         }
 6 
 7         if (DEBUG) {
 8             LogManager.getLogger().d(LOG_TAG,
 9                     String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
10         }
11 
12         ImageView imageView = getImageView();
13         mSuppMatrix.postTranslate(dx, dy);
14         //滑動的時候也不要忘記檢測邊緣 防止留白
15         checkAndDisplayMatrix();
16 
17         //這個地方就是做了一個巧妙的判斷,他要實現的功能就是:
18         //如果你拖拽到了邊緣,還繼續拖拽的話 那就交給父view來處理,如果沒有到邊緣 那我們就繼續自己處理 繼續拖拽圖片了
19         //想象一下我們把photoview放到viewpager的時候 就是這樣處理的
20         ViewParent parent = imageView.getParent();
21         if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
22             if (mScrollEdge == EDGE_BOTH
23                     || (mScrollEdge == EDGE_LEFT && dx >= 1f)
24                     || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
25                 if (null != parent)
26                     parent.requestDisallowInterceptTouchEvent(false);
27             }
28         } else {
29             if (null != parent) {
30                 parent.requestDisallowInterceptTouchEvent(true);
31             }
32         }
33     }

到這我們的這篇部落格就基本結束了,其實這個開源photoview真的挺值得大家好好分析的,如果分析的好,你對android裡面 各種手勢監聽 matrix rectF
等等應該都能瞭如指掌了,以後遇到類似的問題 應該不會沒有頭緒了!

 

相關文章