PhotoView源码分析(2)

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,否则会异常。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值