当前位置: 移动技术网 > 移动技术>移动开发>Android > Android使用ImageView实现支持手势缩放效果

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

2019年07月24日  | 移动技术网移动技术  | 我要评论

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实现支持手势缩放效果,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网