Android App中实现可以双击放大和缩小图片功能的实例
先来看一个很简单的核心图片缩放方法:
publicstaticBitmapscale(Bitmapbitmap,floatscaleWidth,floatscaleHeight){ intwidth=bitmap.getWidth(); intheight=bitmap.getHeight(); Matrixmatrix=newMatrix(); matrix.postScale(scaleWidth,scaleHeight); Log.i(TAG,"scaleWidth:"+scaleWidth+",scaleHeight:"+scaleHeight); returnBitmap.createBitmap(bitmap,0,0,width,height,matrix,true); }
注意要比例设置正确否则可能会内存溢出,比如曾经使用图片缩放时遇到这么个问题:
java.lang.IllegalArgumentException:bitmapsizeexceeds32bits
后来一行行查代码,发现原来是scale的比例计算错误,将原图给放大了20多倍,导致内存溢出所致,重新修改比例值后就正常了。
好了,下面真正来看一下这个实现了放大和原大两个级别的缩放的模块。
功能有:
- 以触摸点为中心放大(这个是网上其他的代码没有的)
- 边界控制(这个是网上其他的代码没有的)
- 双击放大或缩小(主要考虑到电阻屏)
- 多点触摸放大和缩小
这个模块已经通过了测试,并且用户也使用有一段时间了,是属于比较稳定的了。
下面贴上代码及使用方法(没有写测试项目,大家见谅):
ImageControl类似一个用户自定义的ImageView控件。用法将在下面的代码中贴出。
importandroid.content.Context; importandroid.graphics.Bitmap; importandroid.graphics.Matrix; importandroid.util.AttributeSet; importandroid.util.FloatMath; importandroid.view.MotionEvent; importandroid.widget.ImageView; publicclassImageControlextendsImageView{ publicImageControl(Contextcontext){ super(context); //TODOAuto-generatedconstructorstub } publicImageControl(Contextcontext,AttributeSetattrs){ super(context,attrs); //TODOAuto-generatedconstructorstub } publicImageControl(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs,defStyle); //TODOAuto-generatedconstructorstub } //ImageViewimg; MatriximgMatrix=null;//定义图片的变换矩阵 staticfinalintDOUBLE_CLICK_TIME_SPACE=300;//双击时间间隔 staticfinalintDOUBLE_POINT_DISTANCE=10;//两点放大两点间最小间距 staticfinalintNONE=0; staticfinalintDRAG=1;//拖动操作 staticfinalintZOOM=2;//放大缩小操作 privateintmode=NONE;//当前模式 floatbigScale=3f;//默认放大倍数 BooleanisBig=false;//是否是放大状态 longlastClickTime=0;//单击时间 floatstartDistance;//多点触摸两点距离 floatendDistance;//多点触摸两点距离 floattopHeight;//状态栏高度和标题栏高度 BitmapprimaryBitmap=null; floatcontentW;//屏幕内容区宽度 floatcontentH;//屏幕内容区高度 floatprimaryW;//原图宽度 floatprimaryH;//原图高度 floatscale;//适合屏幕缩放倍数 BooleanisMoveX=true;//是否允许在X轴拖动 BooleanisMoveY=true;//是否允许在Y轴拖动 floatstartX; floatstartY; floatendX; floatendY; floatsubX; floatsubY; floatlimitX1; floatlimitX2; floatlimitY1; floatlimitY2; ICustomMethodmCustomMethod=null; /** *初始化图片 * *@parambitmap *要显示的图片 *@paramcontentW *内容区域宽度 *@paramcontentH *内容区域高度 *@paramtopHeight *状态栏高度和标题栏高度之和 */ publicvoidimageInit(Bitmapbitmap,intcontentW,intcontentH, inttopHeight,ICustomMethodiCustomMethod){ this.primaryBitmap=bitmap; this.contentW=contentW; this.contentH=contentH; this.topHeight=topHeight; mCustomMethod=iCustomMethod; primaryW=primaryBitmap.getWidth(); primaryH=primaryBitmap.getHeight(); floatscaleX=(float)contentW/primaryW; floatscaleY=(float)contentH/primaryH; scale=scaleX<scaleY?scaleX:scaleY; if(scale<1&&1/scale<bigScale){ bigScale=(float)(1/scale+0.5); } imgMatrix=newMatrix(); 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); } /** *按下操作 * *@paramevent */ publicvoidmouseDown(MotionEventevent){ mode=NONE; startX=event.getRawX(); startY=event.getRawY(); if(event.getPointerCount()==1){ //如果两次点击时间间隔小于一定值,则默认为双击事件 if(event.getEventTime()-lastClickTime<DOUBLE_CLICK_TIME_SPACE){ changeSize(startX,startY); }elseif(isBig){ mode=DRAG; } } lastClickTime=event.getEventTime(); } /** *非第一个点按下操作 * *@paramevent */ publicvoidmousePointDown(MotionEventevent){ startDistance=getDistance(event); if(startDistance>DOUBLE_POINT_DISTANCE){ mode=ZOOM; }else{ mode=NONE; } } /** *移动操作 * *@paramevent */ publicvoidmouseMove(MotionEventevent){ if((mode==DRAG)&&(isMoveX||isMoveY)){ float[]XY=getTranslateXY(imgMatrix); floattransX=0; floattransY=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); }elseif(mode==ZOOM&&event.getPointerCount()>1){ endDistance=getDistance(event); floatdif=endDistance-startDistance; if(Math.abs(endDistance-startDistance)>DOUBLE_POINT_DISTANCE){ if(isBig){ if(dif<0){ changeSize(0,0); mode=NONE; } }elseif(dif>0){ floatx=event.getX(0)/2+event.getX(1)/2; floaty=event.getY(0)/2+event.getY(1)/2; changeSize(x,y); mode=NONE; } } } } /** *鼠标抬起事件 */ publicvoidmouseUp(){ mode=NONE; } /** *图片放大缩小 * *@paramx *点击点X坐标 *@paramy *点击点Y坐标 */ privatevoidchangeSize(floatx,floaty){ if(isBig){ //如果处于最大状态,则还原 imgMatrix.reset(); imgMatrix.postScale(scale,scale); imgMatrix.postTranslate(subX,subY); isBig=false; }else{ imgMatrix.postScale(bigScale,bigScale);//在原有矩阵后乘放大倍数 floattransX=-((bigScale-1)*x); floattransY=-((bigScale-1)*(y-topHeight));//(bigScale-1)(y-statusBarHeight-subY)+2*subY; floatcurrentWidth=primaryW*scale*bigScale;//放大后图片大小 floatcurrentHeight=primaryH*scale*bigScale; //如果图片放大后超出屏幕范围处理 if(currentHeight>contentH){ limitY1=-(currentHeight-contentH);//平移限制 limitY2=0; isMoveY=true;//允许在Y轴上拖动 floatcurrentSubY=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; floatcurrentSubX=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轴偏移量 * *@parammatrix *变换矩阵 *@return */ privatefloat[]getTranslateXY(Matrixmatrix){ float[]values=newfloat[9]; matrix.getValues(values); float[]floats=newfloat[2]; floats[0]=values[Matrix.MTRANS_X]; floats[1]=values[Matrix.MTRANS_Y]; returnfloats; } /** *获取两点间的距离 * *@paramevent *@return */ privatefloatgetDistance(MotionEventevent){ floatx=event.getX(0)-event.getX(1); floaty=event.getY(0)-event.getY(1); returnFloatMath.sqrt(x*x+y*y); } /** *@authorAdministrator用户自定义方法 */ publicinterfaceICustomMethod{ publicvoidcustomMethod(BooleancurrentStatus); } }
ImageVewActivity这个用于测试的Activity
importandroid.app.Activity; importandroid.graphics.Bitmap; importandroid.graphics.Rect; importandroid.graphics.drawable.BitmapDrawable; importandroid.os.Bundle; importandroid.view.MotionEvent; importandroid.view.View; importandroid.widget.LinearLayout; importandroid.widget.TextView; importandroid.widget.Toast; importejiang.boiler.ImageControl.ICustomMethod; importejiang.boiler.R.id; publicclassImageViewActivityextendsActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ //TODOAuto-generatedmethodstub super.onCreate(savedInstanceState); setContentView(R.layout.common_image_view); findView(); } publicvoidonWindowFocusChanged(booleanhasFocus){ super.onWindowFocusChanged(hasFocus); init(); } ImageControlimgControl; LinearLayoutllTitle; TextViewtvTitle; privatevoidfindView(){ imgControl=(ImageControl)findViewById(id.common_imageview_imageControl1); llTitle=(LinearLayout)findViewById(id.common_imageview_llTitle); tvTitle=(TextView)findViewById(id.common_imageview_title); } privatevoidinit(){ tvTitle.setText("图片测试"); //这里可以为imgcontrol的图片路径动态赋值 //............ Bitmapbmp; if(imgControl.getDrawingCache()!=null){ bmp=Bitmap.createBitmap(imgControl.getDrawingCache()); }else{ bmp=((BitmapDrawable)imgControl.getDrawable()).getBitmap(); } Rectframe=newRect(); getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); intstatusBarHeight=frame.top; intscreenW=this.getWindowManager().getDefaultDisplay().getWidth(); intscreenH=this.getWindowManager().getDefaultDisplay().getHeight() -statusBarHeight; if(bmp!=null){ imgControl.imageInit(bmp,screenW,screenH,statusBarHeight, newICustomMethod(){ @Override publicvoidcustomMethod(BooleancurrentStatus){ //当图片处于放大或缩小状态时,控制标题是否显示 if(currentStatus){ llTitle.setVisibility(View.GONE); }else{ llTitle.setVisibility(View.VISIBLE); } } }); } else { Toast.makeText(ImageViewActivity.this,"图片加载失败,请稍候再试!",Toast.LENGTH_SHORT) .show(); } } @Override publicbooleanonTouchEvent(MotionEventevent){ switch(event.getAction()&MotionEvent.ACTION_MASK){ caseMotionEvent.ACTION_DOWN: imgControl.mouseDown(event); break; /** *非第一个点按下 */ caseMotionEvent.ACTION_POINTER_DOWN: imgControl.mousePointDown(event); break; caseMotionEvent.ACTION_MOVE: imgControl.mouseMove(event); break; caseMotionEvent.ACTION_UP: imgControl.mouseUp(); break; } returnsuper.onTouchEvent(event); } }
在上面的代码中,需要注意两点。一Activity中要重写onTouchEvent方法,将触摸事件传递到ImageControl,这点类似于WPF中的路由事件机制。二初始化imgControl即imgControl.imageInit,注意其中的参数。最后一个参数类似于C#中的委托,我这里使用接口来实现,在放大缩小的切换时要执行的操作都卸载这个方法中。
common_image_view.xml 布局文件
<?xmlversion="1.0"encoding="utf-8"?> <RelativeLayoutxmlns: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>