Android使用ImageView实现支持手势缩放效果
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实现支持手势缩放效果,希望对大家有所帮助
上一篇: java 中堆内存和栈内存理解
下一篇: jQuery基础学习(1)