结合UIImageView实现图片的移动和缩放

系统 1503 0

<!--StartFragment-->

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

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

新建 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];

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

结合UIImageView实现图片的移动和缩放 结合UIImageView实现图片的移动和缩放

<!--StartFragment-->

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

要想识别手势( 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里。我这样做的理由,是因为这是最简单、最容易的实现方式。我参考过网上的几种实现方式,发现基本上都需要使用Quartz2D API,并且实现起来要复杂得多。最终从闭路电视监控系统中得到了启发(想象一下,安保人员通过移动鼠标控制镜头移动的场景)。

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

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 =[touch locationInView : 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张图片现在是一台苹果电脑):

结合UIImageView实现图片的移动和缩放

<!--StartFragment-->

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

<!--EndFragment-->

<!--EndFragment-->

结合UIImageView实现图片的移动和缩放


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论