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>