理解PhotoView的核心,以双击事件为例

来源:互联网 发布:java随机数生成扑克牌 编辑:程序博客网 时间:2024/06/06 19:19

核心

对于一个ImageView或者说View来说,view的大小是不变的,那图片如何实现大小的变化呢?答案就是Matrix
Matrix的定义:字面意思是矩阵,在Android中表示使用矩阵的方法来对图片进行变换。可以略微想象一下,图片有很多个像素点,每个像素点都有它的值,将图片看作一个矩阵,那么对图片进行旋转,缩放实际上就是对矩阵进行变换。Android对这个变换过程封装进了Matrix类
在PhotoView中,ImageView(PhotoView继承于ImageView)的大小等是不变的,变化的是里面的drawable

整体结构

PhotoView:继承于ImageView 实现了IPhotoView接口。
IPhotoView接口:与PhotoView相关的接口。涉及到手势事件的监听和图像的变换。
PhotoViewAttacher:实现了IPhotoView,OnTouchListener等事件的接口。是PhotoView中接口的具体实现类

PhotoView中的几个Matrix

  • mBaseMatrix 基础的matrix,图片根据ScaleType来决定最初的大小,在初始化完成之后就基本不会改变,除非布局发生了变化。
  • mSuppMatrix 由于mBaseMatrix在初始化后基本是不变的(布局发生变化的时候会重置mBaseMatrix),因此需要另外一个matrix来记录这些变化(比如说双击事件后缩放啊什么的),然后通过mBaseMatrix的右乘来将变化加进去。
  • mDrawMatrix mBaseMatrix右乘mSuppMatrix,实际上就是存储所有变化的matrix,最后set进imageView的就是这个

具体看代码流程

PhotoView初始化的时候干了三件事:

  • 绑定PhotoViewAttacher对象
  • 根据ScaleType确定初始Matrix,也就是mBaseMatrix。这里注意,进行Matrix变换必须将ScaleType设置为Matix。那表示我们设置ScaleType就没有效果了吗?其实,PhotoView在初始化时会先记录下当前设置的ScaleType,只有在需要进行变换的时候才会将ScaleType设置成Matrix。
  • 设置各种监听,Drag,手势,DoubleClick等。

一个手势的具体运作流程,以双击事件为例

添加监听通常是在初始化时完成的,去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的变化过程和对细节的处理理解了,那么手势事件的处理就游刃有余了。

PhotoView源码解析

0 0
原创粉丝点击