效果预览
实现思路
本控件主要基于PhotoView来实现,放大、拖拽以及和ViewPager的冲突处理都是借鉴PhotoView的处理方法,并在此基础上加入拖拽返回的功能。
整体实现主要分为两个部分:放大拖拽、拖拽返回。这两部分都是通过变换矩阵来实现的,ImageView的setImageMatrix(matrix)。
原本PhotoVIew利用一个矩阵来记录放大缩小以及放大后拖拽的操作并做适当的变换,在updataMatrix()的时候做一些判断,保证图片的边界不会显示在正常范围以内,这样会导致当图片的高度小于屏幕高度时无法向下拖拽。为了能够向下拖拽返回,并且不要影响原本的图片显示范围的控制逻辑,这里另外加入一个矩阵mDragDownMatrix
来记录向下拖拽的距离,最终和mScaleAndDragMatrix
一起决定图片的矩阵变换。
缩放方面利用安卓的ScaleGestureDetector来监听当前是否在进行缩放操作,如果正在缩放则禁止拖拽的操作。
整个触摸操作的过程主要有两种状态:缩放mScaleGestureDetector.isInProgress()
、拖拽isDragging
。拖拽再细分为放大超出屏幕后的拖拽和向下拖拽返回isDraggingDown
的状态。
进入拖拽状态的判断,在ACTION_MOVE中:
float dX = getActiveX(event) - mLastTouchX;
float dY = getActiveY(event) - mLastTouchY;
if (!isDragging) {
isDragging = Math.sqrt(dX * dX + dY * dY) >= mTouchSlop;
if (isDragging) {
mLastTouchX = getActiveX(event);
mLastTouchY = getActiveY(event);
}
}
要进入具体拖拽操作的判断,避免出现拖拽过程中又重新缩放的冲突:
if(isDragging && !mScaleGestureDetector.isInProgress() && event.getPointerCount() == 1)
在拖拽过程中判断当前是否在向下拖拽返回:
RectF rectF = getDisplayRect(getDrawMatrix());
if (!isDraggingDown && rectF != null) {
boolean isScrollVertical = Math.abs(getActiveX(event) - mActionDownX) +
mTouchSlop * 5 < Math.abs(getActiveY(event) - mActionDownY);
boolean isScrollUp = getActiveY(event) < mActionDownY;
boolean dragVerticalOutOfBounds = (Math.round(rectF.top) >= 0 && !isScrollUp) ||
(Math.round(rectF.bottom) <= getViewHeight() && isScrollUp);
if (isScrollVertical && dragVerticalOutOfBounds) {
mActionDownX = getActiveX(event);
mActionDownY = getActiveY(event);
isDraggingDown = true;
}
}
拖拽操作变换的具体实现:
if (isDraggingDown) {
mDragDownMatrix.reset();
float deltaY = getActiveY(event) - mActionDownY;
mDragDownMatrix.postTranslate(0, deltaY / 3);
updateMatrix();
if (mOnPhotoViewDragListener != null) {
mOnPhotoViewDragListener.onDragOffset(Math.abs(deltaY / 3), getViewHeight() / 6);
}
requestParentDisallowInterceptTouchEvent(true);
} else {
mScaleAndDragMatrix.postTranslate(getActiveX(event) - mLastTouchX, getActiveY(event) - mLastTouchY);
updateMatrix();
mLastTouchX = getActiveX(event);
mLastTouchY = getActiveY(event);
// 处理和ViewPager的滑动冲突
boolean isScrollHorizontal = Math.abs(getActiveX(event) - mActionDownX) + mTouchSlop > Math.abs(getActiveY(event) - mActionDownY) * 5;
boolean isScrollRight = getActiveX(event) > mActionDownX;
if (rectF != null) {
if (isScrollHorizontal && (isScrollRight && Math.round(rectF.left) >= 0)
|| (!isScrollRight && Math.round(rectF.right) <= getViewWidth())) {
requestParentDisallowInterceptTouchEvent(false);
} else {
requestParentDisallowInterceptTouchEvent(true);
}
}
}
应用矩阵变换:
private void updateMatrix() {
RectF rectF = getDisplayRect(getDrawMatrix());
if (rectF != null) {
float deltaX = 0;
float deltaY = 0;
if (rectF.width() < getViewWidth()) {
deltaX = (getViewWidth() - rectF.width()) / 2 - rectF.left;
} else if (rectF.left > 0) {
deltaX = -rectF.left;
} else if (rectF.right < getViewWidth()) {
deltaX = getViewWidth() - rectF.right;
}
if (rectF.height() <= getViewHeight()) {
deltaY = (getViewHeight() - rectF.height()) / 2 - rectF.top;
} else if (rectF.top > 0) {
deltaY = -rectF.top;
} else if (rectF.bottom < getViewHeight()) {
deltaY = getViewHeight() - rectF.bottom;
}
// 确保图片显示在正常的范围以内
mScaleAndDragMatrix.postTranslate(deltaX, deltaY);
Matrix drawMatrix = getDrawMatrix();
drawMatrix.postConcat(mDragDownMatrix);
setImageMatrix(drawMatrix);
}
}
在多点触摸的时候,会选择其中一个触摸点视为Active,抬起手指时,也就是从缩放状态进入拖拽状态,要判断抬起的手指是否为当前视作Active的,并且要更新一下触摸点的位置数据,不然会出现抖动的情况。
case MotionEvent.ACTION_POINTER_UP: {
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = event.getPointerId(newPointerIndex);
mActionDownX = mLastTouchX = event.getX(newPointerIndex);
mActionDownY = mLastTouchY = event.getY(newPointerIndex);
} else {
mActionDownX = mLastTouchX = event.getX(mActivePointerIndex);
mActionDownY = mLastTouchY = event.getY(mActivePointerIndex);
}
break;
}
源码: https://github.com/okhochan/DragToFinishPhotoView