美文网首页在iOS开发的道路上越走越远iOS开发IOS
iOS无限循环轮播图(只使用三个imageView)

iOS无限循环轮播图(只使用三个imageView)

作者: OC笔记 | 来源:发表于2016-10-17 14:45 被阅读4793次

    以前循环轮播图的逻辑:

    以前我写过一个无限循环的轮播图,大概逻辑是:根据数据源(图片的数量)新建若干个imageView,然后在ScrollView的代理方法scrollViewDidScroll里判断需要展示的三个imageView的索引号,将这三个imageView添加到scrollView上进行展示。这种方式的缺点显而易见:有多少张图片,就要新建多少个imageView控件,而且每次都需要向scrollview上添加imageView。

    现在只需要使用三个imageView就可以实现:

    我新做的循环轮播图只需要使用3个imagView控件,并且支持定时器自动滚动播放。大致逻辑:在scrollView上添加3个imageView,每滚动一页,通过KVO观察scrollView的contentOffset的变化,判断需要添加的3张图片的索引号,并重置中间的imageView为当前页(中间的imageView永远都是当前页)。需要注意的是,临界值的判断。

    代码如下:

    YSLoopBanner.h

    #import <UIKit/UIKit.h>
    
    @interface YSLoopBanner : UIView
    
    /** click action */
    @property (nonatomic, copy) void (^clickAction) (NSInteger curIndex) ;
    
    /** data source */
    @property (nonatomic, copy) NSArray *imageURLStrings;
    
    
    - (instancetype)initWithFrame:(CGRect)frame scrollDuration:(NSTimeInterval)duration;
    
    @end
    

    YSLoopBanner.m

    #import "YSLoopBanner.h"
    
    
    @interface YSLoopBanner () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
    
    @property (nonatomic, strong) UIScrollView *scrollView;
    @property (nonatomic, strong) UIPageControl *pageControl;
    @property (nonatomic, strong) UIImageView *leftImageView;
    @property (nonatomic, strong) UIImageView *middleImageView;
    @property (nonatomic, strong) UIImageView *rightImageView;
    
    @property (nonatomic, assign) NSInteger curIndex;
    
    /** scroll timer */
    @property (nonatomic, strong) NSTimer *scrollTimer;
    
    /** scroll duration */
    @property (nonatomic, assign) NSTimeInterval scrollDuration;
    
    @end
    
    @implementation YSLoopBanner
    
    
    #pragma mark - life cycle
    - (instancetype)initWithFrame:(CGRect)frame scrollDuration:(NSTimeInterval)duration
    {
        if (self = [super initWithFrame:frame]) {
            self.scrollDuration = 0.f;
            [self addObservers];
            [self setupViews];
            if (duration > 0.f) {
                self.scrollTimer = [NSTimer scheduledTimerWithTimeInterval:(self.scrollDuration = duration)
                                                                    target:self
                                                                  selector:@selector(scrollTimerDidFired:)
                                                                  userInfo:nil
                                                                   repeats:YES];
                [self.scrollTimer setFireDate:[NSDate distantFuture]];
            }
        }
        
        return self;
    }
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            self.scrollDuration = 0.f;
            [self addObservers];
            [self setupViews];
        }
        
        return self;
    }
    
    - (void)dealloc {
        [self removeObservers];
        
        if (self.scrollTimer) {
            [self.scrollTimer invalidate];
            self.scrollTimer = nil;
        }
    }
    
    #pragma mark - setupViews
    - (void)setupViews {
        [self.scrollView addSubview:self.leftImageView];
        [self.scrollView addSubview:self.middleImageView];
        [self.scrollView addSubview:self.rightImageView];
        [self addSubview:self.scrollView];
        [self addSubview:self.pageControl];
        
        [self placeSubviews];
    }
    
    - (void)placeSubviews {
        self.scrollView.frame = self.bounds;
        self.pageControl.frame = CGRectMake(0, CGRectGetMaxY(self.bounds) - 30.f, CGRectGetWidth(self.bounds), 20.f);
        
        CGFloat imageWidth = CGRectGetWidth(self.scrollView.bounds);
        CGFloat imageHeight = CGRectGetHeight(self.scrollView.bounds);
        self.leftImageView.frame    = CGRectMake(imageWidth * 0, 0, imageWidth, imageHeight);
        self.middleImageView.frame  = CGRectMake(imageWidth * 1, 0, imageWidth, imageHeight);
        self.rightImageView.frame   = CGRectMake(imageWidth * 2, 0, imageWidth, imageHeight);
        self.scrollView.contentSize = CGSizeMake(imageWidth * 3, 0);
    
        [self setScrollViewContentOffsetCenter];
    }
    
    #pragma mark - 把scrollView偏移到中心位置
    - (void)setScrollViewContentOffsetCenter {
        self.scrollView.contentOffset = CGPointMake(CGRectGetWidth(self.scrollView.bounds), 0);
    }
    
    #pragma mark - kvo
    - (void)addObservers {
        [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
    }
    
    - (void)removeObservers {
        [self.scrollView removeObserver:self forKeyPath:@"contentOffset"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:@"contentOffset"]) {
            [self caculateCurIndex];
        }
    }
    
    #pragma mark - getters
    - (UIScrollView *)scrollView {
        if (!_scrollView) {
            _scrollView = [UIScrollView new];
            _scrollView.delegate = self;
            _scrollView.pagingEnabled = YES;
            _scrollView.showsHorizontalScrollIndicator = NO;
            _scrollView.showsVerticalScrollIndicator = NO;
        }
        
        return _scrollView;
    }
    
    - (UIPageControl *)pageControl {
        if (!_pageControl) {
            _pageControl = [UIPageControl new];
            _pageControl.pageIndicatorTintColor = [UIColor lightGrayColor];
            _pageControl.currentPageIndicatorTintColor = [UIColor blueColor];
        }
        
        return _pageControl;
    }
    
    - (UIImageView *)leftImageView {
        if (!_leftImageView) {
            _leftImageView = [UIImageView new];
            _leftImageView.contentMode = UIViewContentModeScaleAspectFit;
            _leftImageView.backgroundColor = [UIColor yellowColor];
        }
        
        return _leftImageView;
    }
    
    - (UIImageView *)middleImageView {
        if (!_middleImageView) {
            _middleImageView = [UIImageView new];
            _middleImageView.contentMode = UIViewContentModeScaleAspectFit;
            UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageClicked:)];
            [_middleImageView addGestureRecognizer:tap];
            _middleImageView.backgroundColor = [UIColor redColor];
            _middleImageView.userInteractionEnabled = YES;
        }
        
        return _middleImageView;
    }
    
    - (UIImageView *)rightImageView {
        if (!_rightImageView) {
            _rightImageView = [UIImageView new];
            _rightImageView.contentMode = UIViewContentModeScaleAspectFit;
            _rightImageView.backgroundColor = [UIColor greenColor];
        }
        
        return _rightImageView;
    }
    
    
    #pragma mark - setters
    - (void)setImageURLStrings:(NSArray *)imageURLStrings {
        if (imageURLStrings) {
            _imageURLStrings = imageURLStrings;
            self.curIndex = 0;
            
            if (imageURLStrings.count > 1) {
                // auto scroll
                [self.scrollTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:self.scrollDuration]];
                self.pageControl.numberOfPages = imageURLStrings.count;
                self.pageControl.currentPage = 0;
                self.pageControl.hidden = NO;
            } else {
                self.pageControl.hidden = YES;
                [self.leftImageView removeFromSuperview];
                [self.rightImageView removeFromSuperview];
                self.scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.scrollView.bounds), 0);
            }
        }
    }
    
    - (void)setCurIndex:(NSInteger)curIndex {
        if (_curIndex >= 0) {
            _curIndex = curIndex;
            
            // caculate index
            NSInteger imageCount = self.imageURLStrings.count;
            NSInteger leftIndex = (curIndex + imageCount - 1) % imageCount;
            NSInteger rightIndex= (curIndex + 1) % imageCount;
            
            // fill image
            self.leftImageView.image = [UIImage imageNamed:self.imageURLStrings[leftIndex]];
            self.middleImageView.image = [UIImage imageNamed:self.imageURLStrings[curIndex]];
            self.rightImageView.image = [UIImage imageNamed:self.imageURLStrings[rightIndex]];
            
            // 每次滚动后,都需要将当前页移动到中间位置
            [self setScrollViewContentOffsetCenter];
            
            self.pageControl.currentPage = curIndex;
        }
    }
    
    #pragma mark - caculate curIndex
    - (void)caculateCurIndex {
        if (self.imageURLStrings && self.imageURLStrings.count > 0) {
            CGFloat pointX = self.scrollView.contentOffset.x;
            
            // 临界值判断,第一个和第三个imageView的contentoffset
            CGFloat criticalValue = .2f;
            
            // 向右滑动,右侧临界值的判断
            if (pointX > 2 * CGRectGetWidth(self.scrollView.bounds) - criticalValue) {
                self.curIndex = (self.curIndex + 1) % self.imageURLStrings.count;
            } else if (pointX < criticalValue) {// 向左滑动,左侧临界值的判断
                self.curIndex = (self.curIndex + self.imageURLStrings.count - 1) % self.imageURLStrings.count;
            }
        }
    }
    
    
    #pragma mark - UIScrollViewDelegate
    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
        if (self.imageURLStrings.count > 1) {
            [self.scrollTimer setFireDate:[NSDate distantFuture]];
        }
    }
    
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        if (self.imageURLStrings.count > 1) {
            [self.scrollTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:self.scrollDuration]];
        }
    }
    
    #pragma mark - button actions
    - (void)imageClicked:(UITapGestureRecognizer *)tap {
        if (self.clickAction) {
            self.clickAction (self.curIndex);
        }
    }
    
    #pragma mark - timer action
    - (void)scrollTimerDidFired:(NSTimer *)timer {
        // 矫正imageView的frame:因为定时器的自动滚动,可能导致轮播图一页显示两张图片的情况
        CGFloat criticalValue = .2f;
        if (self.scrollView.contentOffset.x < CGRectGetWidth(self.scrollView.bounds) - criticalValue || self.scrollView.contentOffset.x > CGRectGetWidth(self.scrollView.bounds) + criticalValue) {
            [self setScrollViewContentOffsetCenter];
        }
        CGPoint newOffset = CGPointMake(self.scrollView.contentOffset.x + CGRectGetWidth(self.scrollView.bounds), self.scrollView.contentOffset.y);
        [self.scrollView setContentOffset:newOffset animated:YES];
    }
    
    @end
    

    调用代码:

    YSLoopBanner *loop = [[YSLoopBanner alloc] initWithFrame:CGRectMake(0, 100, 200, 200) scrollDuration:3.f];
        [self.view addSubview:loop];
        loop.imageURLStrings = @[@"1.jpg", @"2.jpg", @"3.jpg"];
        loop.clickAction = ^(NSInteger index) {
            NSLog(@"curIndex: %ld", index);
        };
    

    github地址

    相关文章

      网友评论

      • 25282f9e7081:感谢楼主.有个建议提一下,关于timer的暂停和恢复的问题,为什么不用NSRunloop的commonModes呢,每次根据滑动和停止滑动来管理timer相对来说更麻烦一点,如果这时候有其它的手势来操作图片的话,也会出现不可预知的问题.
        OC笔记:@fantasticiOS 对的,这个很久没更新了,实际用起来确实需要处理runLoop
      • 退休老干部:有个bug,定时器强引用了自己导致 dealloc 进不来,可以给 timer 加个扩展,target 设置成timer 自己,userInfo 传递 需要定时器执行的 block (iOS 10 已经直接提供了这种方法:)
        OC笔记:嗯,是有这个问题,你也可以手动释放timer
      • 806349745123:创建YSLoopbannner到navigationController上,图片位置会有偏移
        OC笔记:@人类买水精华 ,嗯,解决就好,不过图片的填充模式一般默认是UIViewContentModeScaleToFill,上面写错了,一般轮播图都是这种模式,不然图片显示不全。还有一种填充模式是按比例放大图片填充(相当于截取部分图片,但是图片不会因为全屏而变形了),类型是:scaleAspectFill,需要设置imagview的clipsToBounds为true。你可以试试。
        806349745123:@OC笔记 我是这样的处理的,因为你用了UIScrollView,http://www.jianshu.com/p/28f717b64460
        OC笔记:这个是imageview的填充模式造成的,和nav没有关系,你可以改改图片的填充模式:XXXImageView.contentMode = UIViewContentModeScaleAspectFill;//UIViewContentModeScaleAspectFit;

      本文标题:iOS无限循环轮播图(只使用三个imageView)

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