編輯:關於Android編程
TouchImageView繼承自ImageView具有ImageView的所有功能;除此之外,還有縮放、拖拽、雙擊放大等功能,支持viewpager和scaletype,並伴有動畫效果。
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()); }
初始化,設置ScaleGestureDetector的監聽器為ScaleListener,這是用來處理縮放手勢的,設置GestureDetector的監聽器為GestureListener,這是用來處理雙擊和fling手勢的,前兩個手勢都會引起圖片的縮放,而fling會引起圖片的移動。
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); mGestureDetector = new GestureDetector(context, new GestureListener());
最後設置自定義View的touch事件監聽器為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; } }
觸摸時會走到PrivateOnTouchListener的onTouch,它又會將捕捉到的MotionEvent交給mScaleDetector和mGestureDetector來分析是否有合適的callback函數來處理用戶的手勢。
mScaleDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event);
同時在當前狀態是DRAG時將X、Y移動的距離賦值給變換矩陣
matrix.postTranslate(fixTransX, fixTransY);
給ImageView設置矩陣,完成X、Y的移動,即實現單指拖拽動作
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); } } }
兩指縮放動作會走到ScaleListener的回調,在它的onScale回調中會處理圖片的縮放
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(); }
這裡會將多次縮放的縮放比累積,並設置有最大和最小縮放比,不能超出范圍
normalizedScale *= deltaScale;
最後把X、Y的縮放比和焦點傳給變換矩陣,通過矩陣關聯到ImageView,完成縮放動作
matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY);
在onScaleEnd回調中,我們會判斷是否當前縮放比超出最大縮放比或者小於最小縮放比,如果是,會有一個動畫回到最大或最小縮放比狀態
DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); compatPostOnAnimation(doubleTap);
這裡的動畫DoubleTapZoom就是雙擊動畫,關於DoubleTapZoom我們下面會講到。至此兩指縮放動作就完成了,下面繼續看雙擊縮放動作。
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回調中,設置雙擊縮放比,如果當前無縮放,則設置縮放比為最大值,如果已經是最大值,則設置為無縮放
float targetZoom = (normalizedScale == minScale) ? maxScale : minScale;
然後將當前點擊坐標做為縮放中心,連同縮放比一起交給DoubleTapZoom,完成縮放動畫
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其實是一個線程,實現了Runnable,我們直接看它的Run方法吧,這裡定義了一個時間t
float t = interpolate();
其實t在500ms內通過一個加速差值器從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計算出當前縮放比
double deltaScale = calculateDeltaScale(t);
實現縮放
scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper);
然後根據當前t的值判斷動畫是否結束,如果t小於1,表示動畫還未結束,重新執行本線程,否則設置狀態完成。這裡就是通過在這500ms內多次執行線程,多次重繪ImageView實現動畫效果的。
if (t < 1f) { compatPostOnAnimation(this); } else { setState(State.NONE); }
同時在GestureListener的onFling回調中,設置Fling的X、Y速度,然後執行Fling的位移動畫
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其實也是一個線程,實現了Runnable,根據Fling手勢的X、Y速度我們會執行Scroller的fling函數,並且將當前位置設置為起始位置
scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX,maxX, minY, maxY); currX = startX; currY = startY;
再來看看Run函數,根據scroller當前滾動位置計算出新的位置信息,與舊位置相減得出在X、Y軸平移距離,實現平移
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); }
最後延時一段時間再次調用線程完成新的平移繪圖,如此往復,直到scroller停止滾動,多次重繪ImageView實現了fling動畫效果。
private void compatPostOnAnimation(Runnable runnable) { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { postOnAnimation(runnable); } else { postDelayed(runnable, 1000/60); } }
下面看一看顯示效果吧:
單個圖片
圖片加載到ViewPager中
鏡像圖片
點擊可改變圖片
點擊可改變ScaleType
以上所述是小編給大家介紹的Android使用ImageView實現支持手勢縮放效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!
好久沒有更新博客,寫這篇技術時,感覺很多東西生疏了好多。於是心有感慨:我們做技術的,要是長時間不搞技術,那就是被技術搞!所以攻守之間,大家謹慎思量。冬天已過,放假出去玩耍
Eclipse是老牌的開發工具,相信早期開發android程序每一個碼農都使用過這個軟件,添加ADT插件之後就能開發android程序了。因為是開源的,所以開發起項目來還
集成地圖SDK國內常用的地圖SDK就是百度和高德了,二者的用法大同小異,可按照官網上的開發指南一步步來。下面是我在集成地圖SDK時遇到的問題說明:1、點擊基本地圖功能選項
Android高級控件——ViewPager、GridView、popwindow、SlideMenu(下)android:screenOrien