轮播图是App中常用的展示界面,主要用到的控件有UIScrollView和UIPageControl。难点有两处,一个是定时器自动轮播,一个是手动操作后不影响定器自动轮播
一、要达到的效果
二、逻辑分析
首先,轮播图需要轮播几张图片,就要往scrollView上放几张对应的图片,这里定为3张。图片大小宽度和ScrollView的frame一样,便于翻页。
然后,当图片从头轮播时,到第三张,此时图片应该跳到第一张才能实现循环,但是要保证流畅的话,就不能直接设置scrollView的contentOffset为(0,0)。解决办法是,在scrollView的最后再加一张ImageView,上面放第一张图片,这样的话当轮播到第四张ImageView(视觉效果是第一张图片)时,再设置scrollView的contentOffset为(0,0),这样视觉效果就流畅了
第三,因为需要往两个方向轮播,所以也要在scrollView的开头加一个ImageView,上面放第三张图片,并设置当轮播到这里时设置scrollView的contentOffset为倒数第二张imageView的位置。
综上:要实现三张图片的轮播,ScrollView的contentSize的宽度为5张图片宽度,第一张ImageView放第三张图片,最后一张ImageView放第一张图片,其他的按顺序放图片。以上能实现的是手指滑动的轮播,还有自动轮播和手指滑动后自动轮播的稍复杂功能,但是核心原理还是上面的原理。直接上代码,更合味
三、代码分析
添加UIScrollView、NStimer和UIPageControl控件
@property (nonatomic)UIScrollView *scrollView;
@property(nonatomic,strong)UIPageControl * pageController;//页面控制器
@property(nonatomic,strong)NSTimer * timer;//计时器
定义几个宏
#define imageCount 3//图片的张数
//当前设备的屏幕宽度
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
//当前设备的屏幕高度
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
初始化控件,并将图片添加上去
//初始化定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
//添加scrollView
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 250)];
self.scrollView.contentSize = CGSizeMake((imageCount + 2)*kScreenWidth, 0);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.bounces = NO;
self.scrollView.scrollEnabled = YES;
self.scrollView.contentOffset = CGPointMake(1*kScreenWidth, 0);
self.scrollView.delegate = self;
[self.view addSubview:self.scrollView];
//添加图片
for (int i = 0; i < imageCount+2; i++) {
if (i == 0) {
UIImageView * imageV = [[UIImageView alloc] initWithFrame:CGRectMake(i*kScreenWidth, 0, kScreenWidth, 250)];
[self.scrollView addSubview:imageV];
imageV.image = [UIImage imageNamed:@"13.jpg"];
} else if(i == imageCount + 1){
UIImageView * imageV = [[UIImageView alloc] initWithFrame:CGRectMake(i*kScreenWidth, 0, kScreenWidth, 250)];
[self.scrollView addSubview:imageV];
imageV.image = [UIImage imageNamed:@"11.jpg"];
}else{
UIImageView * imageV = [[UIImageView alloc] initWithFrame:CGRectMake(i*kScreenWidth, 0, kScreenWidth, 250)];
[self.scrollView addSubview:imageV];
imageV.image = [UIImage imageNamed:[NSString stringWithFormat:@"1%d.jpg",i]];
}
}
//在scrollView上添加page
self.pageController = [[UIPageControl alloc] init];
[self.view addSubview:self.pageController];
self.pageController.frame = CGRectMake(kScreenWidth/2 - 50, 250 - 20, 100, 25);
self.pageController.numberOfPages = imageCount;
self.pageController.pageIndicatorTintColor = [UIColor whiteColor];
self.pageController.currentPageIndicatorTintColor = [UIColor cyanColor];
self.pageController.currentPage = 0;
滑动ScrollView时,要设置页码控件PageControl的改变,还要设置在最后一张和第一张ImageView时要做跳转,这些都可以在代理方法中去实现。遵循代理UIScrollViewDelegate
在只要ScrollView滑动时就会走的代理方法中设置PageControl的改变
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//当scrollView滑动时,设置page
CGFloat scroll = scrollView.contentOffset.x/kScreenWidth;
NSInteger number = (NSInteger)scroll;//偏移了几个屏幕宽的距离
if (number == imageCount+1||(number == imageCount&&scroll - number > kScreenWidth/2)) {//如果偏移为最大值或将要到最大值
self.pageController.currentPage = 0;
}else if (number == 0||(number == 0&&scroll-number < kScreenWidth/2)){//如果偏移为最小值或者将要到最小值
self.pageController.currentPage = imageCount- 1;
}else{
if (scroll - number>kScreenWidth/2) {
self.pageController.currentPage = (number-1)+1;
}
if (scroll - number<=kScreenWidth/2) {
self.pageController.currentPage = (number-1);
}
}
}
在滑动结束减速时的代理方法中设置偏移
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
if (self.scrollView.contentOffset.x == (imageCount + 1)*kScreenWidth) {//手动滑到最后一张ImageView
self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
}else if (self.scrollView.contentOffset.x == 0*kScreenWidth) {//手动滑到第一张ImageView
self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);
}
}
以上实现的是可手动轮播,下面是自动轮播的实现
自动轮播的实现主要是在定时器的定时事件中实现功能。先创建一个全局变量,控制显示的图片角标,并循环使用
static NSInteger pageNumber = 0;//用于记录计时器计时循环
然后实现定时器的定时事件方法
-(void)timerAction{
if(pageNumber == imageCount){//手动滑滑到最大偏移,即显示的是视觉上第一张图
pageNumber = 0;
//跳到第一张图
self.scrollView.contentOffset = CGPointMake(kScreenWidth,0);
//然后滑到视觉上第二张图片
[UIView animateWithDuration:0.5 animations:^{
self.scrollView.contentOffset = CGPointMake((pageNumber+2)*kScreenWidth,0);
}];
}else if(pageNumber == 0){
//滑到视觉上第二张图
[UIView animateWithDuration:0.5 animations:^{
self.scrollView.contentOffset = CGPointMake((pageNumber+2)*kScreenWidth,0);
}];
}else {
[UIView animateWithDuration:0.5 animations:^{
self.scrollView.contentOffset = CGPointMake((pageNumber+2)*kScreenWidth,0);
}];
}
pageNumber++;
}
对于上面的方法中pageNumber的理解:pageNumber=0时,当前显示第一张图,但是将要显示第二张图;等于1时当前显示第二张图,将要显示第三张图,以此类推。当pageNumber=3,这时显示的是最后一张ImageView,也就是第一张图,所以将pageNumber设置为0,因为是要循环显示,所以将Scrollview的偏移设置为(kScreenWidth,0),即第一张图,此时将要显示的是第二张图,如此便完成了循环自动轮播
手动轮播和自动轮播都已实现,现在要实现的是自动+手动轮播。
思路:当ScrollView正要自动轮播到下一张图时,我刚好进行了手动操作,这样的话ScrollView到底是按自动播放到下一张还是按手动滑到下一张?感觉有冲突。所以这里的处理方式是,只要进行了手动操作,就将自动播放停止,手动操作完毕,在开启自动播放,这样就不冲突了
先写两个方法,开启定时器的方法和结束定时器的方法
-(void)beginAction{//开启定时器
//如果计时器已开启 先停止
if (self.timer) [self stopAction];
self.timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
-(void)stopAction{//结束定时器
[self.timer invalidate];
self.timer = nil;
}
首先,在ScrollView开始被拖拽时停止定时器,实现ScrollView开始拖拽的代理方法
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
//结束计时
[self stopAction];
}
在拖拽完毕的时候开启定时器,可以在ScrollView的减速结束的代理方法添加开启定时器的代码(这个代理方法上面写过了,直接添加一行代码即可)。
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
if (self.scrollView.contentOffset.x == (imageCount + 1)*kScreenWidth) {//手动滑到最后一张ImageView
self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
}else if (self.scrollView.contentOffset.x == 0*kScreenWidth) {//手动滑到第一张ImageView
self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);
}
//启动定时器
[self beginAction];
}
手指滑动结束后,开始自动播放,此时是定时器的定时事件方法(timerAction)起作用,该播放哪一张图由pageNumber等于几决定,由于定时器停止时pageNumber的值,无法正确指示显示哪一张图了,所以再手动滑动时,应该同步改变pageNumber的值。在ScrollView的减速结束代理方法中加入代码
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
if (self.scrollView.contentOffset.x == (imageCount + 1)*kScreenWidth) {//手动滑到最后一张ImageView
self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
}else if (self.scrollView.contentOffset.x == 0*kScreenWidth) {//手动滑到第一张ImageView
self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);
}
//启动定时器
[self beginAction];
//拖拽结束后给记录轮播到第几张的变量赋值
NSInteger number = (NSInteger)self.scrollView.contentOffset.x/kScreenWidth;
if (number == imageCount+1){//在最后一张(向右才会到这里)
pageNumber = 0;
}else if(number == 0){//在scrollView的第一张(向左才会到这里)
pageNumber = imageCount -1;
}else{
pageNumber = number - 1;
}
}
至此,大功告成
四、demo下载
gitHub下载地址
五、补充
今天去面试,面试官说,在没有失焦的情况下,一直滑动,滑动到头,就会滑不动了。这种情况怎么处理?当时没有回答上来,也没考虑过,回家玩了玩以前的DEMO,确实没有考虑这种情况。但是经过反复验证,找到了一种解决办法。也许不是最简单的。先呈上,请品味。
思路:只要scrollView在动,就会走scrollViewDidScroll代理方法,那只要捕捉到偏移量最大和最小的瞬间,并在此瞬间将scrollView的偏移量设置到对应的位置就可以了。我发现一直拖拽的状态是走了scrollViewWillBeginDragging代理方法,但没有走scrollViewDidEndDragging代理方法,一旦走了scrollViewDidEndDragging代理方法,拖拽就结束。所以,我可以设置一个全局变量,记录拖拽的状态。有了拖拽的状态,我就可以在scrollViewDidScroll的方法中捕捉偏移量最大值和最小值。具体看下面实现。
首先添加一个属性,布尔值类型,用来记录是否在拖拽状态
@property(nonatomic,assign)BOOL isDraging;//是否拖拽
当然,应该给一个初始值,这是我的习惯
self.isDraging = NO;
然后给这个属性进行赋值
在scrollView的开始拖拽的代理方法中,修改isDraging属性的值为YES,表示已经进入拖拽状态
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
//结束计时
[self stopAction];
self.isDraging = YES;
}
在scrollView结束拖拽的代理方法中,修改isDraging属性的值为NO,表示已经结束拖拽状态
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
self.isDraging = NO;
}
然后在代理方法:scrollViewDidScroll中加入两个判断,即在拖拽状态下,滑动到最大值和滑动到最小值时,跳转到第一张图片位置(此位置偏移量为一张图片的宽度)和最后一张图片位置(此位置偏移量为3张图片宽度偏移量位置)
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (self.isDraging&&self.scrollView.contentOffset.x == 0) {
self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);//跳到了倒数第二张ImageView(即最后一张图)
}
if (self.isDraging&&self.scrollView.contentOffset.x == (imageCount + 1)*kScreenWidth) {
self.scrollView.contentOffset = CGPointMake(kScreenWidth,0);//偏移量为第一张图
}
...
}
这样问题就解决了。欢迎指点!