学习目标
1.【理解】UIScrollView实现大图滚动
2.【理解】UIScrollView实现电台界面
3.【理解】UIScrollView的代理
4.【理解】常用属性、方法整理
5.【理解】UIScrollView实现缩放功能
6.【理解】无限循环的图片浏览器(转载内容)
一、UIScrollView实现大图滚动
移动设备的屏幕大小是极其有限的,因此直接展示在用户眼前的内容也相当有限。当展示的内容较多,超出一个屏幕时,用户可通过滚动手势来查看屏幕以外的内容,普通的UIView不具备滚动功能,不能显示过多的内容。
UIScrollView是一个能够滚动的视图控件,可以用来展示大量的内容(要显示的内容超过屏幕大小的时候),并且可以通过滚动查看所有的内容。
创建项目,拖拽一个UIScrollView控件拉满整个屏幕,然后再拖拽一个子控件UIImageView到这个控件上并设置图片和图片原始尺寸,这样我们只能通过屏幕看到一部分的图片内容。
将两个控件进行属性连线,并为UIScrollView设置必要属性,就可以实现大图滚动效果了。
ViewController.m文件
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; //滚动控件 @property (weak, nonatomic) IBOutlet UIImageView *imgView; //图片控件 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //实现滚动必须设置UIScrollView的contentSize属性,表示滚动范围 //设置滚动范围为图片原图大小 self.scrollView.contentSize = self.imgView.frame.size; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end
最终实现效果:
值得注意的是需要展示的内容必须添加到UIScrollView中(是他的子控件),而且必须为UIScrollView的contentSize属性设置一个超过UIScrollView本身尺寸的尺寸,才能实现滚动效果。
二、UIScrollView实现电台界面
UIScrollView必须熟悉的三个属性:
@property(nonatomic) CGPoint contentOffset; //这个属性用来表示UIScrollView滚动的位置 @property(nonatomic) CGSize contentSize; //这个属性用来表示UIScrollView内容的尺寸,滚动范围 @property(nonatomic) UIEdgeInsets contentInset; //这个属性能够在UIScrollView的4周增加额外的滚动区域
根据这三个属性,来实现一个简单的电台界面,首先创建项目,拖拽UIScrollView拉满屏幕,并添加一堆子控件最终让子控件超出UIScrollView范围。
设置好子控件的图片,并进行属性连线操作,主要UIImageView就连最底部一个就行。
拖拽两个UIView与UIScrollView同级,分别放置到顶部和底部,设置高度和透明度。为了效果明显,给UIView设置背景色。
ViewController.m文件
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; @property (weak, nonatomic) IBOutlet UIImageView *imgView; @end @implementation ViewController //隐藏顶部导航栏 - (BOOL)prefersStatusBarHidden { return YES; } - (void)viewDidLoad { [super viewDidLoad]; //根据控件的frame获取这个控件到原点的最大高度 CGFloat contentH = CGRectGetMaxY(self.imgView.frame); //设置滚动范围 self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, contentH); //设置内容初始偏移位置 self.scrollView.contentOffset = CGPointMake(0, -55); //设置内容外边距 self.scrollView.contentInset = UIEdgeInsetsMake(55, 0, 60, 0); } @end
最终实现效果:
三、UIScrollView的代理
在UIScrollView正在滚动或滚动到某个位置或者停止滚动时做一些特定的操作,需要为UIScrollView设置一个代理对象,监听UIScrollView的整个滚动过程。当UIScrollView发生一系列的滚动操作时, 会自动通知它的代理(delegate)对象,给它的代理发送相应的消息,让代理得知它的滚动情况,并做一些操作。
实现监听UIScrollView的步骤:
1.遵守代理协议<UIScrollViewDelegate>
2.实现代理方法
// 用户开始拖拽时调用,拖拽一次调用一次 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; // 滚动过程中重复调用N次 - (void)scrollViewDidScroll:(UIScrollView *)scrollView; // 用户结束拖拽时调用一次 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
3.为UIScrollView设置代理对象
@property(nullable,nonatomic,weak) id<UIScrollViewDelegate> delegate;
以第一个案例为例,监听UIScrollView的各种状态
#import "ViewController.h" @interface ViewController () <UIScrollViewDelegate>//遵守代理协议 @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; //滚动控件 @property (weak, nonatomic) IBOutlet UIImageView *imgView; //图片控件 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //实现滚动必须设置UIScrollView的contentSize属性,表示滚动范围 //设置滚动范围为图片原图大小 self.scrollView.contentSize = self.imgView.frame.size; //指定代理对象为当前控制器 self.scrollView.delegate = self; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } //实现代理方法 // 用户开始拖拽时调用,拖拽一次调用一次 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { NSLog(@"scrollViewWillBeginDragging"); } // 滚动过程中重复调用N次 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { NSLog(@"scrollViewDidScroll"); } // 用户结束拖拽时调用一次 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { NSLog(@"scrollViewDidEndDragging"); } @end
拖动一次滚动效果如下:
四、常用属性、方法整理
常用属性
//内容偏移量,当前显示的内容的顶点相对此控件顶点的x、y距离,默认为CGPointZero @property(nonatomic) CGPoint contentOffset; //控件内容大小,不一定在显示区域,如果这个属性不设置,此控件无法滚动,默认为CGSizeZero @property(nonatomic) CGSize contentSize; //控件四周边距,类似于css中的margin,注意边距不作为其内容的一部分,默认为UIEdgeInsetsZero @property(nonatomic) UIEdgeInsets contentInset; //控件代理,一般用于事件监听,在iOS中多数控件都是通过代理进行事件监听的 @property(nonatomic,assign) id<UIScrollViewDelegate> delegate; //是否启用弹簧效果,启用弹簧效果后拖动到边缘可以看到内容后面的背景,默认为YES @property(nonatomic) BOOL bounces; //是否分页,如果分页的话每次左右拖动则移动宽度是屏幕宽度整数倍,默认为NO @property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled; //是否启用滚动,默认为YES @property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled; //是否显示横向滚动条,默认为YES @property(nonatomic) BOOL showsHorizontalScrollIndicator; //是否显示纵向滚动条,默认为YES @property(nonatomic) BOOL showsVerticalScrollIndicator; //最小缩放倍数,默认为1.0 @property(nonatomic) CGFloat minimumZoomScale; //最大缩放倍数(注意只有maximumZoomScale大于minimumZoomScale才有可能缩放),默认为1.0 @property(nonatomic) CGFloat maximumZoomScale; //(状态)是否正在被追踪,手指按下去并且还没有拖动时是YES,其他情况均为NO @property(nonatomic,readonly,getter=isTracking) BOOL tracking; //是否正在被拖拽 @property(nonatomic,readonly,getter=isDragging) BOOL dragging; //是否正在减速 @property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating; //是否正在缩放 @property(nonatomic,readonly,getter=isZooming) BOOL zooming;
常用方法
//设置滚动位置,第二个参数表示是否启用动画效果 - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; //滚动并显示指定区域的内容,第二个参数表示是否启用动画效果 - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;
常用代理方法
//滚动事件方法,滚动过程中会一直循环执行(滚动中…) - (void)scrollViewDidScroll:(UIScrollView *)scrollView; //开始拖拽事件方法 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; //拖拽操作完成事件方法 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; //即将停止滚动事件方法(拖拽松开后开始减速时执行) - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; //滚动停止事件方法(滚动过程中减速停止后执行) - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; //开始缩放事件方法 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view; //缩放操作完成事件方法 - (void)scrollViewDidZoom:(UIScrollView *)scrollView; //返回缩放视图,注意只有实现这个代理方法才能进行缩放,此方法返回需要缩放的视图 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
五、UIScrollView实现缩放功能
UIScrollView不仅能滚动显示大量内容,还能对其内容进行缩放处理。也就是说,要完成缩放功能的话,只需要将需要缩放的内容添加到UIScrollView中。还是以第一个案例为例来演示实现图片的缩放。
实现缩放的基本步骤:
1.遵守代理协议<UIScrollViewDelegate>。
2.实现代理方法,返回需要被缩放的控件。
//当用户使用捏合手势时,会调用这个方法返回的控件就是需要进行缩放的控件 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
3.设置缩放的最小、最大比例。
@property(nonatomic) CGFloat minimumZoomScale; @property(nonatomic) CGFloat maximumZoomScale;
4.为UIScrollView设置代理对象。
@property(nullable,nonatomic,weak) id<UIScrollViewDelegate> delegate;
缩放过程中调用的代理方法
//缩放完毕的时候调用 - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view; //正在缩放的时候调用 - (void)scrollViewDidZoom:(UIScrollView *)scrollView;
实现代码:
#import "ViewController.h" @interface ViewController () <UIScrollViewDelegate>//遵守代理协议 @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; //滚动控件 @property (weak, nonatomic) IBOutlet UIImageView *imgView; //图片控件 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //实现滚动必须设置UIScrollView的contentSize属性,表示滚动范围 //设置滚动范围为图片原图大小 self.scrollView.contentSize = self.imgView.frame.size; //指定代理对象为当前控制器 self.scrollView.delegate = self; //指定最大最小缩放比例 self.scrollView.minimumZoomScale = 0.5; self.scrollView.maximumZoomScale = 3.5; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } //监听需要缩放的控件,当用户使用捏合手势时就自动调用 - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { //返回需要被缩放的控件 return self.imgView; } @end
效果如下图:
六、无限循环的图片浏览器(转载内容)
如何无限循环?
我们知道在UIScrollView中如果放置其他控件后,只要设置contentSize之后这些图片就可以滚动。如果要让图片无限循环那么只有两种办法,一种是无限循环叠加图片,另一种就是如果最后一张图片浏览完立即显示第一张图片。很明显第一种方法是不现实的,我们考虑使用第二种方式。其实使用第二种方式实现原理比较简单,只要在图片前后各放一张图片即可(此时共有n+2个图片在UIScrollView中)。例如我们有5张图片,只要使用7个UIImageView依次存放:图片5,图片1,图片2,图片3,图片4,图片5,图片1。当从图片1滚动到图片5时由于最后一张是图片1就给用户一种无限循环的感觉,当这张图完全显示后我们迅速将UIScrollView的contentOffset设置到第二个UIImageView,也就是图片1,接着用户可以继续向后滚动。当然向前滚动原理完全一样,当滚动到第一张图片(图片5)就迅速设置UIScrollView的contentOffset显示第6张图(图片5)。为了方便说明请看下面的示意图(注意示意图由于宽度有限只描述了3张图片显示的情景):
如何优化性能?
无限循环实现了,但是我们知道如果图片过多这些图片势必全部加载到内存,这是我们不愿意看到的,此时我们需要优化上面的方案。其实从上面的方案我们也可以看出端倪,我们完全没必要创建n+2个UIImageView,其实3个已经足够(事实上也可以用两个实现,大家不妨自己思考一下),只要一直保持显示中间的UIImageView,滚动时动态更改三个UIImageView的图片即可。例如三个UIImageView默认放图片5、图片1、图片2,当前显示中间的UIImageView,也就是图片1,。如果向后滚动那么就会显示图片2,当图片2显示完整后迅速重新设置三个UIImageView的内容为图片1、图片2、图片3,然后通过contentOffset设置显示中间的UIImageView,也就是图片2。继续向后看到图片3,当图片3滚动完成迅速重新设置3个UIImageView的内容为图片2、图片3、图片4,然后设置contentOffset显示中间的UIImageView,也就是图片3。当然,向前滚动原理完全一样,如此就给用户一种循环错觉,而且不占用过多内存。
下面给出具体的实现,在这个程序中除了UIscrollView我们还可以看到UIPageControl的使用,实现并不复杂。首先我们需要将图片信息存储到plist文件中(日后方便扩展),并且设置plist的key表示图片的名称,value代表对应的图片描述,这个描述我们需要展示在界面上方。具体内容如下:
imageInfo.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>0.jpg</key> <string>iphone 5s</string> <key>1.jpg</key> <string>iphone 5c</string> <key>2.jpg</key> <string>ipad min with retain</string> <key>3.jpg</key> <string>ipad air</string> <key>4.jpg</key> <string>ipod</string> <key>5.jpg</key> <string>ipod touch</string> <key>6.jpg</key> <string>mac book pro</string> <key>7.jpg</key> <string>mac book air</string> <key>8.jpg</key> <string>imac</string> </dict> </plist>
在程序中我们需要读取plist文件并加载对应的图片,这里我们将图片按顺序依次命名:0.jpg、1.jpg…8.jpg。我们的程序主要集中于自定义的KCMainViewController.m中,在这里我们声明1个UIScrollView和3个UIImageView用于显示图片,同时声明一个UILable显示图片描述信息,声明一个UIPageControl来显示当前图片页数,具体代码如下:
KCMainViewController.m
#import "KCMainViewController.h" #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 568 #define IMAGEVIEW_COUNT 3 @interface KCMainViewController ()<UIScrollViewDelegate>{ UIScrollView *_scrollView; UIImageView *_leftImageView; UIImageView *_centerImageView; UIImageView *_rightImageView; UIPageControl *_pageControl; UILabel *_label; NSMutableDictionary *_imageData;//图片数据 int _currentImageIndex;//当前图片索引 int _imageCount;//图片总数 } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; //加载数据 [self loadImageData]; //添加滚动控件 [self addScrollView]; //添加图片控件 [self addImageViews]; //添加分页控件 [self addPageControl]; //添加图片信息描述控件 [self addLabel]; //加载默认图片 [self setDefaultImage]; } #pragma mark 加载图片数据 -(void)loadImageData{ //读取程序包路径中的资源文件 NSString *path=[[NSBundle mainBundle] pathForResource:@"imageInfo" ofType:@"plist"]; _imageData=[NSMutableDictionary dictionaryWithContentsOfFile:path]; _imageCount=(int)_imageData.count; } #pragma mark 添加控件 -(void)addScrollView{ _scrollView=[[UIScrollView alloc]initWithFrame:[UIScreen mainScreen].bounds]; [self.view addSubview:_scrollView]; //设置代理 _scrollView.delegate=self; //设置contentSize _scrollView.contentSize=CGSizeMake(IMAGEVIEW_COUNT*SCREEN_WIDTH, SCREEN_HEIGHT) ; //设置当前显示的位置为中间图片 [_scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0) animated:NO]; //设置分页 _scrollView.pagingEnabled=YES; //去掉滚动条 _scrollView.showsHorizontalScrollIndicator=NO; } #pragma mark 添加图片三个控件 -(void)addImageViews{ _leftImageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; _leftImageView.contentMode=UIViewContentModeScaleAspectFit; [_scrollView addSubview:_leftImageView]; _centerImageView=[[UIImageView alloc]initWithFrame:CGRectMake(SCREEN_WIDTH, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; _centerImageView.contentMode=UIViewContentModeScaleAspectFit; [_scrollView addSubview:_centerImageView]; _rightImageView=[[UIImageView alloc]initWithFrame:CGRectMake(2*SCREEN_WIDTH, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; _rightImageView.contentMode=UIViewContentModeScaleAspectFit; [_scrollView addSubview:_rightImageView]; } #pragma mark 设置默认显示图片 -(void)setDefaultImage{ //加载默认图片 _leftImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",_imageCount-1]]; _centerImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",0]]; _rightImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",1]]; _currentImageIndex=0; //设置当前页 _pageControl.currentPage=_currentImageIndex; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentImageIndex]; _label.text=_imageData[imageName]; } #pragma mark 添加分页控件 -(void)addPageControl{ _pageControl=[[UIPageControl alloc]init]; //注意此方法可以根据页数返回UIPageControl合适的大小 CGSize size= [_pageControl sizeForNumberOfPages:_imageCount]; _pageControl.bounds=CGRectMake(0, 0, size.width, size.height); _pageControl.center=CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT-100); //设置颜色 _pageControl.pageIndicatorTintColor=[UIColor colorWithRed:193/255.0 green:219/255.0 blue:249/255.0 alpha:1]; //设置当前页颜色 _pageControl.currentPageIndicatorTintColor=[UIColor colorWithRed:0 green:150/255.0 blue:1 alpha:1]; //设置总页数 _pageControl.numberOfPages=_imageCount; [self.view addSubview:_pageControl]; } #pragma mark 添加信息描述控件 -(void)addLabel{ _label=[[UILabel alloc]initWithFrame:CGRectMake(0, 10, SCREEN_WIDTH,30)]; _label.textAlignment=NSTextAlignmentCenter; _label.textColor=[UIColor colorWithRed:0 green:150/255.0 blue:1 alpha:1]; [self.view addSubview:_label]; } #pragma mark 滚动停止事件 -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ //重新加载图片 [self reloadImage]; //移动到中间 [_scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0) animated:NO]; //设置分页 _pageControl.currentPage=_currentImageIndex; //设置描述 NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentImageIndex]; _label.text=_imageData[imageName]; } #pragma mark 重新加载图片 -(void)reloadImage{ int leftImageIndex,rightImageIndex; CGPoint offset=[_scrollView contentOffset]; if (offset.x>SCREEN_WIDTH) { //向右滑动 _currentImageIndex=(_currentImageIndex+1)%_imageCount; }else if(offset.x<SCREEN_WIDTH){ //向左滑动 _currentImageIndex=(_currentImageIndex+_imageCount-1)%_imageCount; } //UIImageView *centerImageView=(UIImageView *)[_scrollView viewWithTag:2]; _centerImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",_currentImageIndex]]; //重新设置左右图片 leftImageIndex=(_currentImageIndex+_imageCount-1)%_imageCount; rightImageIndex=(_currentImageIndex+1)%_imageCount; _leftImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",leftImageIndex]]; _rightImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",rightImageIndex]]; } @end
在上面的代码中需要提醒大家的是一定要谨慎在滚动时进行相关操作,前面我们说过滚动事件会循环执行十分消耗性能,因此如果能不在其中操作的话尽可能不要在这个方法中进行相关操作,例如在上面的代码中我们的核心逻辑主要集中在滚动停止事件中,这个事件在一次滚动操作中只需要执行一次。
运行效果: