導言(可以不看):
不吹不黑,也許是東半球最簡單的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