Android使用ImageView實(shí)現(xiàn)支持手勢縮放效果
TouchImageView繼承自ImageView具有ImageView的所有功能;除此之外,還有縮放、拖拽、雙擊放大等功能,支持viewpager和scaletype,并伴有動(dòng)畫效果。
sharedConstructing private void sharedConstructing(Context context) { super.setClickable(true); this.context = context; mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); mGestureDetector = new GestureDetector(context, new GestureListener()); matrix = new Matrix(); prevMatrix = new Matrix(); m = new float[9]; normalizedScale = 1; if (mScaleType == null) { mScaleType = ScaleType.FIT_CENTER; } minScale = 1; maxScale = 3; superMinScale = SUPER_MIN_MULTIPLIER * minScale; superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; setImageMatrix(matrix); setScaleType(ScaleType.MATRIX); setState(State.NONE); onDrawReady = false; super.setOnTouchListener(new PrivateOnTouchListener()); }
初始化,設(shè)置ScaleGestureDetector的監(jiān)聽器為ScaleListener,這是用來處理縮放手勢的,設(shè)置GestureDetector的監(jiān)聽器為GestureListener,這是用來處理雙擊和fling手勢的,前兩個(gè)手勢都會引起圖片的縮放,而fling會引起圖片的移動(dòng)。
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); mGestureDetector = new GestureDetector(context, new GestureListener());
最后設(shè)置自定義View的touch事件監(jiān)聽器為PrivateOnTouchListener,這是touch事件的入口。
super.setOnTouchListener(new PrivateOnTouchListener()); PrivateOnTouchListener private class PrivateOnTouchListener implements OnTouchListener { // // Remember last point position for dragging // private PointF last = new PointF(); @Override public boolean onTouch(View v, MotionEvent event) { mScaleDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event); PointF curr = new PointF(event.getX(), event.getY()); if (state == State.NONE || state == State.DRAG || state == State.FLING) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: last.set(curr); if (fling != null) fling.cancelFling(); setState(State.DRAG); break; case MotionEvent.ACTION_MOVE: if (state == State.DRAG) { float deltaX = curr.x - last.x; float deltaY = curr.y - last.y; float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth()); float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight()); matrix.postTranslate(fixTransX, fixTransY); fixTrans(); last.set(curr.x, curr.y); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: setState(State.NONE); break; } } setImageMatrix(matrix); // // User-defined OnTouchListener // if(userTouchListener != null) { userTouchListener.onTouch(v, event); } // // OnTouchImageViewListener is set: TouchImageView dragged by user. // if (touchImageViewListener != null) { touchImageViewListener.onMove(); } // // indicate event was handled // return true; } }
觸摸時(shí)會走到PrivateOnTouchListener的onTouch,它又會將捕捉到的MotionEvent交給mScaleDetector和mGestureDetector來分析是否有合適的callback函數(shù)來處理用戶的手勢。
mScaleDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event);
同時(shí)在當(dāng)前狀態(tài)是DRAG時(shí)將X、Y移動(dòng)的距離賦值給變換矩陣
matrix.postTranslate(fixTransX, fixTransY);
給ImageView設(shè)置矩陣,完成X、Y的移動(dòng),即實(shí)現(xiàn)單指拖拽動(dòng)作
setImageMatrix(matrix);
ScaleListener
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { setState(State.ZOOM); return true; } @Override public boolean onScale(ScaleGestureDetector detector) { scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true); // // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user. // if (touchImageViewListener != null) { touchImageViewListener.onMove(); } return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { super.onScaleEnd(detector); setState(State.NONE); boolean animateToZoomBoundary = false; float targetZoom = normalizedScale; if (normalizedScale > maxScale) { targetZoom = maxScale; animateToZoomBoundary = true; } else if (normalizedScale < minScale) { targetZoom = minScale; animateToZoomBoundary = true; } if (animateToZoomBoundary) { DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); compatPostOnAnimation(doubleTap); } } }
兩指縮放動(dòng)作會走到ScaleListener的回調(diào),在它的onScale回調(diào)中會處理圖片的縮放
scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true);
scaleImage
private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) { float lowerScale, upperScale; if (stretchImageToSuper) { lowerScale = superMinScale; upperScale = superMaxScale; } else { lowerScale = minScale; upperScale = maxScale; } float origScale = normalizedScale; normalizedScale *= deltaScale; if (normalizedScale > upperScale) { normalizedScale = upperScale; deltaScale = upperScale / origScale; } else if (normalizedScale < lowerScale) { normalizedScale = lowerScale; deltaScale = lowerScale / origScale; } matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY); fixScaleTrans(); }
這里會將多次縮放的縮放比累積,并設(shè)置有最大和最小縮放比,不能超出范圍
normalizedScale *= deltaScale;
最后把X、Y的縮放比和焦點(diǎn)傳給變換矩陣,通過矩陣關(guān)聯(lián)到ImageView,完成縮放動(dòng)作
matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY);
在onScaleEnd回調(diào)中,我們會判斷是否當(dāng)前縮放比超出最大縮放比或者小于最小縮放比,如果是,會有一個(gè)動(dòng)畫回到最大或最小縮放比狀態(tài)
DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); compatPostOnAnimation(doubleTap);
這里的動(dòng)畫DoubleTapZoom就是雙擊動(dòng)畫,關(guān)于DoubleTapZoom我們下面會講到。至此兩指縮放動(dòng)作就完成了,下面繼續(xù)看雙擊縮放動(dòng)作。
GestureListener
private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { if(doubleTapListener != null) { return doubleTapListener.onSingleTapConfirmed(e); } return performClick(); } @Override public void onLongPress(MotionEvent e) { performLongClick(); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (fling != null) { // // If a previous fling is still active, it should be cancelled so that two flings // are not run simultaenously. // fling.cancelFling(); } fling = new Fling((int) velocityX, (int) velocityY); compatPostOnAnimation(fling); return super.onFling(e1, e2, velocityX, velocityY); } @Override public boolean onDoubleTap(MotionEvent e) { boolean consumed = false; if(doubleTapListener != null) { consumed = doubleTapListener.onDoubleTap(e); } if (state == State.NONE) { float targetZoom = (normalizedScale == minScale) ? maxScale : minScale; DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); compatPostOnAnimation(doubleTap); consumed = true; } return consumed; } @Override public boolean onDoubleTapEvent(MotionEvent e) { if(doubleTapListener != null) { return doubleTapListener.onDoubleTapEvent(e); } return false; } }
在onDoubleTap回調(diào)中,設(shè)置雙擊縮放比,如果當(dāng)前無縮放,則設(shè)置縮放比為最大值,如果已經(jīng)是最大值,則設(shè)置為無縮放
float targetZoom = (normalizedScale == minScale) ? maxScale : minScale;
然后將當(dāng)前點(diǎn)擊坐標(biāo)做為縮放中心,連同縮放比一起交給DoubleTapZoom,完成縮放動(dòng)畫
DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); compatPostOnAnimation(doubleTap);
DoubleTapZoom
private class DoubleTapZoom implements Runnable { private long startTime; private static final float ZOOM_TIME = 500; private float startZoom, targetZoom; private float bitmapX, bitmapY; private boolean stretchImageToSuper; private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); private PointF startTouch; private PointF endTouch; DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) { setState(State.ANIMATE_ZOOM); startTime = System.currentTimeMillis(); this.startZoom = normalizedScale; this.targetZoom = targetZoom; this.stretchImageToSuper = stretchImageToSuper; PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); this.bitmapX = bitmapPoint.x; this.bitmapY = bitmapPoint.y; // // Used for translating image during scaling // startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); endTouch = new PointF(viewWidth / 2, viewHeight / 2); } @Override public void run() { float t = interpolate(); double deltaScale = calculateDeltaScale(t); scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper); translateImageToCenterTouchPosition(t); fixScaleTrans(); setImageMatrix(matrix); // // OnTouchImageViewListener is set: double tap runnable updates listener // with every frame. // if (touchImageViewListener != null) { touchImageViewListener.onMove(); } if (t < 1f) { // // We haven't finished zooming // compatPostOnAnimation(this); } else { // // Finished zooming // setState(State.NONE); } } /** * Interpolate between where the image should start and end in order to translate * the image so that the point that is touched is what ends up centered at the end * of the zoom. * @param t */ private void translateImageToCenterTouchPosition(float t) { float targetX = startTouch.x + t * (endTouch.x - startTouch.x); float targetY = startTouch.y + t * (endTouch.y - startTouch.y); PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); matrix.postTranslate(targetX - curr.x, targetY - curr.y); } /** * Use interpolator to get t * @return */ private float interpolate() { long currTime = System.currentTimeMillis(); float elapsed = (currTime - startTime) / ZOOM_TIME; elapsed = Math.min(1f, elapsed); return interpolator.getInterpolation(elapsed); } /** * Interpolate the current targeted zoom and get the delta * from the current zoom. * @param t * @return */ private double calculateDeltaScale(float t) { double zoom = startZoom + t * (targetZoom - startZoom); return zoom / normalizedScale; } }
DoubleTapZoom其實(shí)是一個(gè)線程,實(shí)現(xiàn)了Runnable,我們直接看它的Run方法吧,這里定義了一個(gè)時(shí)間t
float t = interpolate();
其實(shí)t在500ms內(nèi)通過一個(gè)加速差值器從0到1加速增長
private float interpolate() { long currTime = System.currentTimeMillis(); float elapsed = (currTime - startTime) / ZOOM_TIME; elapsed = Math.min(1f, elapsed); return interpolator.getInterpolation(elapsed); }
通過t計(jì)算出當(dāng)前縮放比
double deltaScale = calculateDeltaScale(t);
實(shí)現(xiàn)縮放
scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper);
然后根據(jù)當(dāng)前t的值判斷動(dòng)畫是否結(jié)束,如果t小于1,表示動(dòng)畫還未結(jié)束,重新執(zhí)行本線程,否則設(shè)置狀態(tài)完成。這里就是通過在這500ms內(nèi)多次執(zhí)行線程,多次重繪ImageView實(shí)現(xiàn)動(dòng)畫效果的。
if (t < 1f) { compatPostOnAnimation(this); } else { setState(State.NONE); }
同時(shí)在GestureListener的onFling回調(diào)中,設(shè)置Fling的X、Y速度,然后執(zhí)行Fling的位移動(dòng)畫
fling = new Fling((int) velocityX, (int) velocityY); compatPostOnAnimation(fling);
Fling
private class Fling implements Runnable { CompatScroller scroller; int currX, currY; Fling(int velocityX, int velocityY) { setState(State.FLING); scroller = new CompatScroller(context); matrix.getValues(m); int startX = (int) m[Matrix.MTRANS_X]; int startY = (int) m[Matrix.MTRANS_Y]; int minX, maxX, minY, maxY; if (getImageWidth() > viewWidth) { minX = viewWidth - (int) getImageWidth(); maxX = 0; } else { minX = maxX = startX; } if (getImageHeight() > viewHeight) { minY = viewHeight - (int) getImageHeight(); maxY = 0; } else { minY = maxY = startY; } scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX, maxX, minY, maxY); currX = startX; currY = startY; } public void cancelFling() { if (scroller != null) { setState(State.NONE); scroller.forceFinished(true); } } @Override public void run() { // // OnTouchImageViewListener is set: TouchImageView listener has been flung by user. // Listener runnable updated with each frame of fling animation. // if (touchImageViewListener != null) { touchImageViewListener.onMove(); } if (scroller.isFinished()) { scroller = null; return; } if (scroller.computeScrollOffset()) { int newX = scroller.getCurrX(); int newY = scroller.getCurrY(); int transX = newX - currX; int transY = newY - currY; currX = newX; currY = newY; matrix.postTranslate(transX, transY); fixTrans(); setImageMatrix(matrix); compatPostOnAnimation(this); } } }
Fling其實(shí)也是一個(gè)線程,實(shí)現(xiàn)了Runnable,根據(jù)Fling手勢的X、Y速度我們會執(zhí)行Scroller的fling函數(shù),并且將當(dāng)前位置設(shè)置為起始位置
scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX,maxX, minY, maxY); currX = startX; currY = startY;
再來看看Run函數(shù),根據(jù)scroller當(dāng)前滾動(dòng)位置計(jì)算出新的位置信息,與舊位置相減得出在X、Y軸平移距離,實(shí)現(xiàn)平移
if (scroller.computeScrollOffset()) { int newX = scroller.getCurrX(); int newY = scroller.getCurrY(); int transX = newX - currX; int transY = newY - currY; currX = newX; currY = newY; matrix.postTranslate(transX, transY); fixTrans(); setImageMatrix(matrix); compatPostOnAnimation(this); }
最后延時(shí)一段時(shí)間再次調(diào)用線程完成新的平移繪圖,如此往復(fù),直到scroller停止?jié)L動(dòng),多次重繪ImageView實(shí)現(xiàn)了fling動(dòng)畫效果。
private void compatPostOnAnimation(Runnable runnable) { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { postOnAnimation(runnable); } else { postDelayed(runnable, 1000/60); } }
下面看一看顯示效果吧:
單個(gè)圖片
圖片加載到ViewPager中
鏡像圖片
點(diǎn)擊可改變圖片
點(diǎn)擊可改變ScaleType
以上所述是小編給大家介紹的Android使用ImageView實(shí)現(xiàn)支持手勢縮放效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- Android手勢ImageView三部曲 第二部
- Android手勢ImageView三部曲 第一部
- Android自定義GestureDetector實(shí)現(xiàn)手勢ImageView
- Android ImageView隨手勢變化動(dòng)態(tài)縮放圖片
- Android手勢滑動(dòng)實(shí)現(xiàn)ImageView縮放圖片大小
- Android實(shí)現(xiàn)手勢控制ImageView圖片大小
- Android通過手勢實(shí)現(xiàn)的縮放處理實(shí)例代碼
- android開發(fā)之為activity增加左右手勢識別示例
- android使用gesturedetector手勢識別示例分享
- Android手勢ImageView三部曲 第三部
相關(guān)文章
Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度
本篇文章主要介紹了Android中實(shí)現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android 讓自定義TextView的drawableLeft與文本一起居中
本文主要介紹Android 自定義控件TextView顯示居中問題,在開發(fā)過程中經(jīng)常會遇到控件的重寫,這里主要介紹TextView的drawableLeft與文本一起居中的問題2016-07-07android scrollview 滑動(dòng)到頂端或者指定位置的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猘ndroid scrollview 滑動(dòng)到頂端或者指定位置的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04Android基礎(chǔ)總結(jié)篇之三:Activity的task相關(guān)介紹
這篇文章主要介紹了Android基礎(chǔ)總結(jié)篇之三:Activity的task相關(guān)介紹,具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11Android自定義ScrollView實(shí)現(xiàn)放大回彈效果實(shí)例代碼
本篇文章主要介紹了Android自定義ScrollView實(shí)現(xiàn)放大回彈效果實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03Android ListView添加頭布局和腳布局實(shí)例詳解
這篇文章主要介紹了Android ListView添加頭布局和腳布局實(shí)例詳解的相關(guān)資料,大家看下效果是否是自己想要實(shí)現(xiàn)的效果,這里附了實(shí)現(xiàn)代碼和實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-11-11