PhotoView源码分析(1)
2015-09-03 16:55
507 查看
简介
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> </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实现了。
相关文章推荐
- 原型继承
- c++ primer读书笔记之c++11(三)
- 一对多的单项关联
- linux之用户管理
- 4.Realm(数据处理,交互)
- Unity3D学习笔记《Space Shooter》二
- C语言实验题——三个数排序
- 将两个数组合并并排序
- 第31-35课
- BZOJ 1026: [SCOI2009]windy数( dp )
- 已知: 每个飞机只有一个油箱, 飞机之间可以相互加油(注意是相互,没有加油机) 一箱油可供一架飞机绕地球飞半圈,问题:为使至少一架飞机绕地球一圈回到起飞时的飞机
- 检测出运动目标后提取边界 两个函数 cvFindContours和cvBoundingRect
- Windows内核编程基础篇之获得当前滴答数
- VS2010中“工具>选项中的VC++目录编辑功能已被否决”解决方法
- rdb快照持久化
- hdu 1864 最大报销额(非整数背包)
- NOI 2006 最大获利(最大权闭合)
- 寻找数组中的最大值和最小值
- iOS 应用内购买基础教程 swift篇
- 4000 swift 学习<二> if分支, for循环,