支持多种手势的图片开源库photoView简析

代码地址https://github.com/chrisbanes/PhotoView
photoView 一般用在照片上,从名字就看出来了,在github上还看到功能类似的touchImageView,实现原理差不多,不过从demo上来看,photoview在性能上貌似更好一点,花了一下午时间看了下,自己做个记录呗。

看下构造

public PhotoView(Context context, AttributeSet attr, int        defStyle) {
        super(context, attr, defStyle);
        super.setScaleType(ScaleType.MATRIX);
        mAttacher = new PhotoViewAttacher(this);

        if (null != mPendingScaleType) {
            setScaleType(mPendingScaleType);
            mPendingScaleType = null;
        }
    }
继承的ImageView,里面比较重要的就是PhotoViewAttacher这个类,初始化的时候将imageview传了进去。再看下photoViewAttacher的构造
public PhotoViewAttacher(ImageView imageView) {
        mImageView = new WeakReference<ImageView>(imageView);

        imageView.setOnTouchListener(this);

        mViewTreeObserver = imageView.getViewTreeObserver();
        mViewTreeObserver.addOnGlobalLayoutListener(this);

        // Make sure we using MATRIX Scale Type
        setImageViewScaleTypeMatrix(imageView);

        if (!imageView.isInEditMode()) {
            // Create Gesture Detectors...
            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(mImageView.get());
                            }
                        }
                    });

            mGestureDetector.setOnDoubleTapListener(this);

            // Finally, update the UI so that we're zoomable
            setZoomable(true);
        }

可以看到imageView.setOnTouchListener(this);设置了touchListener,后续的触摸事件入口就在这里,后续会说下它的touch事件的派发;后初始化了手势监听器用来处理手势事件。下面是它的入口ontouch方法

@Override
    public boolean onTouch(View v, MotionEvent ev) {
        boolean handled = false;

        if (mZoomEnabled && hasDrawable((ImageView) v)) {
            ViewParent parent = v.getParent();
        switch (ev.getAction()) {
            case ACTION_DOWN:
                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;
}
    这里我们的操作可能有多种:俩指缩放;放大单手的移动图片;双击局部放大;快速滑动的滚动效果等。作为入口,大致理一下它的事件传递流程:
    首先down事件,最重要的就是cancelFling();如果图片正在快速滑动,取消该快滑。接着将我们的motionEvent传递进手势监听器
    handled = mScaleDragDetector.onTouchEvent(ev);
    mScaleDragDetector这个监听器是个封装过得监听器,下面的代码可以看出不同版本用了不同的Detector,但是之间是继承关系。

    public static GestureDetector newInstance(Context context,OnGestureListener listener) {
    final int sdkVersion = Build.VERSION.SDK_INT;
    GestureDetector detector;

    if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
        detector = new CupcakeGestureDetector(context);
    } else if (sdkVersion < Build.VERSION_CODES.FROYO) {
        detector = new EclairGestureDetector(context);
    } else {
        detector = new FroyoGestureDetector(context);
    }
    detector.setOnGestureListener(listener);
    return detector;

因为我的测试机是4.1 所以被传递到FroyoGestureDetector的onTouchEvent方法

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDetector.onTouchEvent(ev);
        return super.onTouchEvent(ev);
    }
mDetector是在该类定义的缩放的监听器,此时将MotionEvent 也传递进该监听器中,它的定义如下:缩放时会将事件回调到PhotoViewAttacher中

ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float scaleFactor = detector.getScaleFactor();

            if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
                return false;

            mListener.onScale(scaleFactor,
                    detector.getFocusX(), detector.getFocusY());
            return true;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            // NO-OP
        }
    };
    mDetector = new ScaleGestureDetector(context, mScaleListener);
,继续发现还return super.onTouchEvent(ev);进去一看,发现这里才是预处理了drag和flip事件。

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
mVelocityTracker = VelocityTracker.obtain();
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
} else {
LogManager.getLogger().i(LOG_TAG, “Velocity tracker is null”);
}

            mLastTouchX = getActiveX(ev);
            mLastTouchY = getActiveY(ev);
            mIsDragging = false;
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            final float x = getActiveX(ev);
            final float y = getActiveY(ev);
            final float dx = x - mLastTouchX, dy = y - mLastTouchY;

            if (!mIsDragging) {
                // Use Pythagoras to see if drag length is larger than
                // touch slop
                mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
            }

            if (mIsDragging) {
                mListener.onDrag(dx, dy);
                mLastTouchX = x;
                mLastTouchY = y;

                if (null != mVelocityTracker) {
                    mVelocityTracker.addMovement(ev);
                }
            }
            break;
        }

        case MotionEvent.ACTION_CANCEL: {
            // Recycle Velocity Tracker
            if (null != mVelocityTracker) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
            break;
        }

        case MotionEvent.ACTION_UP: {
            if (mIsDragging) {
                if (null != mVelocityTracker) {
                    mLastTouchX = getActiveX(ev);
                    mLastTouchY = getActiveY(ev);

                    // Compute velocity within the last 1000ms
                    mVelocityTracker.addMovement(ev);
                    mVelocityTracker.computeCurrentVelocity(1000);

                    final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
                            .getYVelocity();

                    // If the velocity is greater than minVelocity, call
                    // listener
                    if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
                        mListener.onFling(mLastTouchX, mLastTouchY, -vX,
                                -vY);
                    }
                }
            }

            // Recycle Velocity Tracker
            if (null != mVelocityTracker) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
            break;
        }
我们回到attacher中,分析其中一个:

if (mScaleDragDetector.isScaling()) {
return; // Do not drag if we are already scaling
}
if (DEBUG) {
LogManager.getLogger().d(LOG_TAG,
String.format(“onDrag: dx: %.2f. dy: %.2f”, dx, dy));
}

    ImageView imageView = getImageView();
    mSuppMatrix.postTranslate(dx, dy);
    checkAndDisplayMatrix();

mSuppMatrix是该变换的矩阵,不太明白的度娘Matrix,一大堆资料,看下checkAndDisplayMatrix() ,该方法中会检查边界,通过之后会设置变化的矩阵。


private void checkAndDisplayMatrix() {
if (checkMatrixBounds()) {
setImageViewMatrix(getDrawMatrix());
}
}
第一次分析源码库,打字太累,还有好多细节并没有说道,有啥说错的欢迎指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值