美文网首页iOS记录篇iOS开发资源ios
iOS-无限循环轮播器(注释详细到没有之一)

iOS-无限循环轮播器(注释详细到没有之一)

作者: 小小小阿博er | 来源:发表于2016-06-25 14:20 被阅读2382次

    Bg:

    1)有一段时间没有写文章了,最近事儿比较多,今天有人在技术群里面问我使用UIScrollview实现无限循环轮播的思想(3个UIImageView实现),我当时给了他一篇博客,不过好像这位朋友看的不是很懂,所以我写了一个小Demo打算写这篇文章去讲解下,帮助有需要的朋友们,所以我尽量把能写的注释都写详细了,把思想写全面了,让大家看一遍基本就明白了,里面关于如何好的封装一个控件的细节这里就不实现了,以讲解实现为主哦
    2)另外这种循环利用的思想也是面试可能会问到的,说不定还是加分项哦
    3)另外这个文章说的是3个UIImageView,其实2个imageview完全可以实现(点击这里看2个imageview实现),实时判断向左向右,然后改变imageview的frame的x/y即可,当然还可以完全使用collectionview去实现,这个也比较流行(点击这里看collectionview实现)

    • 先看下效果图
    kobe.gif
    • 这个效果图也没什么特别,大家都看到过无数次了,包括这个无线轮播,大家想必也都了解过,所以这次我们不实现什么特别的效果,主要是通过这个小功能,给有需要的朋友讲解下无线轮播思想

    使用3个imageview实现无线轮播的大致原理

    • 将3个imageview添加到scrollview上面,scrollview的contensize是3个imageview的宽度,设置scrollview一开始初始的偏移量为一个imageview宽度,因为里面有3个UIImageView,所以scrollview默认显示的就是中间的那个imageview,并且关键就是让屏幕显示的始终就是中间的这个imagview
    • 使用3个imageview来回更换图片,并在每一次更换图片后立即再设置scrollview偏移量还为一个imagview的宽度,也就是让scrollview滚动后再滚回原来默认的位置,这样就可以达到始终显示中间那个imageview的效果
    • 看到过其他博客里面有这样描述过这个原理
    ps:例如要使用三个UIImageView循环显示5张图片
    1)由于中间的imageview是显示在屏幕上的,它需要在启动时默认显示第1张图片,那么左边的imagview
    自然就需要显示最后一张图片,右边的imagview自然要显示第二张图片了.所以一开始肯定默认放图片5、
    图片1、图片2,当前显示中间的UIImageView,也就是图片1
    2)如果用户手指向左滑动,那么就会显示图片2,当图片2显示完整后迅速重新设置左中右三个UIImageView
    的内容为图片1、图片2、图片3,然后马上设置contentOffset再次为一开始默认的一个imageview宽度,
    让它滚回默认一开始的位置,以此来达到一直显示的是中间的UIImageView的效果,此刻中间那个imagview
    显示的也就是图片2
    3)继续向左滑动看到图片3,当图片3滚动完成迅速重新设置3个UIImageView的内容为图片2、图片3、图片
    4,然后通过设置contentOffset依然显示中间的那个UIImageView,此刻也就是图片3
    5)当然,向右滑动原理完全一样,如此操作就给用户一种循环的错觉,而且图片多的话不占用过多内存
    
    • 为此我做了一个动态图,以此来动态描述下这个原理
    scroll.gif

    部分代码实现:

    • 大致原理就是上述那些,文字比较多,但是是核心思想,可以借助代码再去理解一下,代码里面注释也是非常详细的
    - (void)creatUI
    {
        //初始化scrollview
        _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
        self.scrollView.contentSize = CGSizeMake(view_WIDTH * 3, view_HEIGHT);
        self.scrollView.showsHorizontalScrollIndicator = NO;
        self.scrollView.showsVerticalScrollIndicator = NO;
        self.scrollView.pagingEnabled = YES;
        self.scrollView.bounces = NO;
        //设置scrollview一开始的偏移量为一个宽度,因为里面有3个UIImageView,所以scrollview默认显示的就是中间的那个imageview
        self.scrollView.contentOffset = CGPointMake(view_WIDTH, 0);
        self.scrollView.delegate = self;
        [self addSubview:self.scrollView];
    
    
    
        //初始化imageview
        _imageViews = [NSMutableArray array];
        //创建三个imageView作为循环复用的载体,图片将循环加载在这三个imageView上面
        for (NSInteger i = 0; i < 3; i++) {
            UIImageView *imageView = [[UIImageView alloc] init];
            imageView.frame = CGRectMake(view_WIDTH * i, 0, view_WIDTH,view_HEIGHT);
            //(self.dataArray.count - 1 + i)%self.dataArray.count也可以达到让一开始3个imageview分别显示最后一张<-->第一张<-->第二张图片,但是让大家理解起来会有一定难度,所以采用下面最简单的方法直接设置
            //imageView.tag = (self.dataArray.count - 1 + i)%self.dataArray.count;
    
            //3个imageview一开始需要的图片分别对应图片数组的图片索引应该是imageview[0].index-->images.count-1,imageview[1].index-->0,imageview[2].index-->1
            NSInteger index = 0;
            if (i == 0) index = _imagesArray.count - 1;
            if (i == 1) index = 0;
            if (i == 2) index = 1;
    
            //把index赋值给imageview的tag值,这样方便在后面方法中通过imageview的tag值直接拿到index,我们就可以轻松从图片数组获取对应的图片然后显示到imageview上面,有媒介的作用
            imageView.tag = index;
            imageView.userInteractionEnabled = YES;
    
           //这里给imageview添加了一个单击手势,通过block回调处理了imageview点击的监听事件
            UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageViewClicked:)];
            [imageView addGestureRecognizer:tap];
    
            //设置imageView上的image图片,2个方法使用哪一个设置都可以,关于2个方法的选择可以看下面详细注释
            [self setImageWithImageView:imageView];
            [self setImageView:imageView atIndex:index];
            //将imageView加入数组中,方便随后取用
            [_imageViews addObject:imageView];
            [self.scrollView addSubview:imageView];
    
        }
        //初始化pageControl,最后添加,这样它会显示在最前面,不会被遮挡
        self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.scrollView.frame) - 30, view_WIDTH, 30)];
        self.pageControl.numberOfPages = _imagesArray.count;
        self.pageControl.currentPage = 0;
        [self addSubview:self.pageControl];
    }
    
    

    /*
     这里给imageview设置图片有2个选择,第一可以使用这个方法,传递一个需要设置的imageview以及对应的index
     好处:一眼就可以让大家明白这个方法内部的实现功能,易与阅读和理解
     相对于下面那个方法的坏处:其实我们完全可以不传递index这个参数,我们完全可以把index赋值给imageview的tag,这样我们只用传递一个imageview过来,就可以既拿到imageview,又可以通过imageview的tag拿到index
     总结:2个方法都可以,看大家喜欢哪一种,哪一种顺手好理解就使用哪一种
     */
    - (void)setImageView:(UIImageView *)imageView atIndex:(NSInteger)index
    {
        //根据实时计算得出的index,从图片数组里面取值,然后赋值给对应左中右3个imageview
        UIImage *image = (UIImage *)_imagesArray[index];
        imageView.image = image;
    
    }
    - (void)setImageWithImageView:(UIImageView *)imageView{
    
        //根据imageView的tag值给imageView设置image
        // UIImage *image = (UIImage *)self.dataArray[imageView.tag];
        // imageView.image = image;
    }
    

    //定时器调用的方法
    - (void)nextPage
    {
        //NSLog(@"定时器的%f",_scrollView.contentOffset.x);
        //定时器方法都是相当于向左滑动,偏移量是增大的,原本偏移量是一倍的宽度,定时器方法执行一次,偏移量就要增大一个宽度,这样也就是setContentOffset:CGPointMake(VIEW_WIDTH * 2, 0),相当于设置偏移量是2倍宽度
        //执行了setContentOffset:方法,系统会自动调用scrollViewDidEndScrollingAnimation:方法,在这个方法里面再设置回偏移量等于一倍的宽度,同时更换各个imageview的图片,那么还是相当于中间的那个imageview显示在屏幕上
        [self.scrollView setContentOffset:CGPointMake(view_WIDTH * 2, 0) animated:YES];
    }
    

    #pragma mark - 更新图片和分页控件的当前页
    - (void)updateImageViewsAndPageControl {
        //先判断出scrollview的操作行为是向左向右还是不动
        //定义一个flag,目前是让scrollview向左向右滑动的时候索引对应的+1或者-1
        int flag = 0;
        if (self.scrollView.contentOffset.x > view_WIDTH)
        {//手指向左滑动
            flag = 1;
        }
        else if (self.scrollView.contentOffset.x == 0)//原本偏移量是一个宽度,现在==0了,那么就是手指向右滑动了
        {//手指向右滑动
            flag = -1;
        }
        else
        {//除了向左向右之外就是没有移动,那么不需要任何操作,直接返回
            return;
        }
    
        //    NSInteger index = 0;
        //修改imageViews中的imageView的tag值,从而修改imageView上显示的image,pageControl的页码
        for (UIImageView *imageView in _imageViews) {
            /*
             (1)当屏幕中间那个imageview显示最后一张图片时,右边的ImageView,也即下一张图片应该是显示最开始的那一张图片(第0张);
    
             (2)当屏幕中间显示最开始的那一张图片(第0张)时,左边的ImageView,也即上一张图片应该是最后一张图片。
             */
            NSInteger index = imageView.tag + flag ;
    
            if (index < 0) {
                index = self.pageControl.numberOfPages - 1;
            } else if (index >= self.pageControl.numberOfPages) {
                index = 0;
            }
    
            imageView.tag = index;
            //更新每一页上的image
            [self setImageWithImageView:imageView];
            [self setImageView:imageView atIndex:index];
        }
        //更新pageControl显示的页码,也就是中间那个imageview的tag值
        self.pageControl.currentPage = [_imageViews[1] tag];
    
        //使用无动画的效果快速切换,也就是把scrollview的偏移量还设置成一个imageview的宽度
        //这里是通过设置scrollview的偏移量让其来回滑动,时刻更换imageview的图片,每换一次,就立即让scrollview以无动画的方式再回到偏移量为一个imageview宽度的偏移量位置,即还是显示的中间那个imageview,以此给用户产生一种来回切换的错觉,实质一直是在显示中间那个imageview
        self.scrollView.contentOffset = CGPointMake(view_WIDTH, 0);
    }
    

    • 其他的就是在scrollview的几个代理方法里面要么开启定时器,要么关闭定时器,要么就是调用updateImageViewsAndPageControl方法更新图片以及分页控件的状态了,这里就不再描述了,详细的建议可以看代码完整参考理解,另外这些是个人理解,不足之处欢迎指正,感谢支持
    • 代码可以点击这里查看

    相关文章

      网友评论

      • OddSilent:你这个如果服务器返回的数据源 是0张图片或者1张图片 会出现问题 建议加入_imageArray.count > 1 的情况
      • c441f0d26d30:设置滑动速度我没找到啊
        c441f0d26d30:博主你还在不 我这个设置滑动速度不行。。。
      • 39d3c13a6193:很好用,谢谢了
      • 雪_晟:在你 手指拖动的瞬间 会突然闪一下 不知道楼主发现没
      • 一杯红酒mm:感谢分享。学习到了
      • Caiflower:网络图片怎么破?
      • mingmingsky:想问下,2个imageview能不能实现同样的功能,通过tableview也可以完成这样的功能,也是循环利用
        Springer:@小小小阿博er 可pod search ZPLoopScrollView 仓库地址https://github.com/twenty-zp/ZPLoopScrollView
        Springer:@mingmingsky 可pod search ZPLoopScrollView 仓库地址https://github.com/twenty-zp/ZPLoopScrollView
        小小小阿博er:@mingmingsky 恩恩,2个也可以的,在文章开头有2个可点击的地方,一个是2个imageview实现的,一个是collectionview实现的
      • Zhang_yD:3张图设置ContentOffSet的轮播有一个缺陷就是手指连续滑动的时候没办法及时更新位置。
      • 神烦取昵称:想问下您的gif图用什么软件做的
        小小小阿博er:@77b364c014c4 没事没事,实用就好
        神烦取昵称:@小小小阿博er 试过了确实好用,感谢分享
        小小小阿博er:@77b364c014c4 licecap,简单实用

      本文标题:iOS-无限循环轮播器(注释详细到没有之一)

      本文链接:https://www.haomeiwen.com/subject/ccxadttx.html