一步一步拆解一个简单的iOS轮播图(三图)
导言(可以不看):
不吹不黑,也许是东半球最简单的iOS轮播图拆分注释(讲解不敢当)了(tree new bee)。(一句话包含两个人,你能猜到有谁吗?提示:一个在卖手机,一个最近在卖书)哈哈。。。
我第一次项目中需要使用轮播图的时候我是用的别人写好的一个轮子,那个轮播封装很多东西,包括比如可以设置pageControl的位置,可以传图片url或本地图片,缓存网络图片等等。但是我觉得没必要搞那么复杂,我喜欢简单并足够做事的东西。现在有时间便想自己把它拆解一下。看了一些简书上一些作者写的关于轮播图的讲解,我发现好多人写的其实是有问题的,虽然不易发现,但是你仔细测一下他的demo,很多都有问题。
我的这个轮播控件基本能满足现有市场上的所有app的轮播,反正我没见过把轮播搞得更花哨的,没太大意义。我自己把它的实现分为三块:1、添加基本控件,控制滚动(也就是控制scrollView,实现代理方法);2、自动滚动,timer;3、处理点击事件(代理)。代码注释很详细了,还看不懂的可以给我留言。
自定义一个View继承自UIView,这个类就是封装的轮播图类
先看看我们需要在初始化用语句中给这个自定义View添加哪些控件:scrollView、pageControl、三个按钮图(有self的应该知道是定义在在类拓展中的属性吧)
1 - (instancetype)initWithFrame:(CGRect)frame { 2 if (self = [super initWithFrame:frame]) { 3 //定义一个scrollView,最主要的轮播控件 4 UIScrollView *scrollView = [[UIScrollView alloc] init]; 5 scrollView.delegate = self; 6 //横竖两种滚轮都不显示 7 scrollView.showsVerticalScrollIndicator = NO; 8 scrollView.showsHorizontalScrollIndicator = NO; 9 //需要分页 10 scrollView.pagingEnabled = YES; 11 //不需要回弹(试了一下加不加应该都没什么影响) 12 scrollView.bounces = NO; 13 [self addSubview:scrollView]; 14 self.scrollView = scrollView; 15 16 //在scrollView中添加三个图片按钮,因为后面需要响应点击事件,所以我直接用按钮不用imageView了,感觉更方便一些 17 for (int i = 0;i < imageBtnCount; i++) { 18 UIButton *imageBtn = [[UIButton alloc] init]; 19 [scrollView addSubview:imageBtn]; 20 } 21 //添加pageControl 22 UIPageControl *pageControl = [[UIPageControl alloc] init]; 23 [self addSubview:pageControl]; 24 self.pageControl = pageControl; 25 } 26 return self; 27 }
类拓展中的属性:(timer后面会需要,定时自动轮播)
1 @property (nonatomic, weak) UIScrollView*scrollView; 2 @property (nonatomic, weak) UIPageControl *pageControl; 3 @property (nonatomic, weak) NSTimer *timer;
接下来布局子控件:
布局子控件之前要先说一个东西:
1 static const int imageBtnCount = 3;
这个count我们很多地方都会用到,因为这个轮播图的原理就是用三张图来实现无限循环轮播的假象。(#define能少用就少用吧啊)
1 //布局子控件 2 - (void)layoutSubviews { 3 [super layoutSubviews]; 4 //设置scrollView的frame 5 self.scrollView.frame = self.bounds; 6 7 CGFloat width = self.bounds.size.width; 8 CGFloat height = self.bounds.size.height; 9 //设置contentSize,不同轮播方向的时候contentSize是不一样的 10 if (self.isScrollDorectionPortrait) { //竖向 11 //contentSize要放三张图片 12 self.scrollView.contentSize = CGSizeMake(width, height * imageBtnCount); 13 } else { //横向 14 self.scrollView.contentSize = CGSizeMake(width * imageBtnCount, height); 15 } 16 //设置三张图片的位置,并为三个按钮添加点击事件 17 for (int i = 0; i < imageBtnCount; i++) { 18 UIButton *imageBtn = self.scrollView.subviews[i]; 19 [imageBtn addTarget:self action:@selector(imageBtnClick:) forControlEvents:UIControlEventTouchUpInside]; 20 if (self.isScrollDorectionPortrait) { //竖向 21 imageBtn.frame = CGRectMake(0, i * height, width, height); 22 } else { //横向 23 imageBtn.frame = CGRectMake(i * width, 0, width, height); 24 } 25 } 26 //设置contentOffset,显示最中间的图片 27 if (self.isScrollDorectionPortrait) { //竖向 28 self.scrollView.contentOffset = CGPointMake(0, height); 29 } else { //横向 30 self.scrollView.contentOffset = CGPointMake(width, 0); 31 } 32 33 //设置pageControl的位置 34 CGFloat pageW = 100; 35 CGFloat pageH = 20; 36 CGFloat pageX = width - pageW; 37 CGFloat pageY = height - pageH; 38 self.pageControl.frame = CGRectMake(pageX, pageY, pageW, pageH); 39 40 }
接下来看一下对外接口:(代理暂时不用看,那是后面处理点击的事了)
1 #import <UIKit/UIKit.h> 2 3 @class ATCarouselView; 4 @protocol ATCarouselViewDelegate <NSObject> 5 @optional 6 /** 7 * 点击图片的回调事件 8 */ 9 - (void)carouselView:(ATCarouselView *)carouselView indexOfClickedImageBtn:(NSUInteger)index; 10 @end 11 12 @interface ATCarouselView : UIView 13 //传入图片数组 14 @property (nonatomic, copy) NSArray *images; 15 //pageControl颜色设置 16 @property (nonatomic, strong) UIColor *currentPageColor; 17 @property (nonatomic, strong) UIColor *pageColor; 18 //是否竖向滚动 19 @property (nonatomic, assign, getter=isScrollDorectionPortrait) BOOL scrollDorectionPortrait; 20 21 @property (weak, nonatomic) id<ATCarouselViewDelegate> delegate; 22 @end
使用者需要设置的东西都在这里了:接下来看set方法:(pageControl的太简单就不占篇幅了)
1 //根据传入的图片数组设置图片 2 - (void)setImages:(NSArray *)images { 3 _images = images; 4 //pageControl的页数就是图片的个数 5 self.pageControl.numberOfPages = images.count; 6 //默认一开始显示的是第0页 7 self.pageControl.currentPage = 0; 8 //设置图片显示内容 9 [self setContent]; 10 //开启定时器 11 [self startTimer]; 12 13 }
下面看setContent方法,设置显示内容,定时器在后面说:
1 //设置显示内容 2 - (void)setContent { 3 //设置三个imageBtn的显示图片 4 for (int i = 0; i < self.scrollView.subviews.count; i++) { 5 //取出三个imageBtn 6 UIButton *imageBtn = self.scrollView.subviews[i]; 7 //这个是为了给图片做索引用的 8 NSInteger index = self.pageControl.currentPage; 9 10 if (i == 0) { //第一个imageBtn,隐藏在当前显示的imageBtn的左侧 11 index--; //当前页索引减1就是第一个imageBtn的图片索引 12 } else if (i == 2) { //第三个imageBtn,隐藏在当前显示的imageBtn的右侧 13 index++; //当前页索引加1就是第三个imageBtn的图片索引 14 } 15 //无限循环效果的处理就在这里 16 if (index < 0) { //当上面index为0的时候,再向右拖动,左侧图片显示,这时候我们让他显示最后一张图片 17 index = self.pageControl.numberOfPages - 1; 18 } else if (index == self.pageControl.numberOfPages) { //当上面的index超过最大page索引的时候,也就是滑到最右再继续滑的时候,让他显示第一张图片 19 index = 0; 20 } 21 imageBtn.tag = index; 22 //用上面处理好的索引给imageBtn设置图片 23 [imageBtn setBackgroundImage:self.images[index] forState:UIControlStateNormal]; 24 [imageBtn setBackgroundImage:self.images[index] forState:UIControlStateHighlighted]; 25 26 } 27 }
先把原理图粘在这吧,对照着代码看可能更容易一点:
最后这个是最核心的步骤:
好了,接着看updateContent:
1 //状态改变之后更新显示内容 2 - (void)updateContent { 3 CGFloat width = self.bounds.size.width; 4 CGFloat height = self.bounds.size.height; 5 [self setContent]; 6 //唯一跟设置显示内容不同的就是重新设置偏移量,让它永远用中间的按钮显示图片,滑动之后就偷偷的把偏移位置设置回去,这样就实现了永远用中间的按钮显示图片 7 //设置偏移量在中间 8 if (self.isScrollDorectionPortrait) { 9 self.scrollView.contentOffset = CGPointMake(0, height); 10 } else { 11 self.scrollView.contentOffset = CGPointMake(width, 0); 12 } 13 }
后面就简单了,滚动的时候的一些操作:
1 //拖拽的时候执行哪些操作 2 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 3 //拖动的时候,哪张图片最靠中间,也就是偏移量最小,就滑到哪页 4 //用来设置当前页 5 NSInteger page = 0; 6 //用来拿最小偏移量 7 CGFloat minDistance = MAXFLOAT; 8 //遍历三个imageView,看那个图片偏移最小,也就是最靠中间 9 for (int i = 0; i < self.scrollView.subviews.count; i++) { 10 UIButton *imageBtn = self.scrollView.subviews[i]; 11 CGFloat distance = 0; 12 if (self.isScrollDorectionPortrait) { 13 distance = ABS(imageBtn.frame.origin.y - scrollView.contentOffset.y); 14 } else { 15 distance = ABS(imageBtn.frame.origin.x - scrollView.contentOffset.x); 16 } 17 if (distance < minDistance) { 18 minDistance = distance; 19 page = imageBtn.tag; 20 } 21 } 22 self.pageControl.currentPage = page; 23 } 24 25 //结束拖拽的时候更新image内容 26 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 27 { 28 [self updateContent]; 29 }
接下来就是定时器和代理设置点击事件了,这种比较简单的我就不多说了,我理解最难的地方都在上面的图里说明白了:
先说最简单的点击事件:.h文件
1 @class ATCarouselView; 2 @protocol ATCarouselViewDelegate <NSObject> 3 @optional 4 /** 5 * 点击图片的回调事件 6 */ 7 - (void)carouselView:(ATCarouselView *)carouselView indexOfClickedImageBtn:(NSUInteger)index; 8 @end
.m文件
1 - (void)imageBtnClick:(UIButton *)btn { 2 // NSLog(@"%ld",btn.tag); 3 if ([self.delegate respondsToSelector:@selector(carouselView:indexOfClickedImageBtn:)]) 4 { 5 [self.delegate carouselView:self indexOfClickedImageBtn:btn.tag]; 6 } 7 8 }
最后是定时器自动轮播的处理:
1 //开始计时器 2 - (void)startTimer { 3 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextImage) userInfo:nil repeats:YES]; 4 [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 5 self.timer = timer; 6 } 7 //停止计时器 8 - (void)stopTimer { 9 //结束计时 10 [self.timer invalidate]; 11 //计时器被系统强引用,必须手动释放 12 self.timer = nil; 13 } 14 //通过改变contentOffset * 2换到下一张图片 15 - (void)nextImage { 16 CGFloat height = self.bounds.size.height; 17 CGFloat width = self.bounds.size.width; 18 if (self.isScrollDorectionPortrait) { 19 [self.scrollView setContentOffset:CGPointMake(0, 2 * height) animated:YES]; 20 } else { 21 [self.scrollView setContentOffset:CGPointMake(2 * width, 0) animated:YES]; 22 } 23 }
最后是使用这个轮播图:
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 ATCarouselView *carousel = [[ATCarouselView alloc] initWithFrame:CGRectMake(0, 20, [UIScreen mainScreen].bounds.size.width, 300)]; 4 carousel.delegate = self; 5 // carousel.scrollDorectionPortrait = YES; 6 carousel.images = @[ 7 [UIImage imageNamed:@"0"], 8 [UIImage imageNamed:@"1"], 9 [UIImage imageNamed:@"2"], 10 [UIImage imageNamed:@"3"], 11 [UIImage imageNamed:@"4"] 12 ]; 13 carousel.currentPageColor = [UIColor orangeColor]; 14 carousel.pageColor = [UIColor grayColor]; 15 [self.view addSubview:carousel]; 16 17 } 18 - (void)carouselView:(ATCarouselView *)carouselView indexOfClickedImageBtn:(NSUInteger )index { 19 NSLog(@"点击了第%ld张图片",index); 20 }
博客里把所有代码都贴上就太浪费空间了,基本上所有比较重要的都在上面了,如果还有不懂的可以看一下demo跑一下,有问题欢迎留言: my github:https://github.com/alan12138/carousel