关闭

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

690人阅读 评论(0) 收藏 举报
分类:

核心

对于一个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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:4657次
    • 积分:162
    • 等级:
    • 排名:千里之外
    • 原创:12篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类