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

2023-12-13 18:40

本文主要是介绍结合UIImageView实现图片的移动和缩放,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

因为种种原因,需要在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;

//重设imageViewframe

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

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

 

 

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

这篇关于结合UIImageView实现图片的移动和缩放的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/489529

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现