iPhone开发之UIScrollView滚动组件的使用(七)利用NSTimer计时器和UIPageControl组件代码实现图片轮播器

时间:2022-03-30 20:35:56
1、分页效果  pagingEnabled实现分页效果,是根据UIScrollView的宽度进行分页的,和内容无关。
2、bringSubviewToFront:  父控件调用此方法可以将某个子控件调到最前面,把其他组件给覆盖掉。
3、UIPageControl:分页指示器控件。介绍。 
(必须放在View内,因为它不需要滚动,所以和UIScrollView位置是兄弟关系)
 在XIB操作时,Tint color:
其它页小点的颜色。  Current color:当前页小点的颜色。
为了让UIPageControl分页指示器和UIScrollView滚动组件联系起来,要进行以下步骤:
(1)为分页指示器设置总页数。
(2)在滚动的时候把UIScrollView获取的当前页数设置给分页指示器当前是第几页。
4、图片轮播器的实现思路:
(1)添加UIScrollView。
(2)动态向UIscrollView中添加图片框(横向)。
(3)设置UIScrollView的contentSize实现滚动,实现横向滚动。
(4)实现分页。
(5)实现分页指示器UIPageControl。
(6)通过NStimer实现自动滚动。
5、图片轮播器实现的具体步骤:
(1)创建一个UIScrollView,设置宽300,高为130(与每张图片大小一致)
(2)向UIscrollView中添加内容(需要进行滚动的内容,添加到UIScrollView的子控件集合中)
-循环添加5个UIImageView控件,设置图片,设置frame
(3)设置UIscrollView的contentSize的width为5个图片宽度的总和,上下不需要滚动所以height设置为0;
(4)去掉水平滚动条
- self.scrollView.showsHorizontalScrollIndicator = NO;
(5)实现自动分页
— self.scrollView.pagingEnabled = YES;
—  问题:设置完pagingEnabled = YES 以后,scrollView是怎么知道该如何分页的?
答:按照UIScrollView组件自身的宽度来实现分页的。 UIScrollView的宽度就是内页的大小。
(6)显示分页指示器
注意点:
— 通过UIPageControl  分页控制器来实现
— 拽一个UIPageControl放到控制器的View中,不要放到UIScrollView中,否则就一起滚动了。
— 设置UIPageControl的Tint  color(其它分页的颜色)和Current Page(当前页颜色)属性颜色。
— 当把UIpageControl添加到控制器的View中的时候,这个控件和UIScrollView根本没有任何关系,所以没有分页指示功能。
(7)实现分页指示器总页数、当前页
— 总页数:UIpageControl组件的numberOfPages属性
  self.pageControl.numberOfPages = imageCount;
— 当前页:currentPage属性
  self.pageControl.currentPage= 0;
  注意:
— 在viewDidLoad中设置总页数
—  在(void)scrollViewDidScroll:  代理方法中设置当前页
—  设置当前页的思路: 通过当前滚动的偏移值来计算出当前滚动到第几页了。
(8)通过定时器(NSTimer)实现自动滚动
— 在viewDidLoad中启动定时器
启动定时器的两种方法:
1> 调用timerWithXxx创建的timer,把这个timer对象手动添加到“消息循环”中才能启动。
2> 调用scheduledTimerWithXxx创建的timer,自动启动(创建完毕后自动启动)
(9)在定时器的方法中实现滚动
思路:
1> 通过UIPageControl获取当前页数,并让页数加一;
2> 根据加一以后的页数乘以每页的宽度(每张图片的宽度)计算出contentOffset.x的偏移值
3> 手动设置偏移值,实现滚动(通过动画的方式设置)
(10)解决Bug
— Bug:当拖拽UIscrollView的时候,保持一段时间不松手的时候,一旦松手UIScrollView就会连续滚动多次。
— 解决思路:在即将拖拽的时候停止计时器,拖拽完毕后再打开一个计时器。
****停止计时器:调用NStimer对象的invalidate(当某个计时器被停止以后,就无法再重用了,下一次必须再重新创建一个新的计时器)。[self.timer  invalidate];
— (void)scrollViewWillBeginDragging
— (void) scrollViewDidEndDragging
(11)解决Bug
— Bug:当单击(拖拽)界面上的某个其它控件的时候,UIScrollView停止滚动的问题。
— 产生Bug的原因:
— 当前处理UI界面的只有一个线程,当这个线程处理UI的拖动事件的时候就没有能力再去处理滚动事件了。
— 注意: 处理UI界面的只能是一个线程。所以,处理UIScrollView的滚动和其他控件的拖拽,只能用同一个线程。如果多个线程都可以操作UI那么就会造成混乱的问题。
解决思路:提高处理滚动timer的优先级。
注意:所有控件的默认的优先级都是NSRunLoopCommonModes,但是网络和计时器对象默认的优先级要比控件的优先级低是NSDefaultRunLoopMode,所以在这里要把计时器的优先级调整为与控件一样的优先级NSRunLoopCommonModes。这样,对于优先级相同的控件,程序会分时间片轮流给他们 ,也就相当于他们在轮流获取线程的处理。
5、知识点
只要将UIScrollView的pagingEnabled属性设置为YES,UIScrollView就会被分割成多个独立的页面,里面的内容就能分页显示。
一般会配合UIPageControl增强分页效果,UIPageControl常用属性如下:
(1)一共有多少页
@property (nonatomic) NSInteger numberOfPages;
(2)当前显示的页码
@property(nonatomic)NSInteger currentPage;
(3)只有一页时是否需要隐藏页码指示器
@property(nonatomic)BOOL hidesForSinglePage;
(4)其他页码指示器的颜色
@property(nonatomic,retain)UIColor *  pageIndicatorTintColor;
(5)当前页码指示器的颜色
@property(nonatomic)UIColor * currentPageIndicatorTintColor;
6、步骤中具体问题的解决方法:
(一)让UIPageControl分页指示器与UIScrollView关联起来
UIPageControl默认情况下与UIScrollView一点关系都没有。想要让UIPageControl实现分页指示器的功能,需要以下两步:
1> 设置总页数numberOfPages
2> 设置当前页currentPage


**设置当前页的思路:
1> 在该方法中根据UIScrollView滚动到第几张图片,然后设置PageControl当前第几个点显示为currentPage
2> 当前页= (当前滚动的偏移contentOffset.x+半个图片的宽度)/每张图片的宽度
**问题:为什么要用偏移加半个长度?
设想:
    如果contentOffset.x小于半个长度,那么加上半个长度以后除以图片宽度结果还是0
    如果contentOffset大于半个长度,那么加上半个长度以后除以一个宽度,结果还是1
公式代码: int page=(scrollView.contentOffset.x+0.5*scrollView.frame.size.width)/scrollView.frame.size.width;
self.pageControl.currentPage=page;
参考代码:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  int page = (scrollView.contentOffset.x+0.5*scrollView.frame.size.width)/scrollView.frame.size.width;
self.pageControl,currentPage=page;

}

(二)定时器
(1)两种不同的定时器
1>NSTimer(时间间隔比较大,大于一秒,几秒)
2>CADdisplayLink(时间间隔比较小,0.0几秒等)
 (2)创建、启动定时器代码参考:
// 方式一:  创建完成后就自动启动循环
[NSTimer scheduledTimerWithTimerInterval: 0.5  target: self  
selector:@selector(nextImage)  userInfo: nil  repeats:YES ];


// 方式二:
// 创建NSTimer 对象
NSTimer *timer=[NSTimer timerWithTimeInterval:1.0 target: self
selector:@selector(test1) userInfo:nil  repeats:YES];
// 将刚创建的NSTimer对象加到消息循环中,这样就会自启动定时器
NSRunLoop *runLoop= [NSRunLoop currentRunLoop];
[runRoop addTimer: timer forMode: NSRunLoopCommonModes];


// 方式三:
// 创建计时器对象
NSTimer *timer=[NSTimer timerWithTimeInterval:1.0 target:self  selector:@selector(test1) userInfo:nil  repeats:YES];
[timer fire]; // 调用一次,执行一次
[timer fire]; // 调用一次,执行一次
注意:Interval指时间间隔。  target:对象,即每隔一个间隔执行哪个对象中的方法    test1:target对象所属类中实现的方法。 repeats:YES  表示循环执行   一般target:self  就是把对象设为自身的控制器对象 ,所以就必须在控制器的.m文件中添加对应的test1方法。

(三)切换下一张图片的方法
//  在这个方法中,根据当前的页数,来计算当前应该设置的偏移量contentOffset的值
-(void)nextImage
{
     // 获取当前的页数
NSInteger  page=self.pageControl.currentPage;
  if(page==self.pageControl.numberOfPages-1)
{
    page=0;
}  else
{
   page++;
}

}

//  根据当前是第几页,计算出偏移量contentOffset的值
CGFloat  x= self.scrollView.frame.size.width*page;
// 通过代码设置contentOffset偏移,实现自动滚动
//  非动画方式
//self.scrollView.contentOffset = CGPointMake(x,0);
// 动画方式
[self.scrollView  setContentOffset: CGPointMake(x,0)  animated: YES];


-------------------------- 下面这种思路不好, 有时候有 bug ----------------------
当手动拖拽滚动的时候, 此时 contentOffset.x 可能并不是一个完整的宽度, 所以会造成最终计算出来的 contentOffset.x 不是一个完整页的宽度
//直接获取现有的 offset.x 然后加上一个图片的宽度
    CGFloat offsetX = self.scrollView.contentOffset.x;
    offsetX += self.scrollView.frame.size.width;
    if (offsetX >= self.scrollView.contentSize.width) {
        offsetX = 0;
    }
    //self.scrollView.contentOffset = CGPointMake(offsetX, 0);
    [self.scrollView setContentOffset:CGPointMake(offsetX, 0) animated:YES];

(四)解决第一个Bug:拖拽住不松手,一旦松手就会滚动很多页。
参考代码:
// 在即将拖拽之前
-(void)scrollViewWillBeginDragging: (UIScrollView *)scrollView
{
  // 停止计时器
  [self.timer  invalidate];
  self.timer = nil;
}


// 在拖拽完毕后再启动计时器
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView  willDecelerate: (BOOL) decelerate
{
// 重新创建一个计时器对象
  self.timer= [NSTimer scheduleTimerWithTimeInterval: 2 target:self selector:@selector(nextImage) userInfo: nil  repeats:YES];

//获取当前处理事件的消息循环
  NSRunLoop *runLoop=[NSRunLoop currentRunRoop];
  
//设置timer的优先级
 [runLoop addTimer:timer  forMode: NSRunRoopCommonModes];
}

(五)解决第二个Bug:当点击或拖拽别的控件时,停止滚动;
参考代码:
 ** 解决: 提高处理滚动的timer的优先级。
每次创建完毕timer控件的时候, 都设置一下timer的优先级

1. 在 viewDidLoad 中第一次创建完毕 timer 的时候修改一次优先级
// 获取当前线程处理事件的消息循环
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 设置timer的优先级
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];

// 2. 在scrollViewDidEndDragging:中重新创建timer 对象后再修改一次优先级
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
 {
// 重新创建一个计时器对象
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(nextImage) userInfo:nil repeats:YES];
 
    // 获取当前线程处理事件的消息循环
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    // 设置timer的优先级
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
 }


注意:
严格意义上讲 CADisplayLink 并不是计时器控件, 它是与显示器刷新频率一致的。比如显示的刷新频率是60赫兹, 那么 CADisplayLink 就会每秒钟执行60次。正是因为这个原因所以有时也把 CADisplayLink 当做计时器控件来使用。

NSTimer叫做“定时器”,它的作用如下
在指定的时间执行指定的任务
每隔一段时间执行指定的任务

调用下面的方法就会开启一个定时任务
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget 
selector:(SEL)aSelector 
userInfo:(id)userInfo 
repeats:(BOOL)yesOrNo;

每隔ti秒,调用一次aTarget的aSelector方法,yesOrNo决定了是否重复执行这个任务

通过invalidate方法可以停止定时器的工作,一旦定时器被停止了,就不能再次执行任务。只能再创建一个新的定时器才能执行新的任务

- (void)invalidate;

代码验证实例如下:

新建一个simple view的工程:

在工程的目录下新建一个文件夹,把图片资源拖拽复制进来

编辑控制器的.h文件如下:

//
// ViewController.h
// 图片轮播器
//
// Created by apple on 15/8/29.
// Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView * scrollView;
@property (nonatomic, strong) UIPageControl * pageControl;
@property (nonatomic, strong) UITextView * textView;
@property (nonatomic, strong) NSTimer * timer;
@end
编辑控制器的.m文件,代码如下:

//
// ViewController.m
// 图片轮播器
//
// Created by apple on 15/8/29.
// Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "ViewController.h"
#define WIDTH [UIScreen mainScreen].bounds.size.width
#define HEIGHT [UIScreen mainScreen].bounds.size.height
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];

// 新建一个UIScrollView组件
CGRect scrollRect = CGRectMake(0, 0, WIDTH, HEIGHT/3.5);
self.scrollView = [[UIScrollView alloc] initWithFrame:scrollRect];
// 因为图片比较大,所以设图片框大小与UIScrollView大小相同
for (int i = 0 ; i<5 ; i++)
{
UIImageView *image = [[UIImageView alloc] initWithFrame:CGRectMake(i*WIDTH, 0, WIDTH, HEIGHT/3.5)];
image.image = [UIImage imageNamed:[NSString stringWithFormat:@"img_%02d.png",i+1]];
[self.scrollView addSubview:image];
}
self.scrollView.showsHorizontalScrollIndicator = NO; // 取消UIScrollView组件的水平滚动条
self.scrollView.pagingEnabled = YES; // 为UIScrollView组件滚动内容时设置分页效果
self.scrollView.bounces = NO; // 设置UIScrollView组件取消弹性设置
self.scrollView.contentSize = CGSizeMake(WIDTH*5, 0); // 将内容的大小告知UIScrollView组件
self.scrollView.delegate = self; // 设置滚动组件的代理类为当前控制器对象所属的类
[self.view addSubview:self.scrollView];

// 在当前控制器vew中再添加一个UIPageControl即分页指示器
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, 0, WIDTH/3, 20)];
self.pageControl.center = CGPointMake(WIDTH/2, HEIGHT/5); // 设置分页器的位置
self.pageControl.pageIndicatorTintColor = [UIColor blueColor]; // 设置其它页指示器圆点的颜色
self.pageControl.currentPageIndicatorTintColor = [UIColor greenColor]; // 设置当前页指示器圆点的颜色
self.pageControl.numberOfPages = 5; // 设置分页器的页数
self.pageControl.currentPageIndicatorTintColor = 0; // 设置当前是第几页
[self.view addSubview:self.pageControl];

// 添加一个计时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextImage) userInfo:nil repeats:YES];
// 先获取当前的消息循环,利用消息循环设置计时器对象的优先级和控件的优先级相同
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:self.timer forMode:NSRunLoopCommonModes];

// 在下方添加一个TextVIew组件
self.textView = [[UITextView alloc] initWithFrame:CGRectMake(10, self.scrollView.frame.size.height+20, WIDTH-20, HEIGHT/3)];
self.textView.text = @"都说天涯与海角,只隔了心得距离,若是心在,天涯海角近在咫尺;丈量过后才知,隔着思念的墙,怎么翻越,都是细碎的忧伤,都是无尽的彷徨;原来思念亦如雨亦如风,淋漓在每一处,斑驳着经年,记忆凌乱不堪,踌躇万千过后,才暮然明白,那难以穿越的网,是你今世种下的蛊!\n日有所思,夜有所梦,昨夜小楼又东风,花开东墙,冥冥中还在想你,总以为不联系,便可随时间淡忘,原来岁月久了,思念越发浓郁,越发的思念,那年你种下的蠱毒,无药可解,深入骨髓,你的模样清晰的如在眼前,依旧未改,亦如当年,站在面前,“我会等你长大……”那声音那么的轻柔,似水般划过,拂过这季的心湖。";
self.textView.tintColor = [UIColor blueColor];
self.textView.textColor = [UIColor greenColor];
self.textView.font = [UIFont systemFontOfSize:18];
[self.view addSubview:self.textView];

}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

// 实现滚动组件UIScrollView组件协议内的滚动方法

//当开始拖动时会执行以下方法
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// 解决第一个Bug:按住滚动内容拖拽或不松手,一旦松手就会滚动好多次。解决方法当开始拖动时令计时器失效。当拖动结束后再新建一个计时器
[self.timer invalidate];
// 计时器一旦失效,就不能再使用了。所以赋给它一个空指针
self.timer = nil;
}

// 在拖动的过程中会执行这个方法
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// 先获根据内容的偏移位置,获取当前的页数
int page =( scrollView.contentOffset.x+scrollView.frame.size.width*0.5)/ scrollView.frame.size.width;

// 在拖动的过程中把当前的页码告知分页控制器组件
self.pageControl.currentPage = page;
}

// 在拖动结束后会执行这个方法
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// 当拖动结束后再新建一个计时器, 第一个Bug解除了
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextImage) userInfo:nil repeats:YES];

// 解决第二个BUg:当拖动点击别的控件时。将停止滚动,这是由于单线程执行,而计时器对象和网络对象的优先级小于控件的优先级,所以当拖动控件时,就不会再执行NSTimer计时器对象的操作。解决方法如下:每当新建一个NSTimer计时器,就先获取当前的消息循环,并在消息循环中设置它的优先级和控件的优先级相同,这时程序会分时间片给优先级相同的对象;
NSRunLoop *runRoop = [NSRunLoop currentRunLoop]; // 获取当前的消息循环

// 利用消息循环为timer对象设置优先级和控件的优先级相同
[runRoop addTimer:self.timer forMode:NSRunLoopCommonModes];

}

// 添加计时器的监听方法
-(void)nextImage
{
NSLog(@"timer计时器======");
// 获取当前的页数
int page = self.pageControl.currentPage;
if (page == self.pageControl.numberOfPages-1) {
page = 0;
}else{
page++;
}

// 根据当前的页数计算内容偏移量contentOffset的大小
// 通过代码设置contentOffset偏移,实现自动滚动
// 非动画方式
//self.scrollView.contentOffset = CGPointMake(page*self.scrollView.frame.size.width, 0);

// 通过动画方式
[self.scrollView setContentOffset:CGPointMake(page*self.scrollView.frame.size.width, 0) animated:YES];
}

@end
运行结果如下:

iPhone开发之UIScrollView滚动组件的使用(七)利用NSTimer计时器和UIPageControl组件代码实现图片轮播器