欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-07 11:43:09
touchimageview继承自imageview具有imageview的所有功能;除此之外,还有缩放、拖拽、双击放大等功能,支持viewpager和scaletype,...

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实现支持手势缩放效果

图片加载到viewpager中

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

镜像图片

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

点击可改变图片

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

点击可改变scaletype

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

以上所述是小编给大家介绍的android使用imageview实现支持手势缩放效果,希望对大家有所帮助