1 PhotoViewAttacher
代码较多,省去不重要的
public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener,
OnGestureListener, ViewTreeObserver.OnGlobalLayoutListener {
// 变量定义及初始化
static final Interpolator sInterpolator = new AccelerateDecelerateInterpolator();
int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;
static final int EDGE_NONE = -1;
static final int EDGE_LEFT = 0;
static final int EDGE_RIGHT = 1;
static final int EDGE_BOTH = 2;
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;
/**
* @return true if the ImageView exists, and it's Drawable existss
*/
private static boolean hasDrawable(ImageView imageView) {
return null != imageView && null != imageView.getDrawable();
}
private WeakReference<ImageView> mImageView;
// Gesture Detectors
private GestureDetector mGestureDetector; // 单机双击等
private uk.co.senab.photoview.gestures.GestureDetector mScaleDragDetector; // 算法
// These are set so we don't keep allocating them on the heap
private final Matrix mBaseMatrix = new Matrix(); // 初始化后基本不变了
private final Matrix mDrawMatrix = new Matrix(); // 最终变换使用的,由上下两个合成而来
private final Matrix mSuppMatrix = new Matrix(); // 存储需要变换的值
private final RectF mDisplayRect = new RectF();
private final float[] mMatrixValues = new float[9];
// Listeners
private OnMatrixChangedListener mMatrixChangeListener;
private OnPhotoTapListener mPhotoTapListener; // 点击到了图片上
private OnViewTapListener mViewTapListener; // 点击到了view上
private OnLongClickListener mLongClickListener;
private OnScaleChangeListener mScaleChangeListener;
private int mIvTop, mIvRight, mIvBottom, mIvLeft;
private FlingRunnable mCurrentFlingRunnable; // Fling
private int mScrollEdge = EDGE_BOTH; // 边界类型
private boolean mZoomEnabled;
private ScaleType mScaleType = ScaleType.FIT_CENTER; // 默认的类型!!!
public PhotoViewAttacher(ImageView imageView) {
this(imageView, true);
}
@SuppressLint("NewApi")
public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
mImageView = new WeakReference<ImageView>(imageView); //store
// true to enable the drawing cache, false otherwise
imageView.setDrawingCacheEnabled(true); // 后面要用cache
imageView.setOnTouchListener(this);
ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer)
observer.addOnGlobalLayoutListener(this);
// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);
// True if this View is in edit mode, false otherwise.
if (imageView.isInEditMode()) {
return;
}
// Create Gesture Detectors...
// 其实只是一个计算方法的集合,回调到这,和GestureDetector不一样,GestureDetector是系统提供的
mScaleDragDetector = VersionedGestureDetector.newInstance(
imageView.getContext(), this);
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);
}
/**
* Clean-up the resources attached to this object. This needs to be called when the ImageView is
* no longer used. A good example is from {@link android.view.View#onDetachedFromWindow()} or
* from {@link android.app.Activity#onDestroy()}. This is automatically called if you are using
* {@link uk.co.senab.photoview.PhotoView}.
*/
@SuppressWarnings("deprecation")
public void cleanup() {
// 最好在onDetachedFromWindow()或onDestroy()中调用这个方法
// 而PhotoView已经自动完成了
// 清空所有的回调,变量也置空
if (null == mImageView) {
return; // cleanup already done
}
final ImageView imageView = mImageView.get();
if (null != imageView) {
// Remove this as a global layout listener
ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer && observer.isAlive()) {
observer.removeGlobalOnLayoutListener(this);
}
// Remove the ImageView's reference to this
imageView.setOnTouchListener(null);
// make sure a pending fling runnable won't be run
cancelFling();
}
if (null != mGestureDetector) {
mGestureDetector.setOnDoubleTapListener(null);
}
// Clear listeners too
mMatrixChangeListener = null;
mPhotoTapListener = null;
mViewTapListener = null;
// Finally, clear ImageView
mImageView = null;
}
@Override
public RectF getDisplayRect() { // 获得当前Imageview中内容的显示区域
checkMatrixBounds();// 检测范围并调整,不能出现空隙
return getDisplayRect(getDrawMatrix()); // 根据最新的matrix返回Rect
}
@Override
public void setRotationTo(float degrees) {
mSuppMatrix.setRotate(degrees % 360);
checkAndDisplayMatrix();
}
@Override
public void setRotationBy(float degrees) {
mSuppMatrix.postRotate(degrees % 360);
checkAndDisplayMatrix();
}
public ImageView getImageView() {
ImageView imageView = null;
if (null != mImageView) {
imageView = mImageView.get(); // 从弱引用中拿取
}
// If we don't have an ImageView, call cleanup()
if (null == imageView) {
cleanup();
}
return imageView;
}
@Override
public float getScale() { // Math.pow幂
return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
}
// 底层回调过来的,表示手指进行了拖动
@Override
public void onDrag(float dx, float dy) {
if (mScaleDragDetector.isScaling()) {
return; // Do not drag if we are already scaling
}
ImageView imageView = getImageView();
mSuppMatrix.postTranslate(dx, dy); // 变换量添加
checkAndDisplayMatrix();
// very good! good idea!
/**
* Here we decide whether to let the ImageView's parent to start taking
* over the touch event.
*
* First we check whether this function is enabled. We never want the
* parent to take over if we're scaling. We then check the edge we're
* on, and the direction of the scroll (i.e. if we're pulling against
* the edge, aka 'overscrolling', let the parent take over).
*/
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) // 这样便可以进行viewpager等的滑动了
parent.requestDisallowInterceptTouchEvent(false); // 允许阻断事件向下传递
}
} else {
if (null != parent) {
parent.requestDisallowInterceptTouchEvent(true); // 不允许阻断,必须传给我
}
}
}
// 底层回调过来的
@Override
public void onFling(float startX, float startY, float velocityX,
float velocityY) {
ImageView imageView = getImageView();
mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
mCurrentFlingRunnable.fling(getImageViewWidth(imageView),// 启动scroll的Fling方法
getImageViewHeight(imageView), (int) velocityX, (int) velocityY);
imageView.post(mCurrentFlingRunnable);
}
@Override
public void onGlobalLayout() { // positon is ok
ImageView imageView = getImageView();
if (null != imageView) {
if (mZoomEnabled) {
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());
}
}
}
// 底层回调过来的
@Override
public void onScale(float scaleFactor, float focusX, float focusY) {
if (getScale() < mMaxScale || scaleFactor < 1f) {
if (null != mScaleChangeListener) {
mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
}
// 添加至mSuppMatrix
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
checkAndDisplayMatrix(); // 更新matrix并应用显示
}
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
boolean handled = false; // for return
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) { // 重要
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:
// If the user has zoomed less than min scale, zoom back to min scale
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();
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;
}
@Override
public void setScale(float scale, boolean animate) {// animate是否渐变
ImageView imageView = getImageView();
if (null != imageView) {
setScale(scale,
(imageView.getRight()) / 2,
(imageView.getBottom()) / 2,
animate);
}
}
@Override
public void setScale(float scale, float focalX, float focalY,
boolean animate) {
ImageView imageView = getImageView();
if (null != imageView) {
// Check to see if the scale is within bounds
if (scale < mMinScale || scale > mMaxScale) {
return;
}
if (animate) { // 渐变则使用run实现
imageView.post(new AnimatedZoomRunnable(getScale(), scale,
focalX, focalY));
} else { // 否则直接完成
mSuppMatrix.setScale(scale, scale, focalX, focalY);
checkAndDisplayMatrix();
}
}
}
public void update() {
ImageView imageView = getImageView();
if (null != imageView) {
if (mZoomEnabled) {
// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);
// Update the base matrix using the current drawable
updateBaseMatrix(imageView.getDrawable());
} else {
// Reset the Matrix...
resetMatrix();
}
}
}
// 获得当前的矩阵
public Matrix getDrawMatrix() {
mDrawMatrix.set(mBaseMatrix);
mDrawMatrix.postConcat(mSuppMatrix);
// mDrawMatrix的设置,先进行了mBaseMatrix变换,而后进行mSuppMatrix的变换
// postTranslate原理一样吧
return mDrawMatrix;
}
private void cancelFling() {
if (null != mCurrentFlingRunnable) {
mCurrentFlingRunnable.cancelFling();
mCurrentFlingRunnable = null;
}
}
/**
* Helper method that simply checks the Matrix, and then displays the result
*/
private void checkAndDisplayMatrix() { // 检测并设定
if (checkMatrixBounds()) {
setImageViewMatrix(getDrawMatrix());
}
}
private void checkImageViewScaleType() {
ImageView imageView = getImageView();
/**
* PhotoView's getScaleType() will just divert to this.getScaleType() so
* only call if we're not attached to a PhotoView.
*/
if (null != imageView && !(imageView instanceof IPhotoView)) {
// 必须是Matrix
if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
throw new IllegalStateException(
"The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher");
}
}
}
// 检测范围并调整,不能出现空隙,变换了矩阵,偏移值添加至mSuppMatrix
private boolean checkMatrixBounds() {
final ImageView imageView = getImageView();
if (null == imageView) {
return false;
}
// 获得显示区域的矩形范围,或Imageview的内容显示范围
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:
// 居中:- rect.top可以先让其居顶,而后(viewHeight - height) / 2让其居中
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供以后使用
mSuppMatrix.postTranslate(deltaX, deltaY);
return true;
}
/**
* Helper method that maps the supplied Matrix to the current Drawable
*
* @param matrix - Matrix to map Drawable against
* @return RectF - Displayed Rectangle
*/
private RectF getDisplayRect(Matrix matrix) {// 根据矩阵获得显示内容的范围
ImageView imageView = getImageView();
if (null != imageView) {
// 图片的Drawable是恒定不变,每次通过matrix变换后,改变了显示范围等,将这个变化的结果内容显示出来而已
Drawable d = imageView.getDrawable();
if (null != d) {
mDisplayRect.set(0, 0, d.getIntrinsicWidth(),
d.getIntrinsicHeight()); // mDisplayRect设定为Drawable的大小范围
// Apply this matrix to the rectangle对矩形进行matrix变换
// 这样一来,变换后的矩形就和现在显示的图形,范围上是一致的了
matrix.mapRect(mDisplayRect);
return mDisplayRect;
}
}
return null;
}
public Bitmap getVisibleRectangleBitmap() { // 构造中设置的cache使能
ImageView imageView = getImageView();
return imageView == null ? null : imageView.getDrawingCache();
}
/**
* Helper method that 'unpacks' a Matrix and returns the required value
*
* @param matrix - Matrix to unpack
* @param whichValue - Which value from Matrix.M* to return
* @return float - returned value
*/
private float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
/**
* Resets the Matrix back to FIT_CENTER, and then displays it.s
*/
private void resetMatrix() {
mSuppMatrix.reset();
setImageViewMatrix(getDrawMatrix());
checkMatrixBounds();
}
private void setImageViewMatrix(Matrix matrix) {
ImageView imageView = getImageView();
if (null != imageView) {
checkImageViewScaleType();
imageView.setImageMatrix(matrix);
// Call MatrixChangedListener if needed
if (null != mMatrixChangeListener) { // 回调
RectF displayRect = getDisplayRect(matrix);
if (null != displayRect) {
mMatrixChangeListener.onMatrixChanged(displayRect);
}
}
}
}
/**
* Calculate Matrix for FIT_CENTER
*
* @param d - Drawable being displayed
*/
private void updateBaseMatrix(Drawable d) { // for BaseMatrix
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) {
mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
(viewHeight - drawableHeight) / 2F);
} else if (mScaleType == ScaleType.CENTER_CROP) {
// CENTER_CROP需要填满Imageview,所以取大
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) {
// CENTER_INSIDE需要内容完整显示,所以取小,并且CENTER_INSIDE不能放大哦,即不可大于1
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();
}
private int getImageViewWidth(ImageView imageView) {
if (null == imageView)
return 0;
return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
}
private int getImageViewHeight(ImageView imageView) {
if (null == imageView)
return 0;
return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
}
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; // 缩放中心x
mFocalY = focalY; // 缩放中心y
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();
onScale(deltaScale, mFocalX, mFocalY);
// We haven't hit our target scale yet, so post ourselves again
if (t < 1f) {
Compat.postOnAnimation(imageView, this); // 隔断时间再次执行run
}
}
private float interpolate() {
// 获得比例时间t: 0---1
float t = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION;
t = Math.min(1f, t); // 确保不大于1
t = sInterpolator.getInterpolation(t);//根据差值器计算出输出值,有个变化效果,什么时候快慢
return t;
}
}
private class FlingRunnable implements Runnable {
private final ScrollerProxy mScroller;
private int mCurrentX, mCurrentY;
public FlingRunnable(Context context) {
mScroller = ScrollerProxy.getScroller(context);
}
public void cancelFling() {
mScroller.forceFinished(true);
}
public void fling(int viewWidth, int viewHeight, int velocityX,
int velocityY) {
// 假设手向左滑动了,那么内容区域应该也向左Fling,scroll的速度应该大于0
// 这样内容才会向左移动
final RectF rect = getDisplayRect(); // 获得当前Imageview中内容的显示区域
if (null == rect) {
return;
}
// 这个值不用纠结,需要结合maxX才有意义
final int startX = Math.round(-rect.left);
final int minX, maxX, minY, maxY;
if (viewWidth < rect.width()) {
minX = 0;
maxX = Math.round(rect.width() - viewWidth);
// 移动的最大范围:maxX - startX = rect.width() - viewWidth + rect.left
// 因为内容区域大,所以rect.left <= 0的。自己画图更容易理解。
// 因此,只要结果是rect.width() - viewWidth + rect.left,那么maxX和startX是多少没关系
} else {
minX = maxX = startX;
}
final int startY = Math.round(-rect.top);
if (viewHeight < rect.height()) {
minY = 0;
maxY = Math.round(rect.height() - viewHeight);
} else {
minY = maxY = startY;
}
mCurrentX = startX;
mCurrentY = startY;
// If we actually can move, fling the scroller
if (startX != maxX || startY != maxY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX,
maxX, minY, maxY, 0, 0);
// 注意,不是每次结果都会移动到最大的值的,这和速度有关系了
// velocityX足够大,那么移动的变化量是可以达到rect.width() - viewWidth + rect.left
// 否则变化量只会小于rect.width() - viewWidth + rect.left了
}
}
@Override
public void run() {
if (mScroller.isFinished()) {
return; // remaining post that should not be handled
}
ImageView imageView = getImageView();
if (null != imageView && mScroller.computeScrollOffset()) {
final int newX = mScroller.getCurrX();
final int newY = mScroller.getCurrY();
// 假设手向左滑动了,那么内容区域应该也向左Fling,此时scroll的值是变大的。
// 可惜scroll只是变化过程的计算者,执行者是matrix。
// 而要让内容左移,那么matrix的值需要小于0才行,因此mCurrentX - newX
// 如果Fling传入的速度和现在思路相反,那就可以newX -mCurrentX。
mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
setImageViewMatrix(getDrawMatrix());
mCurrentX = newX;
mCurrentY = newY;
// Post On animation 循环执行这个run
Compat.postOnAnimation(imageView, this);
}
}
}
}
2 DefaultOnDoubleTapListener
上面设置的多击事件
public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (this.photoViewAttacher == null)
return false;
ImageView imageView = photoViewAttacher.getImageView();
if (null != photoViewAttacher.getOnPhotoTapListener()) {
final RectF displayRect = photoViewAttacher.getDisplayRect();
if (null != displayRect) {
final float x = e.getX(), y = e.getY();
// Check to see if the user tapped on the photo
if (displayRect.contains(x, y)) {
float xResult = (x - displayRect.left) / displayRect.width(); // smart
float yResult = (y - displayRect.top) / displayRect.height();
photoViewAttacher.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult);
return true;
}
}
}
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()) {
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) {
// Wait for the confirmed onDoubleTap() instead
return false;
}
}
小结
通过上面源码,可以找到Photoview的各种事件处理流程,比如单机,双击,拖动,Fling,缩放,以及对父view的事件处理机制等等,看懂了上面代码,相信以后的触摸事件处理会更上手了。
关于scale_type:可以看出,Imageview总是设置为Matrix的,在xml中配置为其他了,这里也会修改;
但是代码中却支持其他类型,原因就是Imageview依然是Matrix的,但PhotoViewAttacher中内部会维护一个用户希望的scaleType类型,在进行base矩阵初始化时updateBaseMatrix,会根据内部的类型去动态改变显示的位置等。并且在checkMatrixBounds中,也会动态去改变调整其范围。
总之,用matrix去实现了用户希望的scaletype,但是scaleType需要设定在PhotoViewAttacher中才有效果,
即用户调用setScaleType(ScaleType scaleType)才行,否则内部默认是FitCenter,而Imageview一直为matrix。
不过注意setScaleType(ScaleType scaleType)不要传入matrix,否则会异常。