本文参考自http://www.tuicool.com/articles/ea2ANjm
简介
PhotoView属性:
可以用于查看图片,并对图片进行拖动缩放,拖动过程中不会出现边缘空白;
双击缩小放大,Fling移动,并支持上述过程的渐变;
在放大情况下也支持viewpager等的拖动切换;
支持多击事件检测,单机,双击事件;
支持各种回调给调用者;
具体的调用方法如下
//将imageview和PhotoViewAttacher 这个控制器关联起来 mAttacher = new PhotoViewAttacher(mImageView);可以看出来 主要的工作都是在这个PhotoViewAttacher里做的。
public PhotoViewAttacher(ImageView imageView) { //使用软引用,防止内存泄露 mImageView = new WeakReference<ImageView>(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; } //根据版本不同 取得需要的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(true); }
接下来我们来看看 addOnGlobalLayoutListener这个接听里做了什么,接口中有onGlobalLayout方法
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()); } } }然后我们跟到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();//这个是取原始图片大小的 永远不会变化的 final int drawableHeight = d.getIntrinsicHeight(); mBaseMatrix.reset(); final float widthScale = viewWidth / drawableWidth; final float heightScale = viewHeight / drawableHeight; if (mScaleType == ScaleType.CENTER) { //根据传进去的scaletype的值来确定 基础的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(); }进入checkMatrixBounds()中看看
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的一个转换。
接着回到PhotoViewAttacher的构造方法,我们看到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的版本号不同 提供不一样的功能,接着我们看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; } //这个函数返回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); } }