您的位置:首页 > 产品设计 > UI/UE

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

2015-08-29 20:28 791 查看
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
运行结果如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: