Android使用ImageView实现支持手势缩放效果
Android使用ImageView实现支持手势缩放效果
发布时间:2016-12-28 来源:查字典编辑
摘要:TouchImageView继承自ImageView具有ImageView的所有功能;除此之外,还有缩放、拖拽、双击放大等功能,支持view...

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); } }

下面看一看显示效果吧:

单个图片

Android使用ImageView实现支持手势缩放效果1

图片加载到ViewPager中

Android使用ImageView实现支持手势缩放效果2

镜像图片

Android使用ImageView实现支持手势缩放效果3

点击可改变图片

Android使用ImageView实现支持手势缩放效果4

点击可改变ScaleType

Android使用ImageView实现支持手势缩放效果5

以上所述是小编给大家介绍的Android使用ImageView实现支持手势缩放效果,希望对大家有所帮助,如果大家有任何疑问请给我们留言,小编会及时回复大家的。在此也非常感谢大家对查字典教程网的支持!

推荐文章
猜你喜欢
附近的人在看
推荐阅读
拓展阅读
相关阅读
网友关注
最新安卓软件开发学习
热门安卓软件开发学习
编程开发子分类