求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
结合UIImageView实现图片的移动和缩放
 

作者:kmyhy,发布于2012-6-28

 

因为种种原因,需要在iphone应用中实现图片查看功能,由于iphone屏幕支持多点触摸,于是是想到用“手势”来实现图片的实时缩放和移动。借鉴无所不在的internet网络资料之后,终于实现此一功能,过程如下。

为方便大家下载,示例代码已上传到资源:http://download.csdn.net/detail/kmyhy/4095890

一、 首先实现原图显示(不缩放)

新建MoveScaleImageView类,继承uiview。用于加载一个UIImage。它有两个主要的成员,一个UIImage对象用于指定一个内存图片,一个UIImageView控件用于显示图片。

@interface MoveScaleImageView :UIView {
 UIImage* originImage;
 UIImageView* imageView;
}
-(void)setImage:(UIImage*)_image;
@end
@implementation MoveScaleImageView
-(id)initWithFrame:(CGRect)frame{
 if (self=[super initWithFrame:frame]) {
 imageView=[[UIImageView alloc]init];
 [self addSubview:imageView];
 // 使图片视图支持交互和多点触摸
 [imageView setUserInteractionEnabled:YES];
 [imageView setMultipleTouchEnabled:YES];
 }
 return self;
}
-(void)dealloc{
 originImage=nil;
 imageView=nil;
 [super dealloc];
}
-(void)setImage:(UIImage *)_image{
 originImage=[[UIImage alloc]initWithCGImage:_image.CGImage];
 [imageView setImage:originImage];
 [imageView setFrame:CGRectMake(0, 0, _image.size.width, _image.size.height)];
// [imageView setNeedsLayout];
} 
@end 

最主要的就是setImage方法。

MoveScaleImageView的使用很简单。在ViewController中构造一个MoveScaleImageView,然后用一个加载了图片文件的UIImage对象设置其image成员:

UIImage* image=[UIImage imageNamed:@"df.jpg"];
MoveScaleImageView* [[MoveScaleImageView alloc]initWithFrame:
  CGRectMake(0, 44, 320, 436)];
[fileContent setImage:image];  

由于在这里我们没有对图片进行任何的缩放处理,对于小图片会位于屏幕的左上角,并在其他地方留下空白;对于尺寸大于屏幕的图片,则图片不能完全显示:

一、 识别手势(单点触摸与多点触摸)

要想识别手势(gesture),必须响应4个手势的通知方法(参考“iphone3开发基础教程”第13章的内容):

touchesBegan,touchesMoved,touchesEnded和touchesCancelled。

首先,我们先来考虑单点触摸情况,这比较简单一些。在单点触摸情况下,移动手指,imageView中的图片可以被拖动,这样,对于比较大的图片,我们可以通过拖动来浏览图片的各个部分,当然,对于能一次显示下全部的图片就不需要拖动了。

修改类MoveScaleImageView,在.h中增加一些声明:

@interface MoveScaleImageView :UIView {
 UIImage* originImage;
 UIImageView* imageView;
 CGPoint gestureStartPoint;//手势开始时起点
 CGFloat offsetX,offsetY;//移动时x,y方向上的偏移量
 CGFloat curr_X,curr_Y;//现在截取的图片内容的原点坐标
}
-(void)setImage:(UIImage*)_image;
-(void)moveToX:(CGFloat)x ToY:(CGFloat)y;
@end  

然后实现touchesBegan和touchesMoved方法。

touchesBegan方法比较简单,记录下手指第一次触摸的位置。因为任何一个拖动都必然有一个起点和终点。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
 UITouch *touch=[touches anyObject];
 gestureStartPoint=[touch locationInView:self];
// NSLog(@"touch:%f,%f",gestureStartPoint.x,gestureStartPoint.y);
}

然后是手指移动后回调的touchesMoved方法:

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
 UITouch* touch=[touches anyObject];
 CGPoint curr_point=[touch locationInView:self];
 //分别计算x,和y方向上的移动
 offsetX=curr_point.x-gestureStartPoint.x;
 offsetY=curr_point.y-gestureStartPoint.y;
 //只要在任一方向上移动的距离超过Min_offset,判定手势有效
 if(fabsf(offsetX)>= min_offset||fabsf(offsetY)>=min_offset){
 [self moveToX:offsetX ToY:offsetY];
 gestureStartPoint.x=curr_point.x;
 gestureStartPoint.y=curr_point.y;
 } 
} 

在这里我们做了一个简单的判断,只有手指移动了超过一定像素(min_offset常量)后,才识别为拖动手势,并调用moveToX方法。在这个方法中,需要不断的更新手指移动的坐标,因为这是一个连续的过程。

-(void)moveToX:(CGFloat)x ToY:(CGFloat)y{
 //计算移动后的矩形框,原点x,y坐标,矩形宽高
 CGFloat destX,destY,destW,destH;
 curr_X=destX=curr_X-x;
 curr_Y=destY=curr_Y-y;
 destW=self.frame.size.width;
 destH=self.frame.size.height;
 if (destX<0) {//左边界越界处理
 curr_X=destX=0;
 }
 if (destY<0) {//上边界越界处理
 curr_Y=destY=0;
 }
 if (destX+destW>originImage.size.width) {//右边界越界处理上面
 curr_X=destX=originImage.size.width-destW;
 }
 if (destY+destH>originImage.size.height) {//右边界越界处理
 curr_Y=destY=originImage.size.height-destH;
 }
 //创建矩形框为本fame
 CGRect rect = CGRectMake(destX, destY,
  self.frame.size.width, self.frame.size.height); 
    imageView.image=[UIImage imageWithCGImage:CGImageCreateWithImageInRect([originImage CGImage], rect)];  
} 

在这个方法中,我们采用了一种特殊的处理方式:截取大图片的一部分,并将截取部分显示在imageView里。我这样做的理由,是因为这是最简单、最容易的实现方式。我参考过网上的几种实现方式,发现基本上都需要使用Quartz2DAPI,并且实现起来要复杂得多。最终从闭路电视监控系统中得到了启发(想象一下,安保人员通过移动鼠标控制镜头移动的场景)。

我们设计了一个矩形框,用它作为模拟的镜头:

CGRect lensRect;//设置镜头的大小

同时还设计了一个全局变量用于记录图片缩放过程中的缩放倍率:

CGFloat scale;//缩放比例

当跟踪到手指移动时,让“镜头”做反向运动(为什么是反向运动?因为我们模拟的是“拖动”效果,而不是“跟踪”效果,二者是恰恰相反的)。并通过 UIImage imageWithCGImage:CGImageCreateWithImageInRect 方法,将镜头中的图像捕捉到imageView中。

这样,移动操作实际上转换成了计算矩形框的位置。当然,我们也要做好边界判断,否则当矩形框超出图片原来的范围时,会发生扭曲缩放的现象。

接下来看怎样识别多点触摸。识别单点触摸和多点触摸其实非常简单,判断touchesBegan的touches参数的count属性即可:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
 if ([touches count]==2) {//识别两点触摸,并记录两点间距离
 NSArray* twoTouches=[touches allObjects];
 originSpace=[self spaceToPoint:[[twoTouches objectAtIndex:0] locationInView:self]
 FromPoint:[[twoTouches objectAtIndex:1]locationInView:self]];
 }else if ([touches count]==1){
 UITouch *touch=[touches anyObject];
 gestureStartPoint=[touchlocationInView:self];
 }
} 

在上面的方法中,我们根据touches的count判断是否是单点触摸并进行分别的处理。对于2点触摸,我们记录了两指间的距离并记录在全局的CGFloat变量originSpace中。spaceToPoint方法是一个简单函数,使用中学中学过的3角函数计算2点间距离:

-(CGFloat)spaceToPoint:(CGPoint)first FromPoint:(CGPoint)two{//计算两点之间的距离
 float x = first.x - two.x;
 float y = first.y - two.y;
 return sqrt(x * x + y * y);
} 

在两点触摸中,需要识别2个手势:外向捏合、内向捏合。通常前者使图像放大,而后者可使图像缩小。

在touchesMoved方法中,这样处理:

if ([touches count]==2) {
 NSArray* twoTouches=[touches allObjects];
 CGFloat currSpace=[self spaceToPoint:[[twoTouches objectAtIndex:0] locationInView:self]
  FromPoint:[[twoTouches objectAtIndex:1]locationInView:self]];
 //如果先触摸一根手指,再触摸另一根手指,则触发touchesMoved方法而不是touchesBegan方法
 //此时originSpace应该是0,我们要正确设置它的值为当前检测到的距离,否则可能导致0除错误
 if (originSpace==0) {
 originSpace=currSpace;
 }
 if (fabsf(currSpace-originSpace)>=min_offset) {//两指间移动距离超过min_offset,识别为手势“捏合”
 CGFloat s=currSpace/originSpace;//计算缩放比例
 [self scaleTo:s]; 
}
 }else if([touches count]==1){
 ?? (省略了部分代码)
 }
 } 

先简单判断了是否为有效捏合(我们为此定义了一个常量min_offset),如果是,则计算手指有效移动长度和手势开始时的两指间距的商,以此作为缩放比例。然后调用scaleTo方法:

-(void)scaleTo:(CGFloat)x{
 scale*=x;
 //缩放限制:>=0.1,<=10
 scale=(scale<0.1)?0.1:scale;
 scale=(scale>10)?10:scale;
//重设imageView的frame
 [self moveToX:0 ToY:0];
}  

这里,为防止用户无限制的对图像进行“捏合”操作,我们限制了scale的值在0.1-10之间(当然你可以将这个阀值定义为常量)。然后调用了一个原地的移动操作,即前面的moveTo方法。然而为支持缩放下的图片移动,这个方法被我们更改了:

-(void)moveToX:(CGFloat)x ToY:(CGFloat)y{
 CGPoint point=CGPointMake(x, y);
 //重设镜头
 [self resetLens:point];
    imageView.image=[UIImage imageWithCGImage:CGImageCreateWithImageInRect([originImage CGImage], lensRect)]; 
[imageView setFrame:CGRectMake(0, 0, lensRect.size.width*scale,vlensRect.size.height*scale)];
} 

其中更多的代码被我们移到了另一个方法resetLens中:

-(void)resetLens:(CGPoint)point{//设置镜头大小和位置
 CGFloat x,y,width,height;
 //===========镜头初始大小=========
 width=self.frame.size.width/scale;
 height=self.frame.size.height/scale;
//===========调整镜大小不得超过图像实际大小==========
 if(width>originImage.size.width){
 width=originImage.size.width;
 }
 if (height>originImage.size.height) {
 height=originImage.size.height;
 }
 //计算镜头移动的位置(等比缩放)
 x=lensRect.origin.x-point.x/scale;
 y=lensRect.origin.y-point.y/scale;
 //左边界越界处理
 x=(x<0)?0:x;
 //上边界越界处理
 y=(y<0)?0:y;
//右边界越界
 x=(x+width>originImage.size.width)?originImage.size.width-width:x;
 //下边界越界处理
 y=(y+height>originImage.size.height)?originImage.size.height-height:y; 
//镜头等比缩放
 lensRect=CGRectMake(x, y, width, height);
} 

这些代码跟原来moveToX方法中的代码有些许的不同,主要是增加了对scale变量的引入,因为在缩放模式下,镜头的移动都是被scale系数缩放过的。通代码中的注释,我们不难理解整个代码。

这样,大图片经过“捏合”操作可以在屏幕上完全显示出来(上面原来基本看不清楚的第2张图片现在是一台苹果电脑):

当然,把小图片“捏合”放大成大图片也是可以的。此外通过手指的移动,能查看图片的不同部分。


相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

 
分享到
 
 
     


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...