实现一个简单易用的无限轮播视图YWLoopScrollView

代码效果:

loop.gif

github Demo地址:wang66/YWLoopScrollViewDemo


用法:

创建YWLoopScrollView视图的实例,并设置数据源和相关属性,并添加在父视图上。而且可以设置代理实现其代理方法,将有关数据回调出来。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.titleLabel.text = @"YWLoopScrollView";
    self.titleView.backgroundColor = RGB(31, 162, 252);
    
    NSArray *imagesArr = @[IMAGE(@"img01.jpeg"),IMAGE(@"img02.jpeg"),IMAGE(@"img03.jpeg"), IMAGE(@"img04.jpeg")]; // ,IMAGE(@"img05.jpeg")
    YWLoopScrollView *loopScrollView = [[YWLoopScrollView alloc] initWithFrame:CGRectMake(0, 0, Width_MainScreen, 250) images:imagesArr];
    loopScrollView.scrollInterval = 2.f;
    loopScrollView.isAutoScroll = YES;  // default is YES
    loopScrollView.delegate = self;
    [self.contentView addSubview:loopScrollView];
}

#pragma mark - YWLoopScrollViewDelegate
- (void)ywLoopScrollView:(YWLoopScrollView *)scrollView currentPageIndex:(NSInteger)index image:(id)image
{
    NSLog(@"❤:currentPageIndex is %d", (int)index);
}

- (void)ywLoopScrollView:(YWLoopScrollView *)scrollView didSelectedPageIndex:(NSInteger)index image:(id)image
{
    NSLog(@"❤:didSelectedPageIndex is %d", (int)index);
    SecondViewController *secondVC = [SecondViewController new];
    secondVC.image = image;
    [self.navigationController pushViewController:secondVC animated:YES];
}

YWLoopScrollView.h接口:

#import 

@class YWLoopScrollView;
@protocol YWLoopScrollViewDelegate 

- (void)ywLoopScrollView:(YWLoopScrollView *)scrollView currentPageIndex:(NSInteger)index image:(id)image;
- (void)ywLoopScrollView:(YWLoopScrollView *)scrollView didSelectedPageIndex:(NSInteger)index image:(id)image;

@end

@interface YWLoopScrollView : UIView

@property (nonatomic, strong)UIColor        *pageControlDefaultColor; // pageControl的默认颜色
@property (nonatomic, strong)UIColor        *pageControlSelectedColor; // pageControl当前点的颜色
@property (nonatomic, assign)CGFloat         scrollInterval; // 滚动时间
@property (nonatomic, assign)CGRect          pageControlRect; // pageControl的frame
@property (nonatomic, assign)BOOL            isAutoScroll; // 是否自动滚动
@property (nonatomic, weak)id delegate;

- (instancetype)initWithFrame:(CGRect)frame images:(NSArray *)images;
//- (instancetype)initWithFrame:(CGRect)frame imagesUrl:(NSArray *)imagesUrl;
@end


@interface YWCollectionCell : UICollectionViewCell

@end```

---

### 无限循环轮播的原理:

说起轮播图片,一般可以创建一个``UIScrollView``,在上面添加多个``UIImageView``实现。也可以直接使用``UICollectionView``来实现,而且后者更好一些,它有复用机制,性能可能更好点。``YWLoopScrollView``是用``collectionView``实现的。

无限循环轮播,即可以往左右无限滚动。当前屏幕为最后一张图片时,继续往后滚动则重新从第一张图片开始显示;若当前屏幕为第一张图片,往前滚动则是最后一张图片依次往前滚动。如下,假如数据源是``A``,``B``,``C``,``D``四张图片,默认是``A``在占据屏幕的,往后滑动屏幕,则依次出现图片``B``,``C``,``D``。当当前屏幕为最后一张图片``D``时,继续往右滑动,则重新从``A``开始依次显示。同理,若当前屏幕是显示``A``图片的,则往左滑动时会从``D``开始依次显示图片。

##### 怎么实现上述效果呢?看下图(来自知乎:[ios轮播图实现原理?](https://www.zhihu.com/question/28720980))。
![5b11921b4dfc8c362d1a78e3e0e17aa4_b.jpg](http://upload-images.jianshu.io/upload_images/988593-36804034d492bada.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

>我们可以把滚动视图``collectionView``的数据源个数设为原始数据源N+2,这样的话滚动视图的滚动范围``contentSize``则多出来两张图片范围。在原始数据源的最后追加一项``A``图片,在原始数据源的开头追加一项``D``图片。如此一来,当我们滑动到``D``图片继续往后滑动时会滑动到追加的``A``图片上,若此时,我们将``collectionView``的``contentOffset``以非动画形式设置为原始``A``的位置,让``collectionView``回到第一张图片的位置。如此,便可循环滚动。

>往左滑动也同理。``A``原本就是第一张图片了,但是因为我们在前面追加了``D``图片,从``A``往左滑动,便滑动到了``D``,此时,若我们以非动画的形式设置``collectionView``的``contentOffset``为原始``D``的位置,让``collectionView``回到最后一张图片的位置。如此也实现了循环滚动。


##### 用代码来实现上述原理的逻辑:

pragma mark - scrollView delegate

// 减速停止时触发

  • (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
    [self scrollRefresh];
    }
    // setContentOffset/scrollRectVisible:animated: 这些滚动动画结束时触发
  • (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
    {
    [self scrollRefresh];
    }

pragma mark - 循环轮播的核心逻辑

  • (void)scrollRefresh
    {
    NSInteger dataIndex = floor(_collecionView.contentOffset.x/CGRectGetWidth(self.frame));
    NSIndexPath *indexPath = [_collecionView indexPathForItemAtPoint:_collecionView.contentOffset];
    NSInteger currentPage = [self imageIndexFromRowIndex:indexPath.row];

    if(dataIndex==0){
    [_collecionView scrollRectToVisible:CGRectMake(CGRectGetWidth(self.frame)*_imagesArr.count, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)) animated:NO];
    _pageControl.currentPage = _imagesArr.count-1;
    }
    else if(dataIndex==_imagesArr.count+1){
    [_collecionView scrollRectToVisible:CGRectMake(CGRectGetWidth(self.frame), 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)) animated:NO];
    _pageControl.currentPage = 0;
    }else{
    _pageControl.currentPage = currentPage;
    }

    NSLog(@"---❤%d---",(int)dataIndex);

    // callback
    if([self.delegate respondsToSelector:@selector(ywLoopScrollView:currentPageIndex:image:)]){
    [self.delegate ywLoopScrollView:self currentPageIndex:currentPage image:_imagesArr[currentPage]];
    }
    }

// collectionView 每行对应的图片数据

  • (NSInteger)imageIndexFromRowIndex:(NSInteger)rowIndex
    {
    if(rowIndex==0){
    return self.imagesArr.count-1;
    }else if(rowIndex==self.imagesArr.count+1){
    return 0;
    }else{
    return rowIndex-1;
    }
    }

上述原理的代码都在``scrollRefresh``这个方法里,该方法是在``scrollView``以下两个代码方法执行的。说到这里,有必要全面了解一下``UIScrollView``的各个代理方法触发时机和作用:[ScrollView的基本用法丶代理方法](http://www.cnblogs.com/longiang7510/p/5368197.html)

---

### 用``NSTimer``实现定时自动滑动图片:

  • (void)startAutoScroll
    {
    [self stopAutoScroll];
    _timer = [NSTimer scheduledTimerWithTimeInterval:_scrollInterval target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
    }

  • (void)stopAutoScroll
    {
    if(_timer){
    [_timer invalidate];
    }
    }

// 定时器触发的自动滚动

  • (void)nextPage
    {
    if(_imagesArr.count==0||_imagesArr.count==1){
    return;
    }

    CGPoint offset = _collecionView.contentOffset;
    NSIndexPath *indexPath = [_collecionView indexPathForItemAtPoint:offset];

    [_collecionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row+1 inSection:indexPath.section] atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
    }

因为``nextPage``方法里执行了``scrollToItemAtIndexPath:atScrollPosition:animation:``方法,它会触发``scrollView``的代理方法``scrollViewDidEndScrollingAnimation:``,从而执行``scrollRefresh``方法里的轮播逻辑。




你可能感兴趣的:(实现一个简单易用的无限轮播视图YWLoopScrollView)