Android App中实现可以双击放大和缩小图片功能的实例
程序员文章站
2024-02-25 21:13:33
先来看一个很简单的核心图片缩放方法:
public static bitmap scale(bitmap bitmap, float scalewidth,...
先来看一个很简单的核心图片缩放方法:
public static bitmap scale(bitmap bitmap, float scalewidth, float scaleheight) { int width = bitmap.getwidth(); int height = bitmap.getheight(); matrix matrix = new matrix(); matrix.postscale(scalewidth, scaleheight); log.i(tag, "scalewidth:"+ scalewidth +", scaleheight:"+ scaleheight); return bitmap.createbitmap(bitmap, 0, 0, width, height, matrix, true); }
注意要比例设置正确否则可能会内存溢出,比如曾经使用图片缩放时遇到这么个问题:
java.lang.illegalargumentexception: bitmap size exceeds 32bits
后来一行行查代码,发现原来是 scale 的比例计算错误,将原图给放大了 20 多倍,导致内存溢出所致,重新修改比例值后就正常了。
好了,下面真正来看一下这个实现了放大和原大两个级别的缩放的模块。
功能有:
- 以触摸点为中心放大(这个是网上其他的代码没有的)
- 边界控制(这个是网上其他的代码没有的)
- 双击放大或缩小(主要考虑到电阻屏)
- 多点触摸放大和缩小
这个模块已经通过了测试,并且用户也使用有一段时间了,是属于比较稳定的了。
下面贴上代码及使用方法(没有写测试项目,大家见谅):
imagecontrol 类似一个用户自定义的imageview控件。用法将在下面的代码中贴出。
import android.content.context; import android.graphics.bitmap; import android.graphics.matrix; import android.util.attributeset; import android.util.floatmath; import android.view.motionevent; import android.widget.imageview; public class imagecontrol extends imageview { public imagecontrol(context context) { super(context); // todo auto-generated constructor stub } public imagecontrol(context context, attributeset attrs) { super(context, attrs); // todo auto-generated constructor stub } public imagecontrol(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); // todo auto-generated constructor stub } // imageview img; matrix imgmatrix = null; // 定义图片的变换矩阵 static final int double_click_time_space = 300; // 双击时间间隔 static final int double_point_distance = 10; // 两点放大两点间最小间距 static final int none = 0; static final int drag = 1; // 拖动操作 static final int zoom = 2; // 放大缩小操作 private int mode = none; // 当前模式 float bigscale = 3f; // 默认放大倍数 boolean isbig = false; // 是否是放大状态 long lastclicktime = 0; // 单击时间 float startdistance; // 多点触摸两点距离 float enddistance; // 多点触摸两点距离 float topheight; // 状态栏高度和标题栏高度 bitmap primarybitmap = null; float contentw; // 屏幕内容区宽度 float contenth; // 屏幕内容区高度 float primaryw; // 原图宽度 float primaryh; // 原图高度 float scale; // 适合屏幕缩放倍数 boolean ismovex = true; // 是否允许在x轴拖动 boolean ismovey = true; // 是否允许在y轴拖动 float startx; float starty; float endx; float endy; float subx; float suby; float limitx1; float limitx2; float limity1; float limity2; icustommethod mcustommethod = null; /** * 初始化图片 * * @param bitmap * 要显示的图片 * @param contentw * 内容区域宽度 * @param contenth * 内容区域高度 * @param topheight * 状态栏高度和标题栏高度之和 */ public void imageinit(bitmap bitmap, int contentw, int contenth, int topheight, icustommethod icustommethod) { this.primarybitmap = bitmap; this.contentw = contentw; this.contenth = contenth; this.topheight = topheight; mcustommethod = icustommethod; primaryw = primarybitmap.getwidth(); primaryh = primarybitmap.getheight(); float scalex = (float) contentw / primaryw; float scaley = (float) contenth / primaryh; scale = scalex < scaley ? scalex : scaley; if (scale < 1 && 1 / scale < bigscale) { bigscale = (float) (1 / scale + 0.5); } imgmatrix = new matrix(); subx = (contentw - primaryw * scale) / 2; suby = (contenth - primaryh * scale) / 2; this.setimagebitmap(primarybitmap); this.setscaletype(scaletype.matrix); imgmatrix.postscale(scale, scale); imgmatrix.posttranslate(subx, suby); this.setimagematrix(imgmatrix); } /** * 按下操作 * * @param event */ public void mousedown(motionevent event) { mode = none; startx = event.getrawx(); starty = event.getrawy(); if (event.getpointercount() == 1) { // 如果两次点击时间间隔小于一定值,则默认为双击事件 if (event.geteventtime() - lastclicktime < double_click_time_space) { changesize(startx, starty); } else if (isbig) { mode = drag; } } lastclicktime = event.geteventtime(); } /** * 非第一个点按下操作 * * @param event */ public void mousepointdown(motionevent event) { startdistance = getdistance(event); if (startdistance > double_point_distance) { mode = zoom; } else { mode = none; } } /** * 移动操作 * * @param event */ public void mousemove(motionevent event) { if ((mode == drag) && (ismovex || ismovey)) { float[] xy = gettranslatexy(imgmatrix); float transx = 0; float transy = 0; if (ismovex) { endx = event.getrawx(); transx = endx - startx; if ((xy[0] + transx) <= limitx1) { transx = limitx1 - xy[0]; } if ((xy[0] + transx) >= limitx2) { transx = limitx2 - xy[0]; } } if (ismovey) { endy = event.getrawy(); transy = endy - starty; if ((xy[1] + transy) <= limity1) { transy = limity1 - xy[1]; } if ((xy[1] + transy) >= limity2) { transy = limity2 - xy[1]; } } imgmatrix.posttranslate(transx, transy); startx = endx; starty = endy; this.setimagematrix(imgmatrix); } else if (mode == zoom && event.getpointercount() > 1) { enddistance = getdistance(event); float dif = enddistance - startdistance; if (math.abs(enddistance - startdistance) > double_point_distance) { if (isbig) { if (dif < 0) { changesize(0, 0); mode = none; } } else if (dif > 0) { float x = event.getx(0) / 2 + event.getx(1) / 2; float y = event.gety(0) / 2 + event.gety(1) / 2; changesize(x, y); mode = none; } } } } /** * 鼠标抬起事件 */ public void mouseup() { mode = none; } /** * 图片放大缩小 * * @param x * 点击点x坐标 * @param y * 点击点y坐标 */ private void changesize(float x, float y) { if (isbig) { // 如果处于最大状态,则还原 imgmatrix.reset(); imgmatrix.postscale(scale, scale); imgmatrix.posttranslate(subx, suby); isbig = false; } else { imgmatrix.postscale(bigscale, bigscale); // 在原有矩阵后乘放大倍数 float transx = -((bigscale - 1) * x); float transy = -((bigscale - 1) * (y - topheight)); // (bigscale-1)(y-statusbarheight-suby)+2*suby; float currentwidth = primaryw * scale * bigscale; // 放大后图片大小 float currentheight = primaryh * scale * bigscale; // 如果图片放大后超出屏幕范围处理 if (currentheight > contenth) { limity1 = -(currentheight - contenth); // 平移限制 limity2 = 0; ismovey = true; // 允许在y轴上拖动 float currentsuby = bigscale * suby; // 当前平移距离 // 平移后,内容区域上部有空白处理办法 if (-transy < currentsuby) { transy = -currentsuby; } // 平移后,内容区域下部有空白处理办法 if (currentsuby + transy < limity1) { transy = -(currentheight + currentsuby - contenth); } } else { // 如果图片放大后没有超出屏幕范围处理,则不允许拖动 ismovey = false; } if (currentwidth > contentw) { limitx1 = -(currentwidth - contentw); limitx2 = 0; ismovex = true; float currentsubx = bigscale * subx; if (-transx < currentsubx) { transx = -currentsubx; } if (currentsubx + transx < limitx1) { transx = -(currentwidth + currentsubx - contentw); } } else { ismovex = false; } imgmatrix.posttranslate(transx, transy); isbig = true; } this.setimagematrix(imgmatrix); if (mcustommethod != null) { mcustommethod.custommethod(isbig); } } /** * 获取变换矩阵中x轴偏移量和y轴偏移量 * * @param matrix * 变换矩阵 * @return */ private float[] gettranslatexy(matrix matrix) { float[] values = new float[9]; matrix.getvalues(values); float[] floats = new float[2]; floats[0] = values[matrix.mtrans_x]; floats[1] = values[matrix.mtrans_y]; return floats; } /** * 获取两点间的距离 * * @param event * @return */ private float getdistance(motionevent event) { float x = event.getx(0) - event.getx(1); float y = event.gety(0) - event.gety(1); return floatmath.sqrt(x * x + y * y); } /** * @author administrator 用户自定义方法 */ public interface icustommethod { public void custommethod(boolean currentstatus); } }
imagevewactivity 这个用于测试的activity
import android.app.activity; import android.graphics.bitmap; import android.graphics.rect; import android.graphics.drawable.bitmapdrawable; import android.os.bundle; import android.view.motionevent; import android.view.view; import android.widget.linearlayout; import android.widget.textview; import android.widget.toast; import ejiang.boiler.imagecontrol.icustommethod; import ejiang.boiler.r.id; public class imageviewactivity extends activity { @override protected void oncreate(bundle savedinstancestate) { // todo auto-generated method stub super.oncreate(savedinstancestate); setcontentview(r.layout.common_image_view); findview(); } public void onwindowfocuschanged(boolean hasfocus) { super.onwindowfocuschanged(hasfocus); init(); } imagecontrol imgcontrol; linearlayout lltitle; textview tvtitle; private void findview() { imgcontrol = (imagecontrol) findviewbyid(id.common_imageview_imagecontrol1); lltitle = (linearlayout) findviewbyid(id.common_imageview_lltitle); tvtitle = (textview) findviewbyid(id.common_imageview_title); } private void init() { tvtitle.settext("图片测试"); // 这里可以为imgcontrol的图片路径动态赋值 // ............ bitmap bmp; if (imgcontrol.getdrawingcache() != null) { bmp = bitmap.createbitmap(imgcontrol.getdrawingcache()); } else { bmp = ((bitmapdrawable) imgcontrol.getdrawable()).getbitmap(); } rect frame = new rect(); getwindow().getdecorview().getwindowvisibledisplayframe(frame); int statusbarheight = frame.top; int screenw = this.getwindowmanager().getdefaultdisplay().getwidth(); int screenh = this.getwindowmanager().getdefaultdisplay().getheight() - statusbarheight; if (bmp != null) { imgcontrol.imageinit(bmp, screenw, screenh, statusbarheight, new icustommethod() { @override public void custommethod(boolean currentstatus) { // 当图片处于放大或缩小状态时,控制标题是否显示 if (currentstatus) { lltitle.setvisibility(view.gone); } else { lltitle.setvisibility(view.visible); } } }); } else { toast.maketext(imageviewactivity.this, "图片加载失败,请稍候再试!", toast.length_short) .show(); } } @override public boolean ontouchevent(motionevent event) { switch (event.getaction() & motionevent.action_mask) { case motionevent.action_down: imgcontrol.mousedown(event); break; /** * 非第一个点按下 */ case motionevent.action_pointer_down: imgcontrol.mousepointdown(event); break; case motionevent.action_move: imgcontrol.mousemove(event); break; case motionevent.action_up: imgcontrol.mouseup(); break; } return super.ontouchevent(event); } }
在上面的代码中,需要注意两点。一activity中要重写ontouchevent方法,将触摸事件传递到imagecontrol,这点类似于wpf中的路由事件机制。二初始化imgcontrol即imgcontrol.imageinit,注意其中的参数。最后一个参数类似于c#中的委托,我这里使用接口来实现,在放大缩小的切换时要执行的操作都卸载这个方法中。
common_image_view.xml 布局文件
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ejiang.boiler.imagecontrol android:id="@+id/common_imageview_imagecontrol1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/ic_launcher" /> <linearlayout android:id="@+id/common_imageview_lltitle" style="@style/reporttitle1" android:layout_alignparentleft="true" android:layout_alignparenttop="true" > <textview android:id="@+id/common_imageview_title" style="@style/title2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:text="报告" /> </linearlayout> </relativelayout>