最近在實現一個類似淘寶中的評論列表的功能,其中要在列表中顯示評論圖,點擊圖片後顯示大圖進行查看,各家app幾乎都會有這樣的功能。 可以看到,一個體驗較好的查看大圖的基本功能有, 第一,左右滑動時切換圖片; 第二,雙擊或雙指縮放實現圖片的縮放; 第三,圖片放大時,滑動到邊緣繼續滑動時,切換圖片。 因為 ...
最近在實現一個類似淘寶中的評論列表的功能,其中要在列表中顯示評論圖,點擊圖片後顯示大圖進行查看,各家app幾乎都會有這樣的功能。
可以看到,一個體驗較好的查看大圖的基本功能有,
第一,左右滑動時切換圖片;
第二,雙擊或雙指縮放實現圖片的縮放;
第三,圖片放大時,滑動到邊緣繼續滑動時,切換圖片。
因為我們的app中使用了fresco庫,但fresco提供的SimpleDraweeView不支持縮放,看網上有人擴展了SimpleDraweeView,使之支持縮放。但經過漫長的調研,發現fresco近期提供了一個新的sample:ZoomableDraweeView,專門用來支持縮放,欣喜若狂的下載下來把玩了一把,發現三個需求點都滿足!可惜的是,這個控制項在細節上有幾點不滿足:雙擊後放大到最大,再雙擊後卻縮小為最小(期望恢復為正常大小),雖然最小可以設置,但這個值應該是在雙指縮小時才用到。另一點是在雙指縮小並鬆開後,圖片保持在那個縮小的尺寸(期望自動恢復為正常大小)。
查看代碼後發現需要修改幾點就可以滿足我的需求。下麵的內容主要記錄我思考問題、解決問題的思路,如果你也有類似的需求,可以直接拿代碼:https://github.com/ibosong/CommentGallery
1. 雙擊恢復正常尺寸
修改DoubleTapGestureListener 中的onDoubleTapEvent方法,因為主要修改的邏輯在雙指鬆開後,於是我們在MotionEvent.ACTION_UP的case中修改相關邏輯。首先判斷mDoubleTapScroll,即是否是雙擊後不鬆開並滑動的操作,這種操作下如果在鬆開手指時,圖片為縮小狀態,應當恢復正常大小,所以將原代碼:
if (mDoubleTapScroll) { float scale = calcScale(vp); zc.zoomToPoint(scale, mDoubleTapImagePoint, mDoubleTapViewPoint); }
修改為:
if (mDoubleTapScroll) { float scale = calcScale(vp); if (scale < 1.0f) { zc.zoomToPoint(1.0f, mDoubleTapImagePoint, mDoubleTapViewPoint, DefaultZoomableController.LIMIT_ALL, DURATION_MS, null); } else { zc.zoomToPoint(scale, mDoubleTapImagePoint, mDoubleTapViewPoint); } }
else裡面的代碼是正常雙擊後的代碼,將其中的minScale 改為1.0f即可
else { final float maxScale = zc.getMaxScaleFactor(); final float minScale = zc.getMinScaleFactor(); if (zc.getScaleFactor() < (maxScale + minScale) / 2) { zc.zoomToPoint( maxScale, ip, vp, DefaultZoomableController.LIMIT_ALL, DURATION_MS, null); } else { zc.zoomToPoint( /*minScale*/1.0f, ip, vp, DefaultZoomableController.LIMIT_ALL, DURATION_MS, null); } }
2. 雙指縮放,處理雙指縮小圖片後鬆開手指的情況
閱讀代碼可知,ZoomableDraweeView中的onTouchEvent方法調用了DefaultZoomableController的onTouchEvent方法,這裡面通過mGestureDetector的處理,最終回調到ZoomableDraweeView中的onGestureBegin,onGestureUpdate和onGestureEnd這三個方法中。mGestureDetector的處理是在MultiPointerGestureDetector的onTouchEvent方法中。這裡面原來的按下和鬆開手指的邏輯是這樣的:
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: { // restart gesture whenever the number of pointers changes mNewPointerCount = getPressedPointerCount(event); stopGesture(); updatePointersOnTap(event); if (mPointerCount > 0 && shouldStartGesture()) { startGesture(); } break;
}
每次在MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP情況下執行相同的動作:先stopGesture,然後startGesture,即先觸發onGestureEnd,然後觸發onGestureBegin。顯然這樣的處理是不合邏輯的,為什麼在手指按下的時候要觸發onGestureEnd?於是我們將代碼修改為:
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: mNewPointerCount = getPressedPointerCount(event); updatePointersOnTap(event); if (mPointerCount > 0 && shouldStartGesture()) { startGesture(); } break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: { mNewPointerCount = getPressedPointerCount(event);
updatePointersOnTap(event); stopGesture(); break; }
在手指按下的時候觸發onGestureBegin,手指抬起的時候觸發onGestureEnd。這時候我們只要在DefaultZoomableController中的onGestureEnd方法中處理鬆開手指的情況:如果圖片被縮小,則通過調用zoomToPoint方法將圖片恢復正常大小。
@Override public void onGestureEnd(TransformGestureDetector detector) { FLog.v(TAG, "onGestureEnd"); // When the image was zoomed in, releasing the fingers will restore the size of image. if (getScaleFactor() < 1.0f) { zoomToPoint(1.0f, new PointF(0.f, 0.f), new PointF(0.f, 0.f)); } }
這裡onGestureUpdate方法里的邏輯也要改一下,不要再調用mGestureDetector.restartGesture()。
@Override
public void onGestureUpdate(TransformGestureDetector detector) { FLog.v(TAG, "onGestureUpdate"); boolean transformCorrected = calculateGestureTransform(mActiveTransform, LIMIT_ALL); onTransformChanged(); // if (transformCorrected) { // mGestureDetector.restartGesture(); // } // A transformation happened, but was it without correction?
mWasTransformCorrected = transformCorrected; }
這樣實現,操作起來比較生硬,恢復大小的時候沒有動畫。
於是我們將onGestureEnd中的處理移至AbstractAnimatedZoomableController中,並將zoomToPoint修改為另一個實現了動畫的重載的方法:
@Override public void onGestureEnd(TransformGestureDetector detector) { // When the image was zoomed in, releasing the fingers will restore the size of image. if (getScaleFactor() < 1.0f) { zoomToPoint(1.0f, new PointF(0.f, 0.f), new PointF(0.f, 0.f), LIMIT_ALL, 300, null); } }
這樣我們的改造就全部完成了,安裝體驗一下。