iOS 轮播图

轮播图是App中常用的展示界面,主要用到的控件有UIScrollView和UIPageControl。难点有两处,一个是定时器自动轮播,一个是手动操作后不影响定器自动轮播

一、要达到的效果

效果动图.gif

二、逻辑分析

首先,轮播图需要轮播几张图片,就要往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);//偏移量为第一张图
    }
    ...
}

这样问题就解决了。欢迎指点!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容