Android开源项⽬源码解析--PhotoView源码解析(七)
项⽬:
1. 功能介绍
特性(Features):
⽀持 Pinch ⼿势⾃由缩放。
⽀持双击放⼤/还原。
⽀持平滑滚动。
在滑动⽗控件下能够运⾏良好。(例如:ViewPager)
⽀持基于 Matrix 变化(放⼤/缩⼩/移动)的事件监听。
优势:
PhotoView 是 ImageView 的⼦类,⾃然的⽀持所有 ImageView 的源⽣⾏为。
任意项⽬可以⾮常⽅便的从 ImageView 升级到 PhotoView,不⽤做任何额外的修改。
可以⾮常⽅便的与 ImageLoader/Picasso 之类的异步⽹络图⽚读取库集成使⽤。
事件分发做了很好的处理,可以⽅便的与 ViewPager 等同样⽀持滑动⼿势的控件集成。
2. 总体设计
PhotoView 这个库实际上⽐较简单,关键点其实就是 Touch 事件处理和 Matrix 图形变换的应⽤.
2.1 TouchEvent 及⼿势事件处理
对 TouchEvent 分发流程不了解的建议先阅读
本库中对 Touch 事件的处理流程请参考第三部分的流程图,会有⼀个⽐较直观的认识。
2.2 Matrix
由于 Matrix 是 Android 系统源⽣ API,很多开发者对此都⽐较熟悉,为了不影响阅读效果,故不在此详细叙述,如果对其不是很了解,可以查看本⽂档末尾的 Matrix 补充说明
3. 流程图
Touch 及⼿势事件判定及传递流程:
编织地垫
如图,从架构上看,⼲净利落的将事件层层分离,交由不同的 Detector 处理,最后再将处理结果回调给 PhtotViewAttacher 中的 Matrix 去实现图形变换效果。
4. 详细设计
4.1 核⼼类功能介绍
Core 核⼼类
4.1.1 PhotoView
PhotoView 类负责暴露所有供外部调⽤的 API,其本⾝直接继承⾃ ImageView,同时实现了 IPhotoView 接⼝. IPhotoView 接⼝提供了缩放相关的设置属性和操控 matrix 变化的回调接⼝.
主要⽅法说明:
public PhotoView(Context context)
public PhotoView(Context context, AttributeSet attr)
车定位怎么定位public PhotoView(Context context, AttributeSet attr, int defStyle)
构造函数,完全与 ImageView 相同,你可以将 PhotoView 直接当做 ImageView 使⽤,完全兼容.
public void tPhotoViewRotation(float rotationDegree)
⽤于设置图⽚旋转⾓度.
注意:例如使⽤ Android 相机拍摄的相⽚,会根据拍摄时⼿机⽅向的不同,在 EXIF 中存储不同的旋转⾓度信息,显⽰时往往需要查询 EXIF 信息并将照⽚旋转⾄正确的⽅向. 通常我们处理这种问题有两种⽅案:
通过 ateBitmap ⽅式重建出正确⽅向的图⽚,再加载到 ImageView 中显⽰。(不建议使⽤,因为会占⽤双倍的内
存,Bitmap 的回收不是⽴即⽣效的。)
在 ImageView 中使⽤⾃定义 Matrix 将图⽚旋转到正确的⽅向。
恐怖拼音由于 PhotoView 中对图⽚的缩放操作依赖对 Matrix 的操作,⾃定义 Matrix 会⼲扰 PhotoView 的缩放⾏为,所以 PhotoView 并不⽀持ScaleType.Matrix. 可参见 PhotoViewAttacher 源码:
/**
* @return true if the ScaleType is supported.
*/
private static boolean isSupportedScaleType(final ScaleType scaleType) {
if (null == scaleType) {
return fal;
}
switch (scaleType) {
ca MATRIX:
throw new IllegalArgumentException(scaleType.name()
+ " is not supported in PhotoView");
default:
return true;
}
}
这⾥特意提供了⼀个额外的 tPhotoViewRotation ⽅法即是为了解决这个问题。
public boolean canZoom()
public void tZoomable(boolean zoomable)
缩放功能开关及状态获取. 关闭后 PhotoView 将不再响应缩放动作.
public RectF getDisplayRect()
public Matrix getDisplayMatrix()
public boolean tDisplayMatrix(Matrix finalRectangle)
获取及设置当前matrix状态.
public ScaleType getScaleType()
获取缩放模式。使⽤的源⽣的 ImageView.ScaleType. 在 PhotoView 中默认值为 FIT_CENTER.
public void tAllowParentInterceptOnEdge(boolean allow)
设置标志位是否允许⽗控件捕获发⽣在边缘的 TouchEvent
这个标志位实际上对应的是 questDisallowInterceptTouchEvent(boolean flag)
经常做⾃定义 View 处理 TouchEvent 的对这个⽅法应当都不陌⽣。
PhotoView 中英⽂注释:
* 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 = Parent();
if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling()) {
if (mScrollEdge == EDGE_BOTH
|| (mScrollEdge == EDGE_LEFT && dx >= 1f)
|| (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
if (null != parent)
}
} el {
if (null != parent) {
}
}
通过调⽤ tAllowParentInterceptOnEdge(fal),可以完全屏蔽⽗控件的 TouchEvent. 这个设置是为了防⽌⽗控件响应InterceptTouchEvent.
例如
PhotoView 外层是 ScrollView,通过 requestDisallowInterceptTouchEvent ⽅法可以阻⽌ ScrollView 响应滑动⼿势.
PhotoView 本⾝已做好了相关处理,在 PhotoView 滚到图⽚边缘时,Scroll 事件由⽗控件处理,在 Pho
toView 未滚动到边缘时,Scroll 事件由PhotoView 处理.
除⾮开发者有特殊的需求,否则不需要⾃⼰去调⽤该⽅法改变 TouchEvent 事件的阻断逻辑.
public void tImageDrawable(Drawable drawable)
public void tImageResource(int resId)
public void tImageURI(Uri uri)
重载了 ImageView 的 3 个设置图⽚的⽅法,以确保图⽚改变时 PhotoViewAttacher 及时更新视图和重置 matrix 状态protected void onDetachedFromWindow()
重载了 ImageView 的⽅法,⽤于在视图被从 Window 中移除时,通知 PhotoViewAttacher 清空数据.
4.1.2 IPhotoView
IPhotoView 接⼝定义了缩放相关的⼀组 t/get ⽅法.PhotoView 是其实现类. 相关⽅法已在 PhotoView 中介绍,这⾥略过.
4.1.3 PhotoViewAttacher
核⼼类
private static boolean isSupportedScaleType(final ScaleType scaleType)
判断 ScaleType 是否⽀持。这个判断中实际只有 ScaleType.Matrix 会返回 fal.
由于 PhotoView 中缩放滑动操作都依赖Matrix,所以并不⽀持⽤户再传⼊⾃定义 Matrix.
不动笔墨不读书
public void cleanup()
PhotoView 不再使⽤时,可⽤于释放相关资源。移除 Obrver, Listener.
public boolean tDisplayMatrix(Matrix finalMatrix)
通过 Matrix 来直接修改 ImageView 的显⽰状态。
private void cancelFling()
取消惯性滑动。
private boolean checkMatrixBounds()
检查当前显⽰范围是否处于边界上,并更新 mScrollEdge 标志位。
处理 TouchEvent 时需要根据 mScrollEdge 标志位的状态来判断是否允许 ViewParent 的 InterceptTouchEvent 接收 TouchEvent.
private void retMatrix()
重置 Matrix 状态,并恢复⾄ FIT_CENTER 状态
private void updateBaMatrix(Drawable d)
根据 PhotoView 的宽⾼和 Drawable 的宽⾼计算 FIT_CENTER 状态的 Matrix.
public void onDrag(float dx, float dy)
OnGestureListener 接⼝回调的实现⽅法.
实际完成拖拽/移动效果. 核⼼代码:
mSuppMatrix.postTranslate(dx, dy);
通过改代码修改 Matrix 中 View 的起始位置,制造出图⽚被拖拽移动的效果.
public void onFling(float startX, float startY, float velocityX, float velocityY)
OnGestureListener 接⼝回调的实现⽅法. 实际完成惯性滑动效果.
惯性滑动效果分两部分完成.
1) 调⽤
mScroller.fling(startX, startY, velocityX, velocityY, minX,
maxX, minY, maxY, 0, 0);
进⾏惯性滑动辅助计算.
对 Scroller 不了解的可以参考官⽅说明
简单来讲,Scroller 是⼀个辅助计算器,它可以帮你计算出某⼀时刻 View 的滚动状态及位置,但是它本⾝不会对 View 进⾏任何更改
2) 使⽤了 FlingRunnable 和 Compat.postOnAnimation(imageView,mFlingRunnable)在每⼀帧绘制前更新 Matrix 状态关于 FlingRunnable 和 Compat.postOnAnimation 类的作⽤机制可以参考下⾯ 4.1.4 双11购物狂欢节
的说明.
public void onScale(float scaleFactor, float focusX, float focusY)
OnGestureListener 接⼝回调的实现⽅法.
实际完成缩放效果.
核⼼代码:
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
对 Matrix 作⽤机制不了解的话,可以拉到⽂档最后,有⼀个针对 Matrix 的简略介绍.
内部类 FlingRunnable
实现惯性滑动的动画效果.
这个 Runnable 必须配合 View.postOnAnimation(view,runnable) 使⽤.
在下⼀帧绘制前,系统会执⾏该 Runnable,这样我们就可以在 runnable 中更新 UI 状态.
原理上类似⼀个递归调⽤,每次 UI 绘制前更新 UI 状态,并指定下次 UI 更新前再执⾏⾃⼰.
这种写法与使⽤循环或 Handler 每隔 16ms 刷新⼀次 UI 基本等价,但是更为⽅便快捷.
更新 UI 的核⼼逻辑⾮常简单,根据 mScroller 计算出的偏移量更新 Matrix 状态:
mSuppMatrix.postTranslate(dx, dy);
内部类 AnimatedZoomRunnable
实现双击时的缩放动画.
作⽤机制基本同上.
区别是 AnimatedZoomRunnable 的执⾏进度由 AccelerateDecelerateInterpolator 控制.
对 Interpolator 没有概念的可以参阅官⽅ Demo
你也可以简单认为这就是⼀个动画进度控制器.
核⼼逻辑依然很简单,根据动画进度缩⼩/放⼤图⽚
基本色
mSuppMatrix.postScale(deltaScale, deltaScale, mFocalX, mFocalY);
接⼝及⼯具类
4.1.4 Compat
⽤于做 View.postOnAnimation ⽅法在低版本上的兼容.
注:View.postOnAnimation (Runnable action) 在 PhotoView 中⽤于处理双击放⼤/缩⼩惯性滑动时的动画效果.
每次系统绘图前都会先执⾏这个 Runnable 回调,通过在此时改变视图状态以实现动画效果。该⽅法仅⽀持 api >= 16 所以 PhotoView 中使⽤了 Compat 类来做低版本兼容。
实际上也可以使⽤ android.support.v4.view.ViewCompat 替代。对⽐ android.support.v4.view.ViewCompat 和
4.1.5 ScrollerProxy
抽象类,主要是为了做不⽤版本之间的兼容,具体说明见GingerScroller IcsScroller PreGingerScroller这三个接⼝实现类的说明.
4.1.6 GingerScroller
ScrollerProxy接⼝实现类适⽤于 API 9 ~ 14 即 2.3 ~ 4.0 之间的所有 Android 版本. 其实现主要基于 android.widget.OverScroller
4.1.7 IcsScroller
适⽤于 API 14 以上即 4.0 以上的所有 Android 版本其实现基于源⽣ android.widget.OverScroller , 没有任何修改.
4.1.8 PreGingerScroller
适⽤于 API 9 以下即 2.3 以下的所有 Android 版本其实现主要基于 android.widget.Scroller
4.1.9 GestureDetector
接⼝,主要是为了做不同版本之间的兼容,具体说明见CupcakeGestureDetector,EclairGestureDetector,FroyoGestureDetector三个接⼝的实现类.
4.1.10 OnGestureListener
⼿势回调接⼝
4.1.11 CupcakeGestureDetector
适⽤于 api < 7 的设备,此时 PhotoView 不⽀持双指 pinch 放⼤/缩⼩操作
4.1.12 EclairGestureDetector
适⽤于 api >= 8 , ⽤于修正多指操控的问题,使 TouchEvent 的 getActiveX getActiveY 指向正确的 Pointer,并将事件传递
同旁内角互补
市场绩效给CupcakeGestureDetector处理,此时 PhotoView 不⽀持双指 pinch 放⼤/缩⼩操作
4.1.13 FroyoGestureDetector
适⽤于 api > 9 , 通过 android.view.ScaleGestureDetector 实现对 Pinch ⼿势的⽀持,并将事件传递给EclairGestureDetector处理
注意: 以上 3 个类并不实际执⾏放⼤/缩⼩⾏为, 判断⾏为之后会回调给 PhtotViewAttacher 执⾏缩放/移
动操作
4.1.14 VersionedGestureDetector