PhotoView图片缩放控件源码浅析(一)
本文參考自http://www.tuicool.com/articles/ea2ANjm
簡介
PhotoView屬性:?
可以用于查看圖片,并對圖片進(jìn)行拖動(dòng)縮放,拖動(dòng)過程中不會(huì)出現(xiàn)邊緣空白;?
雙擊縮小放大,Fling移動(dòng),并支持上述過程的漸變;?
在放大情況下也支持viewpager等的拖動(dòng)切換;?
支持多擊事件檢測,單機(jī),雙擊事件;?
支持各種回調(diào)給調(diào)用者;
具體的調(diào)用方法如下
//將imageview和PhotoViewAttacher 這個(gè)控制器關(guān)聯(lián)起來 mAttacher = new PhotoViewAttacher(mImageView); 可以看出來 主要的工作都是在這個(gè)PhotoViewAttacher里做的。
public PhotoViewAttacher(ImageView imageView) {//使用軟引用,防止內(nèi)存泄露mImageView = new WeakReference<ImageView>(imageView); //這個(gè)draswingcache 我們做截屏的時(shí)候會(huì)經(jīng)常用到,只需要理解成我們可以通過getDrawingCache拿到view里的內(nèi)容(這個(gè)內(nèi)容被轉(zhuǎn)成了bitmap) imageView.setDrawingCacheEnabled(true); imageView.setOnTouchListener(this); ?//這里就是監(jiān)聽imageview的 layout變化用的 imageview發(fā)生變化就會(huì)調(diào)用這個(gè)回調(diào)接口 ViewTreeObserver observer = imageView.getViewTreeObserver(); if (null != observer)observer.addOnGlobalLayoutListener(this); ?// 設(shè)置繪制時(shí)這個(gè)imageview 可以隨著matrix矩陣進(jìn)行變換 setImageViewScaleTypeMatrix(imageView); ? //這個(gè)是讓你在可視化界面能看到預(yù)覽效果的,大家自定義控件時(shí) 也可以使用這個(gè)技巧 if (imageView.isInEditMode()) {return; } //根據(jù)版本不同 取得需要的mScaleDragDetector 主要就是監(jiān)聽pinch手勢的 mScaleDragDetector = VersionedGestureDetector.newInstance(imageView.getContext(), this); //這個(gè)dectecor 就是用來監(jiān)聽雙擊和長按事件的? mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() {// forward long click listener @Override public void onLongPress(MotionEvent e) {if (null != mLongClickListener) {mLongClickListener.onLongClick(getImageView()); }}}); //監(jiān)聽雙擊事件 mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); // Finally, update the UI so that we're zoomable setZoomable(true); }
接下來我們來看看?addOnGlobalLayoutListener這個(gè)接聽里做了什么,接口中有onGlobalLayout方法
public void onGlobalLayout() {ImageView imageView = getImageView(); if (null != imageView) {if (mZoomEnabled) {?//這個(gè)地方要注意imageview的 四個(gè)坐標(biāo)點(diǎn)是永遠(yuǎn)不會(huì)變化的。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()); }} } 然后我們跟到updateBaseMatrix()里邊
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();//這個(gè)是取原始圖片大小的 永遠(yuǎn)不會(huì)變化的 final int drawableHeight = d.getIntrinsicHeight(); mBaseMatrix.reset(); final float widthScale = viewWidth / drawableWidth; final float heightScale = viewHeight / drawableHeight; if (mScaleType == ScaleType.CENTER) {? ?//根據(jù)傳進(jìn)去的scaletype的值來確定 基礎(chǔ)的matrix大小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(); } 然后我們跟到resetMatrix()中看看
/** * Resets the Matrix back to FIT_CENTER, and then displays it.s */ private void resetMatrix() {mSuppMatrix.reset(); setImageViewMatrix(getDrawMatrix()); checkMatrixBounds(); } 進(jìn)入checkMatrixBounds()中看看
private boolean checkMatrixBounds() {//檢查當(dāng)前顯示范圍是否在邊界上 ?然後對圖片進(jìn)行平移(垂直或水平方向) 防止出現(xiàn)留白的現(xiàn)象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; }
這個(gè)地方有的人可能會(huì)對最后那個(gè)檢測是否在邊界的那個(gè)函數(shù)不太明白,其實(shí)還是挺好理解的,對于容器imageview來說 他的范圍是固定的。里面的drawable是不斷的變化的,
但是這個(gè)drawable 可以和 RectF來關(guān)聯(lián)起來,這個(gè)rectF 就是描述出一個(gè)矩形,這個(gè)矩形就恰好是drawable的大小范圍。他有四個(gè)值 分別是top left right和bottom。
其中2個(gè)值表示矩形的左上面ed點(diǎn)的坐標(biāo) 另外2個(gè)表示右下角的坐標(biāo)。一個(gè)矩形由這2個(gè)點(diǎn)即可確定位置以及大小。我用下圖來表示:
所以那個(gè)函數(shù)你要想理解的話 就是自己去畫個(gè)圖。就能知道如何判斷是否到邊緣了!實(shí)際上就是drawbl---matrix---rectF的一個(gè)轉(zhuǎn)換。 接著回到PhotoViewAttacher的構(gòu)造方法,我們看到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; } 我們發(fā)現(xiàn)這個(gè)地方是一個(gè)單例,實(shí)際上這邊代碼就是根據(jù)sdk的版本號不同 提供不一樣的功能,接著我們看FroyoGestureDetector()
@TargetApi(8) 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; }//這個(gè)函數(shù)返回true,onScale函數(shù)才會(huì)被真正調(diào)用@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); }}
總結(jié)
以上是生活随笔為你收集整理的PhotoView图片缩放控件源码浅析(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用photoview+viewpage
- 下一篇: 圆形头像CircleImageView源