@archeryc
2016-06-02T04:21:05.000000Z
字数 4203
阅读 2160
源码
对于一个ImageView或者说View来说,view的大小是不变的,那图片如何实现大小的变化呢?答案就是Matrix。
Matrix的定义:字面意思是矩阵,在Android中表示使用矩阵的方法来对图片进行变换。可以略微想象一下,图片有很多个像素点,每个像素点都有它的值,将图片看作一个矩阵,那么对图片进行旋转,缩放实际上就是对矩阵进行变换。Android对这个变换过程封装进了Matrix类。
在PhotoView中,ImageView(PhotoView继承于ImageView)的大小等是不变的,变化的是里面的drawable。
PhotoView:继承于ImageView 实现了IPhotoView接口。
IPhotoView接口:与PhotoView相关的接口。涉及到手势事件的监听和图像的变换。
PhotoViewAttacher:实现了IPhotoView,OnTouchListener等事件的接口。是PhotoView中接口的具体实现类。
PhotoView初始化的时候干了三件事:
添加监听通常是在初始化时完成的,去PhotoView和PhotoViewAttacher的构造函数里找找看。最终在PhotoViewAttacher的构造函数中找到了:
mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
mGestureDetector负责从onTouch中把MotionEvent传递给DefaultOnDoubleTapListener。
if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
handled = true;
}
总而言之,DefaultOnDoubleTapListener是通过如上的方式接收到View的MotionEvent的,但这不是重点,重点是在DefaultOnDoubleTapListener中如何处理这个事件。代码其实很少:
@Override
public boolean onDoubleTap(MotionEvent ev) {
if (photoViewAttacher == null)
return false;
try {
float scale = photoViewAttacher.getScale();//获取当前缩放值
float x = ev.getX();
float y = ev.getY();
//PhotoView预定义了3种缩放大小,大,中,小,可以实际使用感受下,双击后先缩放到中,再双击缩放到大,再双击缩放到小。
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;
}
不管是当前是哪种缩放大小,都调用了PhotoViewAttacher的setScale方法,看看这个方法:
@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) {//双击缩放都是有动画的,所以调用的是这个方法
imageView.post(new AnimatedZoomRunnable(getScale(), scale,
focalX, focalY));
//动画的具体实现是通过不停地调用一个runnable,直到到达动画的duration,具体实现可以自己去看下。
} else {
//没有缩放动画就直接setScale
mSuppMatrix.setScale(scale, scale, focalX, focalY);
checkAndDisplayMatrix();//这个方法很关键,如果你自己写过双击缩放,会发现有时缩放后会把View的背景露出来,也就是drawable没有贴合在view的边缘
}
}
}
private void checkAndDisplayMatrix() {
if (checkMatrixBounds()) {//这个就是我们说的关键方法
setImageViewMatrix(getDrawMatrix());//这里就是把mDrawMatrix设置进PhotoView中
}
}
/**
* 对drawable进行平移使其贴合边界
*
* @return
*/
private boolean checkMatrixBounds() {
final ImageView imageView = getImageView();
if (null == imageView) {
return false;
}
//这个方法获取在matrix变换后的drawable矩阵
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) {//贴合的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;
}
// 根据计算出的delta去进行平移,这边的逻辑还是比较简单的
mSuppMatrix.postTranslate(deltaX, deltaY);
return true;
}
至此,一次双击事件的处理就完成了,其他的手势操作都类似。PhotoView的核心是Matrix,如果把其中Matrix的变化过程和对细节的处理理解了,那么手势事件的处理就游刃有余了。