PhotoView源码分析(1)

简介

PhotoView属性:
可以用于查看图片,并对图片进行拖动缩放,拖动过程中不会出现边缘空白;
双击缩小放大,Fling移动,并支持上述过程的渐变;
在放大情况下也支持viewpager等的拖动切换;
支持多击事件检测,单机,双击事件;
支持各种回调给调用者;

准备知识

Log

public int v(String tag, String msg, Throwable tr) {
    // tr: An exception to log
    return Log.v(tag, msg, tr);
}

    // let debug flag be dynamic, but still Proguard can be used to remove from
    // release builds
    private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);

ScrollerProxy代理类

抽象类,里面根据系统版本会提供对应的Scroller对象

public static ScrollerProxy getScroller(Context context) {
        if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
            return new PreGingerScroller(context);
        } else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
            return new GingerScroller(context);
        } else {
            return new IcsScroller(context);
        }
}

9 2.3
PreGingerScroller:
Scroller mScroller = new Scroller(context);

14 4.0
GingerScroller:

mScroller = new OverScroller(context);
    public boolean computeScrollOffset() {
        // Workaround for first scroll returning 0 for the direction of the edge it hits.
        // Simply recompute values. // Workaround : 工作区
        if (mFirstScroll) { // 暂时没看到设置为true的地方
            mScroller.computeScrollOffset();
            mFirstScroll = false;
        }
        return mScroller.computeScrollOffset();
    }

4.0+
IcsScroller: extends GingerScroller

 public boolean computeScrollOffset() {
        return mScroller.computeScrollOffset();
 }

VersionedGestureDetector 手势检测

根据系统版本返回对应的手势检测算法对象, 返回的对象都实现了自定义的GestureDetector接口,可见,其自身只是维护了手势检测的算法,需要真正的使用者传参数过来调用的。
mListener.onDrag(dx, dy);和mListener.onFling(mLastTouchX, mLastTouchY, -vX, -vY);以及
onScale(float scaleFactor, float focusX, float focusY);会回调出去的

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;
}

版本 5 2点0 CupcakeGestureDetector

onDrag和onFling都在此处回调

    public CupcakeGestureDetector(Context context) {
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        // Minimum velocity to initiate a fling, as measured in pixels per second.
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        // Distance in pixels a touch can wander before we think the user is scrolling
        mTouchSlop = configuration.getScaledTouchSlop();
    }

    public boolean isScaling() { // !!!
        return false;
    }

    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                // Helper for tracking the velocity of touch events
                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 // Pythagoras:勾股定理
                    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);
                            // 注意传过去的速度值的符号!假设手向左滑动了,那么内容区域应该也向左Fling
                            // 此时传过去的值为-vX为正数,而回调出去的使用者利用的是Scroll,因此-vX
                            // 是合理的,为正数的话,内容区域才会scroll左边的。
                        }
                    }
                }

                // Recycle Velocity Tracker
                if (null != mVelocityTracker) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
            }
        }
        return true;
    }

版本 8 2点2 EclairGestureDetector

继承:EclairGestureDetector extends CupcakeGestureDetector
处理了多手指情况下getActiveXY的值

    private int mActivePointerIndex = 0;
    float getActiveX(MotionEvent ev) {
        try {
            return ev.getX(mActivePointerIndex);
        } catch (Exception e) {
            return ev.getX();
        }
    }
    float getActiveY(MotionEvent ev) {
        try {
            return ev.getY(mActivePointerIndex);
        } catch (Exception e) {
            return ev.getY();
        }
    }

onTouchEvent事件,说明:
1 a手指按下触发down,之后b手指按下没有down,然后b手指抬起,触发ACTION_POINTER_UP,接着a手指抬起触发ACTION_UP;如果a手指先抬起,b后抬起,触发顺序一样,最后还是ACTION_UP,当存在三个以上手指时,效果还是一样的,即多次ACTION_POINTER_UP,最后是ACTION_UP。

2 pointerId的规则:当多个手指依次按下时,他们的顺序编号为0,1,2等等此时如果0抬起了,之后有按下一个手指,那么最新按的那个手指编号是0,即编号是重复利用的,并且每次从0开始检测,如果没有手指使用,那么就给当前手指了,否则就查找下一个编号。ID是按下时确定的,之后便不变了。

3 pointerIndex的规则:根据当前按下的手指ID大小排序,计算出pointerIndex的大小,比如按下了三个手指,之后中间那个1抬起了,那么第三个手指的ID还是2,但是它的index变成了1. INDEX是手指个数发生变化时,根据ID排序重新计算出来的。比如如果第一个手指抬起了,那么之后的所有手指的index都会减1.

    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                // 当有多手指按下时,只会响应第一个手指按下,其余不响应,并且只有手指全部抬起时才会再次进入这里
                mActivePointerId = ev.getPointerId(0);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // 最后一个手指抬起时会调用
                mActivePointerId = INVALID_POINTER_ID;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                // deprecation 弃用,反对。注释意思是说Compat里面的过期变量的
                // Ignore deprecation, ACTION_POINTER_ID_MASK and
                // ACTION_POINTER_ID_SHIFT has same value and are deprecated
                // You can have either deprecation or lint target api warning
                // 根据action找到当前手指的index值
                final int pointerIndex = Compat.getPointerIndex(ev.getAction());
                // 根据index值找到当前手指的ID值
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) { // 比较是否是设定的那个有效的ID手指,因为ID是不变的
                    // This was our active pointer going up. Choose a new
                    // active pointer and adjust accordingly.
                    // pointerIndex的值肯定是从0开始的,因此如果当前是0,那么就以下一个点作为
                    // 基准,否则,以index为0的点为基准,而这个点有可能是第一个手指抬起后,最新
                    // 按下的那个手指,因为ID是重复使用的,index是根据id排出来的。
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                    mLastTouchX = ev.getX(newPointerIndex);
                    mLastTouchY = ev.getY(newPointerIndex);
                }
                break;
        }
        mActivePointerIndex = ev.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId: 0); // 根据id找index
        return super.onTouchEvent(ev); // 调用CupcakeGestureDetector的on方法
    }

版本 2点2 equal or more

onScale在此处回调了

public class FroyoGestureDetector extends EclairGestureDetector {
    // scale手势检测器,高版本就是好啊
    protected final ScaleGestureDetector mDetector;
    public FroyoGestureDetector(Context context) {
        super(context);
        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; // NaN:not a NO
                    // 回调,将手势操作引起的缩放比例和中心点
                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);
    }
    @Override
    public boolean isScaling() {
        return mDetector.isInProgress();
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDetector.onTouchEvent(ev);
        return super.onTouchEvent(ev);
    }
}

Compat类

    private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
    public static void postOnAnimation(View view, Runnable runnable) {
        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
            postOnAnimationJellyBean(view, runnable);
        } else {
            view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
        }
    }
    @TargetApi(16)
    private static void postOnAnimationJellyBean(View view, Runnable runnable) {
        view.postOnAnimation(runnable);
        //Causes the Runnable to execute on the next animation time step. The runnable will be run on the user interface thread.
    }
    // 上面手势检测使用的,根据action获取index,根据系统调用不同方法
    public static int getPointerIndex(int action) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
            return getPointerIndexHoneyComb(action);
        else
            return getPointerIndexEclair(action);
    }
    @SuppressWarnings("deprecation")
    @TargetApi(Build.VERSION_CODES.ECLAIR)
    private static int getPointerIndexEclair(int action) {
        return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
    }
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private static int getPointerIndexHoneyComb(int action) {
        return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    }

注释

@Deprecated
@param midScale medium scale preset
<p>&nbsp;</p>  // 空白区域
<p/>
{@link #setMediumScale(float mediumScale)}
{@link android.widget.ImageView.ScaleType}
<br/>
<p>This <strong>must</strong> be used if you need to set the page before
  the views are drawn on screen (e.g., default start page).</p>
   override的注释
      /*
     * (non-Javadoc)
     *
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    <pre>ssfsd</pre>防止被换行

PhotoView说明

继承自Imageview,里面包含了PhotoViewAttacher变量
以及
ScaleType mPendingScaleType;

注意

    // 覆盖了Imageview的setScaleType,当在xml中配置了matrix后,下面代码会先于PhotoView的初始化init()函数执行
    public void setScaleType(ScaleType scaleType) { 
        if (null != mAttacher) {
            mAttacher.setScaleType(scaleType);
        } else {
            mPendingScaleType = scaleType;
        }
    }
    // init中
    if (null != mPendingScaleType) {
            setScaleType(mPendingScaleType);
            mPendingScaleType = null;
    }
      // 继而
     mAttacher.setScaleType(scaleType);
     // 接着
     throw new IllegalArgumentException(scaleType.name() + " is not supported in PhotoView");
     // 因此不要配置matrix属性

    @Override
    protected void onDetachedFromWindow() { // 良好的习惯,不用时
        mAttacher.cleanup();
        super.onDetachedFromWindow();
    }
    @Override
    protected void onAttachedToWindow() {
        init();
        super.onAttachedToWindow();
    }     

而PhotoView的所有功能基本上由PhotoViewAttacher实现了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值