代码地址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());
}
}