多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > PhotoView 源碼解讀

PhotoView 源碼解讀

來源:程序員人生   發布時間:2016-08-01 17:24:36 閱讀次數:3058次

轉載請注明本文出自maplejaw的博客(http://blog.csdn.net/maplejaw_)

開源庫地址:https://github.com/chrisbanes/PhotoView
PhotoView是1個用來幫助開發者輕松實現ImageView縮放的庫。開發者可以輕易控制對圖片的縮放旋等等操作。
image_1akkgdkg01saq1l01jdvaaqeeh9.png-11.6kB
PhotoView的使用極為簡單,而且提供了兩種方案??墒褂闷胀ǖ腎mageView,也能夠使用該庫中提供的ImageView(PhotoView)。

  • 使用PhotoView
    只需以下援用該庫中的ImageView,無需關心其它實現細節,你的ImageView即可具有縮放效果。
"@+id/iv_photo" android:layout_width="fill_parent" android:layout_height="fill_parent" />
  • 針對普通ImageView
    有的時候,可能由于1些歷史緣由,使得你不能不用原來的ImageView。榮幸的是該庫也提供了1種解決方案。只需用PhotoViewAttacher包裝便可。
PhotoViewAttacher mAttacher=new PhotoViewAttacher(mImageView);//用PhotoViewAttacher包裝 mAttacher.update();//當圖片改變時需調用update(); mAttacher.cleanup();//當ImageView不再使用時回收資源(可在onDestory中 調用)。PhotoView已實現了這個功能不需要自己管理。

PhotoView真的很奇異,接下來我們去源碼里1探究竟吧。順便多說1句,圖片的縮放大量應用到了Matrix相干知識,不了解的務必要先查閱相干資料哦。強烈推薦Android Matrix 這篇文章,固然也能夠看我的這篇Android Matrix矩陣詳解。

源碼解讀

這次源碼解讀我們從使用普通ImageView入手,普通的ImageView如果想縮放,必須依賴于PhotoViewAttacher,而PhotoViewAttacher又實現了IPhotoView接口。IPhotoView主要定義了1些經常使用的操作和默許值,由于方法實在太多了,就不逐一羅列了,直接上圖。
IPhotoView定義的所有抽象方法以下。
image_1am7q83srm9n1nb5odsarv17sm9.png-98.5kB
IPhotoView的部份源碼以下。

public interface IPhotoView { float DEFAULT_MAX_SCALE = 3.0f;//默許最大縮放倍數為3倍 float DEFAULT_MID_SCALE = 1.75f;//默許中間縮放倍數為1.75倍 float DEFAULT_MIN_SCALE = 1.0f;//默許最小縮放倍數為1倍 int DEFAULT_ZOOM_DURATION = 200;//默許的縮放間隔為200ms boolean canZoom();//可以縮放 RectF getDisplayRect();//獲得顯示矩形 boolean setDisplayMatrix(Matrix finalMatrix);//設置顯示矩陣 Matrix getDisplayMatrix();//獲得顯示矩陣 //.. //省略了部份源碼

介紹完IPhotoView接口后,現在改來看看PhotoViewAttacher了,PhotoViewAttacher的屬性也比較多,以下:

private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();//插值器,用于縮放動畫 int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;//默許的縮放間隔 static final int EDGE_NONE = -1;//圖片兩邊都不在邊沿內 static final int EDGE_LEFT = 0;//圖片左側顯示在View的左側緣內 static final int EDGE_RIGHT = 1;//圖片右側顯示在View的右側緣內 static final int EDGE_BOTH = 2;//圖片兩邊都在邊沿內 static int SINGLE_TOUCH = 1;//單指 private float mMinScale = DEFAULT_MIN_SCALE;//最小縮放倍數 private float mMidScale = DEFAULT_MID_SCALE;//中間縮放倍數 private float mMaxScale = DEFAULT_MAX_SCALE;//最大縮放倍數 private boolean mAllowParentInterceptOnEdge = true;//當在邊沿操作時,允許父布局攔截事件。 private boolean mBlockParentIntercept = false;//禁止父布局攔截事件 private WeakReferencemImageView;//弱援用 //手勢探測器 private GestureDetector mGestureDetector;//單擊,長按,Fling private uk.co.senab.photoview.gestures.GestureDetector mScaleDragDetector;//縮放和拖拽 private final Matrix mBaseMatrix = new Matrix();//基礎矩陣,用來保存初始的顯示矩陣 private final Matrix mDrawMatrix = new Matrix();//繪畫矩陣,用來計算最后顯示區域的矩陣,是在mBaseMatrix和mSuppMatrix的基礎上計算出來的。 private final Matrix mSuppMatrix = new Matrix();//這個矩陣我也不知道怎樣稱呼,也不知道是否是Supply的意思,暫且叫作供應矩陣吧,用來保存旋轉平移和縮放的矩陣。 private final RectF mDisplayRect = new RectF();//顯示矩形 private final float[] mMatrixValues = new float[9];//用來保存矩陣的值。3*3 // 各類監聽 private OnMatrixChangedListener mMatrixChangeListener; private OnPhotoTapListener mPhotoTapListener; private OnViewTapListener mViewTapListener; private OnLongClickListener mLongClickListener; private OnScaleChangeListener mScaleChangeListener; private OnSingleFlingListener mSingleFlingListener; //保存ImageView的top,right,bottom,left private int mIvTop, mIvRight, mIvBottom, mIvLeft; //Fling時的Runable private FlingRunnable mCurrentFlingRunnable; private int mScrollEdge = EDGE_BOTH;//兩邊邊沿 private float mBaseRotation;//基礎旋轉角度 private boolean mZoomEnabled;//是不是可以縮放 private ScaleType mScaleType = ScaleType.FIT_CENTER;//默許縮放類型

另外PhotoViewAttacher中還定義了以下幾個接口。

public interface OnMatrixChangedListener { /** * 當用來顯示Drawable的Matrix改變時回調 * @param rect - 顯示Drawable的新邊界 */ void onMatrixChanged(RectF rect); } public interface OnScaleChangeListener { /** * 當ImageView改變縮放時回調 * * @param scaleFactor 小于1表示縮小,大于1表示放大 * @param focusX 縮放焦點X * @param focusY 縮放焦點Y */ void onScaleChange(float scaleFactor, float focusX, float focusY); } public interface OnPhotoTapListener { /** * *當用戶敲擊在照片上時回調,如果在空白區域不會回調 * @param view - ImageView * @param x -用戶敲擊的位置(在圖片中從左往右的位置)占圖片寬度的百分比 * @param y -用戶敲擊的位置(在圖片中從上往下的位置)占圖片高度的百分比 */ void onPhotoTap(View view, float x, float y); /** * 在圖片外部的空白區域敲擊回調 * */ void onOutsidePhotoTap(); } public interface OnViewTapListener { /** * 只要用戶敲擊ImageView就會回調,不論是不是在圖片上。 * @param view - View the user tapped. * @param x -敲擊View的x坐標 * @param y -敲擊View的y坐標 */ void onViewTap(View view, float x, float y); } public interface OnSingleFlingListener { /** * 用戶使用單指在ImageView上快速滑動時回調,不論是不是在圖片上。 * @param e1 - 第1次觸摸事件 * @param e2 - 第2次觸摸事件 * @param velocityX - 水平滑過的速度. * @param velocityY - 豎直滑過的素組. */ boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); }

在看完PhotoViewAttacher的1些屬性和接口外,現在就來看PhotoViewAttacher的構造方法。即new PhotoViewAttacher(mImageView)這1句。

public PhotoViewAttacher(ImageView imageView) { this(imageView, true); } public PhotoViewAttacher(ImageView imageView, boolean zoomable) { mImageView = new WeakReference<>(imageView);//弱援用 imageView.setDrawingCacheEnabled(true);//開啟繪制緩存區,用于獲得可見區的bitmap imageView.setOnTouchListener(this);//設置Touch監聽,用于添加手勢監聽 ViewTreeObserver observer = imageView.getViewTreeObserver(); if (null != observer) observer.addOnGlobalLayoutListener(this);//用于監聽ImageView的大小 // 確保ImageView的ScaleType為Matrix setImageViewScaleTypeMatrix(imageView); if (imageView.isInEditMode()) { return; } //初始化多指縮放/拖拽手勢探測器 mScaleDragDetector = VersionedGestureDetector.newInstance( imageView.getContext(), this); //初始化其它手勢監聽(長按,Fling) mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() { //長按 @Override public void onLongPress(MotionEvent e) { if (null != mLongClickListener) { mLongClickListener.onLongClick(getImageView()); } } //Fling @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mSingleFlingListener != null) { if (getScale() > DEFAULT_MIN_SCALE) { return false; } if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH || MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) { return false; } return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY); } return false; } }); //設置默許的雙擊處理方案。 mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); //基礎旋轉角度 mBaseRotation = 0.0f; //設置是不是可縮放 setZoomable(zoomable); }

構造方法主要做了1些初始化工作,比如添加了手勢監聽(雙指縮放,拖拽,雙擊,長按)等等。而且,如果希望圖片具有縮放功能,還得設置ImageView的scaleType為matrix,下面我們就1步步剖析。

默許設置

為了理解起來更聯貫1點,我們先看setZoomable中的源碼。

@Override public void setZoomable(boolean zoomable) { mZoomEnabled = zoomable; update(); } public void update() { ImageView imageView = getImageView();//獲得ImageView if (null != imageView) { if (mZoomEnabled) { //再次確保ImageView的ScaleType為MATRIX setImageViewScaleTypeMatrix(imageView); //更新基礎矩陣mBaseMatrix updateBaseMatrix(imageView.getDrawable()); } else { //重置矩陣 resetMatrix(); } } }

可以看出,除賦值mZoomEnabled外,還調用了update()方法,前面我們說了,每次更換圖片時需調用update()刷新。在update()中,如果是可縮放的,就更新mBaseMatrix,否則重置矩陣。
updateBaseMatrix的源碼以下:

private void updateBaseMatrix(Drawable d) { ImageView imageView = getImageView();//獲得ImageView if (null == imageView || null == d) { return; } //獲得ImageView的寬高 final float viewWidth = getImageViewWidth(imageView); final float viewHeight = getImageViewHeight(imageView); //獲得Drawable的固有的寬高 final int drawableWidth = d.getIntrinsicWidth(); final int drawableHeight = d.getIntrinsicHeight(); mBaseMatrix.reset();//重置mBaseMatrix矩陣 //獲得寬的縮放比,drawableWidth * widthScale = viewWidth final float widthScale = viewWidth / drawableWidth; //獲得高的縮放比,drawableHeight * heightScale = viewHeight final float heightScale = viewHeight / drawableHeight; //注意,這里的ScaleType不是ImageView的ScaleType,由于ImageView的ScaleType已被強迫設為Matrix。這里的ScaleType是PhotoViewAttacher的ScaleType,因此可以通過設置PhotoViewAttacher的setScaleType來摹擬原ImageView的效果,以滿足實際需求。 if (mScaleType == ScaleType.CENTER) {//如果縮放類型為ScaleType.CENTER //基礎矩陣就平移二者的寬度差1半,以保持居中 mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, (viewHeight - drawableHeight) / 2F); } else if (mScaleType == ScaleType.CENTER_CROP) {//如果縮放類型為ScaleType.CENTER_CROP float scale = Math.max(widthScale, heightScale);//取最大值 mBaseMatrix.postScale(scale, scale);//使最小的那1邊也縮放到View的尺寸 //平移到中間 mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F); } else if (mScaleType == ScaleType.CENTER_INSIDE) { //如果縮放類型為ScaleType.CENTER_INSIDE //計算縮放值 float scale = Math.min(1.0f, Math.min(widthScale, heightScale)); //當圖片寬高超越View寬高時調用,否則縮放還是1 mBaseMatrix.postScale(scale, scale); //平移到中間 mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);//平移 } else { //如果是FIT_XX相干的縮放類型 RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight); RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight); if ((int) mBaseRotation % 180 != 0) { mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth); } //直接根據Matrix提供的setRectToRect來設置 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(); }

可以看出updateBaseMatrix,主要是在根據ScaleType來調劑顯示位置和縮放級別,使其到達ImageView的ScaleType效果。為何需要這個功能?由于ImageView已被強迫設置ScaleType為Matrix,但是如果我們依然需要ScaleType的顯示效果怎樣辦?因而PhotoViewAttacher提供了setScaleType來摹擬相干效果。從上面的源碼應當不難看出,mBaseMatrix用來保存根據ScaleType調劑過的的原始矩陣。默許的ScaleType為ScaleType.FIT_CENTER。
接下來,我們來看resetMatrix()。

private void resetMatrix() { mSuppMatrix.reset();//重置供應矩陣 setRotationBy(mBaseRotation);//設置初始的旋轉角度 setImageViewMatrix(getDrawMatrix());//把最mDrawMatrix設置給ImageView,以對圖片進行變化。 checkMatrixBounds();//檢查Matrix邊界 }

設置旋轉角度的源碼以下,mSuppMatrix后乘了旋轉角度。然落后行檢查邊界,最落后行顯示。

public void setRotationBy(float degrees) { mSuppMatrix.postRotate(degrees % 360);//后乘旋轉角度 checkAndDisplayMatrix();//檢查Matrix邊界,然后顯示 } //檢查Matrix邊界和顯示 private void checkAndDisplayMatrix() { if (checkMatrixBounds()) { //調劑效果進行顯示 setImageViewMatrix(getDrawMatrix()); } }

checkMatrixBounds()用來檢查Matrix邊界。相干源碼以下。

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);//獲得View的高 if (height <= viewHeight) {//如果圖片的高小于等于View,說明圖片的垂直方向可以完全顯示在View里面 //因而根據縮放類型進行邊界調劑 switch (mScaleType) { case FIT_START: deltaY = -rect.top;//向上移動到View的頂部 break; case FIT_END: deltaY = viewHeight - height - rect.top;//向下移動到View的底部 break; default: deltaY = (viewHeight - height) / 2 - rect.top;//否則就居中顯示 break; } } else if (rect.top > 0) { //如果圖片高度超越來View的高,但是rect.top > 0說明ImageView上邊還有空余的區域。 deltaY = -rect.top;//因而計算偏移距離 } else if (rect.bottom < viewHeight) { //同理。底部也有空余 deltaY = viewHeight - rect.bottom; } //獲得ImageView的寬,同理進行邊界調劑。 final int viewWidth = getImageViewWidth(imageView); if (width <= viewWidth) {//如果寬度小于View的寬,進行相應調劑 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;//圖片寬度小于View的寬度,說明兩邊顯示在邊沿內 } else if (rect.left > 0) { mScrollEdge = EDGE_LEFT;//rect.left > 0表示顯示在左側邊沿內 deltaX = -rect.left; } else if (rect.right < viewWidth) { deltaX = viewWidth - rect.right; mScrollEdge = EDGE_RIGHT;//右側在邊沿內 } else { mScrollEdge = EDGE_NONE;//兩邊都不在邊沿內 } //最后,將平移給mSuppMatrix mSuppMatrix.postTranslate(deltaX, deltaY); return true; }

為何要檢查邊界呢?那是由于當你進行旋轉或縮放變換后,由于縮放的錨點是以手指為中心的,有時候會發現顯示的區域不對,比如說,當圖片大于View的寬高時,但是矩陣的邊界與View之間竟然還有空白區,明顯不太公道。這時候需要進行平移對齊View的寬高。

在檢查顯示邊界時,我們需要獲得圖片的顯示矩形,那末怎樣獲得Drawable的終究顯示矩形呢?
getDrawMatrix()用來獲得mDrawMatrix終究矩陣,mDrawMatrix實際上是在mBaseMatrix基礎矩陣上后乘mSuppMatrix供應矩陣產生的。

public Matrix getDrawMatrix() { mDrawMatrix.set(mBaseMatrix); mDrawMatrix.postConcat(mSuppMatrix); return mDrawMatrix; }

通過setImageViewMatrix將終究的矩陣利用到ImageView中,這時候我們就可以看到顯示效果了。

private void setImageViewMatrix(Matrix matrix) { ImageView imageView = getImageView();//獲得ImageView if (null != imageView) { checkImageViewScaleType();//檢查縮放類型,必須為Matrix,否則拋異常 imageView.setImageMatrix(matrix);//利用矩陣 //回調監聽 if (null != mMatrixChangeListener) { RectF displayRect = getDisplayRect(matrix);//獲得顯示矩形 if (null != displayRect) { mMatrixChangeListener.onMatrixChanged(displayRect); } } } }

另外,通過以下的源碼可以獲得顯示矩形,matrix.mapRect用來映照最新的變換到原始的矩形。

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());//獲得Drawable尺寸,初始化原始矩形 matrix.mapRect(mDisplayRect);//將矩陣的變換映照給mDisplayRect,得到終究矩形 return mDisplayRect; } } return null; }

看完以上的源碼,相信流程已非常清楚了,當設置圖片時,通過update()我們可以初始化1個mBaseMatrix,然后如果想縮放、旋轉等,進行設置利用到mSuppMatrix,終究通過對mBaseMatrix和mSuppMatrix計算得到mDrawMatrix,然后利用到ImageView中,便完成了我們的使命了。

既然1切的變換都會利用到mSuppMatrix中。那末接下來我們回到PhotoViewAttacher的構造方法中繼續瀏覽其他源碼,以了解這個進程究竟是怎樣實現的。

Touch事件監聽

Touch事件中,主要讓手勢探測器進行處理事件。核心源碼以下。

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: if (null != parent) { //不允許父布局攔截ACTION_DOWN事件 parent.requestDisallowInterceptTouchEvent(true); } else { LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null"); } cancelFling(); //取消Fling事件 break; case ACTION_CANCEL: case ACTION_UP: //當手指抬起時 if (getScale() < mMinScale) {//如果小于最小值 RectF rect = getDisplayRect();//獲得顯示矩陣 if (null != rect) { //恢復到最小 v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY())); handled = true; } } break; } //如果mScaleDragDetector(縮放、拖拽)不為空,讓它處理事件 if (null != mScaleDragDetector) { //獲得狀態 boolean wasScaling = mScaleDragDetector.isScaling(); boolean wasDragging = mScaleDragDetector.isDragging(); handled = mScaleDragDetector.onTouchEvent(ev); //mScaleDragDetector處理事件過后的狀態,如果前后都不在縮放和拖拽,就允許父布局攔截 boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling(); boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging(); mBlockParentIntercept = didntScale && didntDrag;//禁止父類攔截的標識 } // 如果mGestureDetector(雙擊,長按)不為空,交給它處理事件 if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) { handled = true; } } return handled; }

雙擊縮放

我們來看1下雙擊縮放mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));這類實現方案。DefaultOnDoubleTapListener實現了GestureDetector.OnDoubleTapListener接口。

public interface OnDoubleTapListener { /** * 當單擊時回調,不同于OnGestureListener.onSingleTapUp(MotionEvent),這個回調方法只在確信譽戶不會產生第2次敲擊時調用 * @param e MotionEvent.ACTION_DOWN * @return true if the event is consumed, else false */ boolean onSingleTapConfirmed(MotionEvent e); /** * 當雙擊時調用. * @param e MotionEvent.ACTION_DOWN * @return true if the event is consumed, else false */ boolean onDoubleTap(MotionEvent e); /** *當兩次敲擊間回調,回調 MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP事件 * @param e The motion event that occurred during the double-tap gesture. * @return true if the event is consumed, else false */ boolean onDoubleTapEvent(MotionEvent e); }

既然知道DefaultOnDoubleTapListener實現了GestureDetector.OnDoubleTapListener接口,那末直接去看DefaultOnDoubleTapListener中是怎樣實現的。

//單擊事件 @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (this.photoViewAttacher == null) return false; ImageView imageView = photoViewAttacher.getImageView();//獲得ImageView //如果OnPhotoTapListener不為null時回調 if (null != photoViewAttacher.getOnPhotoTapListener()) { final RectF displayRect = photoViewAttacher.getDisplayRect();//獲得當前的顯示矩形 if (null != displayRect) { final float x = e.getX(), y = e.getY();//獲得第1次敲擊時的坐標 if (displayRect.contains(x, y)) {//判斷是否是敲擊在顯示矩陣內 //如果是的,就計算敲擊百分比 float xResult = (x - displayRect.left) / displayRect.width(); float yResult = (y - displayRect.top) / displayRect.height(); //敲擊圖片內回調 photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult); return true; }else{ //如果敲擊在圖片外回調 photoViewAttacher.getOnPhotoTapListener().onOutsidePhotoTap(); } } } //如果OnViewTapListener不為null時回調,不管在不在圖片里外 if (null != photoViewAttacher.getOnViewTapListener()) { photoViewAttacher.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY()); } return false; } //雙擊事件,在這里實現縮放 @Override public boolean onDoubleTap(MotionEvent ev) { if (photoViewAttacher == null) return false; try { float scale = photoViewAttacher.getScale();//獲得當前縮放比 float x = ev.getX();//獲得敲擊的坐標 float y = ev.getY();//獲得敲擊的坐標 if (scale < photoViewAttacher.getMediumScale()) { //如果之前的縮放小于中等值,現在就縮放到中等值,縮放錨點就是當前的敲擊事件坐標,true表示需要動畫縮放。 photoViewAttacher.setScale(photoViewAttacher.getMediumScale(), x, y, true); } else if (scale >= photoViewAttacher.getMediumScale() && scale < photoViewAttacher.getMaximumScale()) { //如果之前的縮放大于中等值,現在就縮放到最大值,縮放錨點就是當前的敲擊事件坐標 photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(), x, y, true); } else { //否則縮放到最小值,縮放錨點就是當前的敲擊事件坐標 photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(), x, y, true); } } catch (ArrayIndexOutOfBoundsException e) { // Can sometimes happen when getX() and getY() is called } return true; } @Override public boolean onDoubleTapEvent(MotionEvent e) { //由于不需要處理兩次敲擊間的其他事件,故這里不做處理 return false; }

從這里可以看出,在單擊時,會回調OnPhotoTapListener和OnViewTapListener,然后將坐標回調出去,如果是雙擊,則根據當前縮放比來判定現在的縮放比然后通過setScale設置縮放比和敲擊的坐標。單擊操作我們其實不怎樣關心,我們更關心雙擊的縮放操作,因而,查看setScale源碼。

@Override public void setScale(float scale, float focalX, float focalY, boolean animate) { ImageView imageView = getImageView();//獲得ImageView //.. //省略了部份源碼 //是不是需要動畫 if (animate) { imageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY)); } else { //設置給mSuppMatrix矩陣 mSuppMatrix.setScale(scale, scale, focalX, focalY); checkAndDisplayMatrix(); } } }

setScale的源碼還是比較簡單的,如果不需要動畫,直接設置給mSuppMatrix,然落后行檢查顯示。如果需要動畫的話,就履行AnimatedZoomRunnable。AnimatedZoomRunnable實現了Runnable接口,主要實現代碼以下。

private class AnimatedZoomRunnable implements Runnable { private final float mFocalX, mFocalY;//焦點 private final long mStartTime;//開始時間 private final float mZoomStart, mZoomEnd; public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) { mFocalX = focalX; mFocalY = focalY; mStartTime = System.currentTimeMillis(); mZoomStart = currentZoom; mZoomEnd = targetZoom; } @Override public void run() { ImageView imageView = getImageView(); if (imageView == null) { return; } float t = interpolate();//獲得當前的時間插值 float scale = mZoomStart + t * (mZoomEnd - mZoomStart);//根據插值,獲得當前時間的縮放值 float deltaScale = scale / getScale();//獲得縮放比,大于1表示在放大,小于1在縮小。deltaScale * getScale() = scale //回調出去,deltaScale表示相對上次要縮放的比例 onScale(deltaScale, mFocalX, mFocalY); if (t < 1f) {//插值小于1表示沒有縮放完成,通過不停post進行履行動畫 Compat.postOnAnimation(imageView, this);//Compat根據版本做了兼容處理,小于4.2用了 view.postDelayed,大于等于4.2用了view.postOnAnimation。 } } } //計算當前時間的插值 private float interpolate() { float t = 1.0F * (float)(System.currentTimeMillis() - this.mStartTime) / (float)PhotoViewAttacher.this.ZOOM_DURATION; t = Math.min(1.0F, t); t = PhotoViewAttacher.sInterpolator.getInterpolation(t); return t; } }

onScale的相干源碼以下,可以看出,調用了mSuppMatrix.postScale和checkAndDisplayMatrix()來進行顯示縮放。

@Override public void onScale(float scaleFactor, float focusX, float focusY) { if ((getScale() < mMaxScale || scaleFactor < 1f) && (getScale() > mMinScale || scaleFactor > 1f)) { if (null != mScaleChangeListener) { //監聽 mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); } //縮放 mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); checkAndDisplayMatrix(); } }

雙擊縮放中的動畫縮放的流程是這樣的,首先會記錄1個開始時間mStartTime,然后根據當前時間來獲得插值interpolate()以便了解當前應當處于的進度,根據插值求出當前的縮放值scale,然后與上次相比求出縮放比差值deltaScale,然后通過onScale回調出去,終究通過Compat.postOnAnimation來履行這個Runable,如此反復直到插值為1,縮放到目標值為止。

雙指縮放及拖拽

雙擊縮放的相干源碼到此為止,接下來看看通過雙指縮放與拖拽的實現源碼。即VersionedGestureDetector.newInstance(imageView.getContext(), this);這句。
VersionedGestureDetector看名字便知道又做了版本兼容處理。里面只有1個靜態方法newInstance,源碼以下。

//根據版本進行了控制 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) { //小于Android 2.0 detector = new CupcakeGestureDetector(context); } else if (sdkVersion < Build.VERSION_CODES.FROYO) { //小于Android 2.2 detector = new EclairGestureDetector(context); } else { detector = new FroyoGestureDetector(context); } detector.setOnGestureListener(listener); return detector; } }

newInstance中傳入了OnGestureListener,這個OnGestureListener是自定義的接口,源碼以下。

public interface OnGestureListener { //拖拽時回調 void onDrag(float dx, float dy); //Fling時回調 void onFling(float startX, float startY, float velocityX, float velocityY); //縮放時回調,`onScale`在雙擊動畫縮放時已介紹過了,scaleFactor表示相對上次的縮放比 void onScale(float scaleFactor, float focusX, float focusY); }

可以看出,回調了縮放、Fling和拖拽3種情況。現在我們回到newInstance相干源碼,可以看出有3種探測器CupcakeGestureDetector、EclairGestureDetector和FroyoGestureDetector。且3者是相互繼承的關系,FroyoGestureDetector繼承于EclairGestureDetector,EclairGestureDetector繼承于CupcakeGestureDetector。
其中CupcakeGestureDetector和EclairGestureDetector不支持雙指縮放。由于Android2.0以下不支持多點觸控,因而CupcakeGestureDetector核心源碼以下:

float getActiveX(MotionEvent ev) { return ev.getX(); } float getActiveY(MotionEvent ev) { return ev.getY(); } @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: { final float x = getActiveX(ev); final float y = getActiveY(ev); final float dx = x - mLastTouchX, dy = y - mLastTouchY; if (!mIsDragging) { //如果手指移動的距離大于mTouchSlop,表示在拖拽 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;
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 久久精品视频9 | 国内成人精品亚洲日本语音 | 波多野结衣福利视频 | 欧美久久xxxxxx影院 | 久久久久久国产精品免费 | 日本久久精品免视看国产成人 | 日本亚州在线播放精品 | 国产欧美自拍视频 | 伊人久久大香 | 成人gav| 国产中的精品suv一区二区 | 国产精品久久99 | 久久欧美精品欧美久久欧美 | 亚洲图区综合 | 亚洲精品自拍愉拍第二页 | 综合欧美亚洲 | 国产精品久久精品 | 日韩中文字幕视频在线观看 | 国产在线观看成人免费视频 | 日本特黄特色大片免费播放视频 | 亚洲精品乱码久久久久久蜜桃 | 国产亚洲精品久久久久久午夜 | 亚洲国产成人久久笫一页 | 精品国产96亚洲一区二区三区 | 日韩理伦片秋霞理伦 | 手机看片久久高清国产日韩 | 国产精品欧美日韩一区二区 | 亚洲人成网站观看在线观看 | 成人网在线看 | 国产精品二区页在线播放 | 精品国产一区二区三区2021 | 国产成人精品.一二区 | 日韩免费一级片 | 日韩一二区 | 99爱视频99爱在线观看免费 | 午夜羞羞视频在线观看 | 成人欧美视频在线观看播放 | 亚洲人在线观看 | 国产乱人乱精一区二区视频密 | c看欧美激情毛片 | 永久免费网站 |