实现一个简单易用的无限轮播视图YWLoopScrollView
代码效果:

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 <UIKit/UIKit.h>
@class YWLoopScrollView;
@protocol YWLoopScrollViewDelegate <NSObject>
- (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<YWLoopScrollViewDelegate> 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))。

>我们可以把滚动视图``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``方法里的轮播逻辑。