寫在前面:
一胞得、關(guān)于無限輪播的思路
無非就是以下幾種:collectionView實(shí)現(xiàn)荧止、N+2個(gè)imageView實(shí)現(xiàn)、三個(gè)imageView實(shí)現(xiàn)阶剑,兩個(gè)imageView實(shí)現(xiàn)跃巡。
這其中collectionView感覺有點(diǎn)偷懶的意思,N+2和3個(gè)imageView相比起來3個(gè)imageView更節(jié)省資源牧愁,兩個(gè)imageView還沒有了解過素邪,這里不做介紹。
二猪半、關(guān)于借鑒
樓主自己在寫輪播的時(shí)候查了很多資料兔朦,有很多好用的第三方庫,但是感覺這個(gè)簡(jiǎn)單的功能還是自己做好一點(diǎn)磨确,后期維護(hù)也方便點(diǎn)沽甥,下面也會(huì)將參考的文章鏈接貼出來。
以下的文章中有一些有坑的地方乏奥,我主要參考了一些結(jié)構(gòu)和命名安接。
參考文章:http://www.reibang.com/p/1325998d976b?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=qq
三、個(gè)人實(shí)現(xiàn)原理:
一個(gè)UIView中包含兩個(gè)子視圖:UIScrollView和UIPageControl。
最后將輪播圖拿出去用的時(shí)候直接用這個(gè)UIView的對(duì)象就好盏檐。
Q1:為什么不直接將UIPageControl放在ScrollView中:
A:如果將pageContro放在ScrollView中歇式,那么小圓點(diǎn)會(huì)隨著輪播圖的滾動(dòng)跟著滾動(dòng),并且需要添加在每張圖上都添加一個(gè)pageControl胡野,視覺效果不好材失,也很麻煩。如果不這樣那么就需要?jiǎng)討B(tài)計(jì)算設(shè)置pageControl的frame硫豆,我覺得相較于我采用的那種方法而言資源占用會(huì)更大龙巨。
四、關(guān)于坑的問題
這篇文章中熊响,樓主暫時(shí)沒有發(fā)現(xiàn)什么坑旨别,但是有兩點(diǎn)不確定的地方,這兩點(diǎn)在實(shí)際使用中可能會(huì)有坑汗茄,希望大家注意秸弛,具體如下:
1、代碼中用到了KVO洪碳,但是在移除KVO的時(shí)候是在相關(guān)view的dealloc移除的递览,不確定這個(gè)地方會(huì)不會(huì)有問題,如果有大神瞳腌,希望可以指正绞铃。
2、代碼中的NSTimer(定時(shí)器)本來我個(gè)人理想狀態(tài)下應(yīng)該是想在ViewWillAppera和viewDidDisappear中分別來啟動(dòng)和移除的嫂侍,來防止循環(huán)引用儿捧,無法釋放對(duì)象,但是卻發(fā)現(xiàn)在View中根本沒有調(diào)用這兩個(gè)方法挑宠,只有在Controller中才調(diào)用這兩方法菲盾,這點(diǎn)沒查到答案。所以最后是在dealloc中移除的痹栖,不知道會(huì)不會(huì)留坑亿汞,所以這點(diǎn)需要大家注意。如果有大神揪阿,可以幫我解答一下疗我,謝謝。
---------------------------------我是正文分割線------------------------------------------
思路(直接引用別人文章的南捂,不多說了):
既然是三張圖片吴裤,那在實(shí)現(xiàn)的過程中,為了可以左右滑動(dòng)溺健,都有圖片麦牺,所以初始化的時(shí)候,要讓 UIScrollView 的偏移量為自身的寬度,而且不管是滑像上一張還是下一張剖膳,為了可以正常進(jìn)行左右滑動(dòng)魏颓,在滑動(dòng)結(jié)束的時(shí)候,都要讓 UIScrollView 的偏移量為自身的寬度吱晒,所以在 UIScrollView 滑動(dòng)完一個(gè)圖片之后甸饱,都要將 UIScrollView 的偏移量設(shè)置為自身寬度。
具體實(shí)現(xiàn):
分別創(chuàng)建了兩個(gè)文件:
ZWCHeaderViewScrollView.h
ZWCHeaderViewScrollView.m
ZWCheaderView.h
ZWCheaderView.m
首先ZWCHeaderViewScrollView.h中留給外部的接口:
//將當(dāng)前小圓點(diǎn)的位置存起來
@property(nonatomic,assign)NSInteger currentPageInteger;
//定時(shí)器
@property(nonatomic,strong)NSTimer *rotateTimer;
//重寫init方法
- (instancetype)initWithFrame:(CGRect)frame;
//公布一個(gè)對(duì)外的方法仑濒,傳入圖片的網(wǎng)絡(luò)地址設(shè)置展示輪播圖叹话。
- (void)showWith:(NSArray *)imageViewUrlArray;
ZWCHeaderViewScrollView.m中需要先創(chuàng)建左中右三個(gè)imageView
//創(chuàng)建左中右三個(gè)imageView
@property(nonatomic,strong)UIImageView *leftImageView;
@property(nonatomic,strong)UIImageView *centerImageView;
@property(nonatomic,strong)UIImageView *rightImageView;
并且聲明下面的屬性:
//將傳進(jìn)來的imageView存起來
@property(nonatomic,strong)NSArray *imageArray;
//將傳進(jìn)來的frame存起來。
@property(nonatomic,assign)CGRect scrViewFrame;
對(duì)三個(gè)屬性進(jìn)行懶加載
#pragma mark --- 屬性懶加載
-(UIImageView *)leftImageView{
if (_leftImageView == nil) {
_leftImageView = [[UIImageView alloc]init];
}
return _leftImageView;
}
-(UIImageView *)centerImageView{
if (_centerImageView == nil) {
_centerImageView = [[UIImageView alloc]init];
}
return _centerImageView;
}
-(UIImageView *)rightImageView{
if (_rightImageView == nil) {
_rightImageView = [[UIImageView alloc]init];
}
return _rightImageView;
}
然后大致理一下思路:
1墩瞳、外界初始化一個(gè)ZWCHeaderViewScrollView對(duì)象
2驼壶、通過初始化的對(duì)象調(diào)用show方法并傳入相應(yīng)的圖片網(wǎng)絡(luò)地址
3、在m文件中喉酌,一單show方法被調(diào)用热凹,則立即創(chuàng)建左中右三個(gè)視圖并添加到ScrollView中。
根據(jù)上面的思路ZWCHeaderViewScrollView.m中有如下代碼
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.scrViewFrame = frame;
self.contentSize = CGSizeMake(CGRectGetWidth(frame)*3, CGRectGetHeight(frame));
//這里一定要將偏移量設(shè)置為顯示中間那張圖瞭吃。
self.contentOffset = CGPointMake(CGRectGetWidth(frame), CGRectGetMinY(frame));
self.delegate = self;
self.pagingEnabled = YES;
self.showsHorizontalScrollIndicator = NO;
//將當(dāng)前顯示圖片設(shè)置為第一張
self.currentPageInteger = 0;
}
return self;
}
-(void)showWith:(NSArray *)imageViewUrlArray
{
self.imageArray = imageViewUrlArray;
//創(chuàng)建添加左中右三個(gè)視圖
[self creaetLeftImageView];
[self createRightImageView];
[self createCenterImageView];
}
//左邊視圖
- (void)creaetLeftImageView{
NSInteger index = (self.currentPageInteger-1+self.imageArray.count)%self.imageArray.count;
self.leftImageView.frame =CGRectMake(0, 0, CGRectGetWidth(self.scrViewFrame), CGRectGetHeight(self.scrViewFrame));
[self.leftImageView sd_setImageWithURL:self.imageArray[index][@"pic"] placeholderImage:[UIImage imageNamed:@"tempImage"]];
self.leftImageView.contentMode = UIViewContentModeScaleAspectFill;
self.leftImageView.userInteractionEnabled = YES;
[self addSubview:self.leftImageView];
}
//中間視圖
- (void)createCenterImageView{
self.centerImageView.frame = CGRectMake(CGRectGetWidth(self.scrViewFrame), 0, CGRectGetWidth(self.scrViewFrame), CGRectGetHeight(self.scrViewFrame));
[self.centerImageView sd_setImageWithURL:self.imageArray[self.currentPageInteger][@"pic"] placeholderImage:[UIImage imageNamed:@"tempImage"]];
self.centerImageView.contentMode = UIViewContentModeScaleAspectFill;
self.centerImageView.userInteractionEnabled = YES;
[self addSubview:self.centerImageView];
}
//右邊視圖
- (void)createRightImageView{
self.rightImageView.frame = CGRectMake(CGRectGetWidth(self.scrViewFrame)*2, 0, CGRectGetWidth(self.scrViewFrame), CGRectGetHeight(self.scrViewFrame));
NSInteger index = (self.currentPageInteger+1+self.imageArray.count)%self.imageArray.count;
[self.rightImageView sd_setImageWithURL:self.imageArray[index][@"pic"] placeholderImage:[UIImage imageNamed:@"tempImage"]];
self.rightImageView.contentMode = UIViewContentModeScaleAspectFill;
self.rightImageView.userInteractionEnabled = YES;
[self addSubview:self.rightImageView];
}
因?yàn)闃侵魉杏玫亩际蔷W(wǎng)絡(luò)圖片碌嘀,所以直接用了SDWebImage涣旨,如果大家用的本地圖片歪架,這里的數(shù)組直接傳本地圖片名就行了。
到這一步霹陡,添加了三張圖和蚪,已經(jīng)可以滾動(dòng)了,但是只能左右各滾動(dòng)一下烹棉。無法無限滾動(dòng)攒霹,然后再理一下思路:
看一下圖片
用戶看到的永遠(yuǎn)是2這個(gè)畫面,當(dāng)用戶往右滑動(dòng)的時(shí)候看到了三浆洗,滑動(dòng)完之后我們立刻將2這個(gè)位置的圖片設(shè)置為3催束,將3這個(gè)位置的圖片設(shè)置為4(假設(shè)一共5張圖)將1這個(gè)位置的圖片設(shè)置為2,然后立刻將ScrollView的contentOffSet再次移動(dòng)回2這個(gè)位置伏社,并且不用動(dòng)畫效果抠刺。因?yàn)檫@個(gè)動(dòng)作太快,導(dǎo)致用戶根本看不出這個(gè)變化的過程摘昌。誤認(rèn)為是在輪播速妖。
下面上代碼:
思路:只要當(dāng)用戶一拖拽完屏幕,我們就需要判斷聪黎,用戶有沒有翻頁(當(dāng)用戶拖拽沒有超過屏幕一半的距離的時(shí)候罕容,ScrollView是不會(huì)翻頁的),往左翻頁還是往右翻頁,并根據(jù)往左還是往右锦秒,來設(shè)置相應(yīng)的圖片來顯示露泊。
每次當(dāng)ScrollView的滑動(dòng)停止時(shí)就會(huì)調(diào)用一次下面這個(gè)方法(ScrollView的代理方法),以此來抓住用戶一拖拽完就進(jìn)行圖片數(shù)據(jù)處理的時(shí)機(jī)旅择。
//當(dāng)滾動(dòng)停止?jié)L動(dòng)時(shí)調(diào)用此方法滤淳。
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//因?yàn)槿绻捶摮晒Γ琧ontentOffSet會(huì)回到原點(diǎn)砌左,所以這里只需要對(duì)大于0小于0進(jìn)行判斷即可脖咐。
//獲取滾動(dòng)視圖移動(dòng)的距離。
CGFloat userDistance = self.contentOffset.x - CGRectGetWidth(self.scrViewFrame);
if (userDistance < 0 ) {
//往左翻頁汇歹,將currentPage往上翻頁
self.currentPageInteger = (self.currentPageInteger - 1 + self.imageArray.count)% self.imageArray.count;
[self resetContent];
}else if (userDistance > 0){
//往右翻頁屁擅,將currentPage往下翻頁
self.currentPageInteger = (self.currentPageInteger + 1 + self.imageArray.count)%self.imageArray.count;
[self resetContent];
}else{
//用戶未翻頁成功,什么都不做产弹。
}
}
改變顯示圖片的數(shù)據(jù)后派歌,馬上調(diào)用重置數(shù)據(jù)方法:
#pragma mark ---- 用戶翻頁后重置數(shù)據(jù)
- (void)resetContent{
//重置偏移量
CGPoint offset = CGPointMake(CGRectGetWidth(self.scrViewFrame), 0);
[self setContentOffset:offset];
//重置圖片
NSInteger leftIndex = (self.currentPageInteger-1+self.imageArray.count)%self.imageArray.count;
NSInteger centerIndex = self.currentPageInteger;
NSInteger rightIndex = (self.currentPageInteger+1+self.imageArray.count)%self.imageArray.count;
[self.leftImageView sd_setImageWithURL:self.imageArray[leftIndex][@"pic"] placeholderImage:[UIImage imageNamed:@"tempImage"]];
[self.centerImageView sd_setImageWithURL:self.imageArray[centerIndex][@"pic"] placeholderImage:[UIImage imageNamed:@"tempImage"]];
[self.rightImageView sd_setImageWithURL:self.imageArray[rightIndex][@"pic"] placeholderImage:[UIImage imageNamed:@"tempImage"]];
}
到這里,你的滾動(dòng)視圖已經(jīng)可以無限輪播了痰哨。接下來要做的就是胶果,添加定時(shí)器實(shí)現(xiàn)自動(dòng)輪播。
關(guān)于定時(shí)器的一些基本知識(shí)不懂的可以看這里:
http://www.cnblogs.com/zhang6332/p/6253466.html
首先在init方法中啟動(dòng)定時(shí)器,就一行代碼(下面貼的是init方法中加入了啟動(dòng)定時(shí)器后的代碼斤斧,注意看注釋都寫的很清楚)
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.scrViewFrame = frame;
self.contentSize = CGSizeMake(CGRectGetWidth(frame)*3, CGRectGetHeight(frame));
//這里一定要將偏移量設(shè)置為顯示中間那張圖早抠。
self.contentOffset = CGPointMake(CGRectGetWidth(frame), CGRectGetMinY(frame));
self.delegate = self;
self.pagingEnabled = YES;
self.showsHorizontalScrollIndicator = NO;
//將當(dāng)前顯示圖片設(shè)置為第一張
self.currentPageInteger = 0;
//啟動(dòng)定時(shí)器
self.rotateTimer = [NSTimer scheduledTimerWithTimeInterval:4 target:self selector:@selector(aoutScroll) userInfo:nil repeats:YES];
}
return self;
}
啟動(dòng)定時(shí)器后調(diào)用的aoutScroll方法如下:
注意,當(dāng)定時(shí)器自動(dòng)翻頁的時(shí)候需要用動(dòng)畫撬讽。
#pragma mark --- 定時(shí)器自動(dòng)翻頁方法
- (void)aoutScroll{
NSLog(@"定時(shí)器被調(diào)用------------------------");
//這里需要判斷如果用戶正在拖動(dòng)屏幕或者視圖正在滾動(dòng)蕊连,是不可以自動(dòng)翻頁的,避免和用戶的操作相沖突游昼。
if (![self isDragging] || ![self isDecelerating]) {
//這里只對(duì)contentOffset進(jìn)行設(shè)置甘苍,因?yàn)橐坏┰O(shè)置了contentOffSet后代理就會(huì)自動(dòng)調(diào)用- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView此方法,代碼重用烘豌,會(huì)利用我們上面寫好的邏輯幫我們處理剩下的東西载庭。
[self setContentOffset:CGPointMake(CGRectGetWidth(self.scrViewFrame)*2, 0) animated:YES];
}
}
添加好定時(shí)器之后還需要考慮,如果用戶正在拖動(dòng)視圖或者視圖正在滾動(dòng)廊佩,那么此時(shí)需要先暫停定時(shí)器囚聚,當(dāng)用戶的拖拽動(dòng)作結(jié)束之后再開啟定時(shí)器。防止和用戶動(dòng)作沖突罐寨。
#pragma mark --- ScrollView代理
//當(dāng)調(diào)用contentoffset方法動(dòng)畫完畢時(shí)調(diào)用次方法
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
[self scrollViewDidEndDecelerating:self];
NSLog(@"調(diào)用方法contentoffset方法動(dòng)畫完畢時(shí)調(diào)用次方法+++++++++++");
}
//當(dāng)用手指拖拽的時(shí)候調(diào)用次方法
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
NSLog(@"正在拖拽視圖靡挥,所以需要將自動(dòng)播放暫停掉");
//setFireDate:設(shè)置定時(shí)器在什么時(shí)間啟動(dòng)
//[NSDate distantFuture]:將來的某一時(shí)刻
[self.rotateTimer setFireDate:[NSDate distantFuture]];
}
當(dāng)用戶屏幕停止?jié)L動(dòng)后開啟定時(shí)器。(注意看注釋)
//當(dāng)手指拖拽產(chǎn)生的滾動(dòng)停止?jié)L動(dòng)時(shí)調(diào)用此方法鸯绿。
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//視圖靜止之后跋破,過4秒在開啟定時(shí)器
NSLog(@"開啟定時(shí)器");
[self.rotateTimer setFireDate:[NSDate dateWithTimeInterval:4 sinceDate:[NSDate date]]];
NSLog(@"scrollViewDidEndDecelerating------------%f",self.contentOffset.x);
//獲取滾動(dòng)視圖移動(dòng)的距離簸淀。
CGFloat userDistance = self.contentOffset.x - CGRectGetWidth(self.scrViewFrame);
if (userDistance < 0 ) {
//往左翻頁,將currentPage往上翻頁
self.currentPageInteger = (self.currentPageInteger - 1 + self.imageArray.count)% self.imageArray.count;
[self resetContent];
}else if (userDistance > 0){
//往右翻頁毒返,將currentPage往下翻頁
self.currentPageInteger = (self.currentPageInteger + 1 + self.imageArray.count)%self.imageArray.count;
[self resetContent];
}else{
//用戶未翻頁成功租幕,什么都不做。
}
}
到這里拧簸,已經(jīng)可以無限自動(dòng)滾動(dòng)了劲绪。接下來需要添加pageControl
------------------------------------------我是分割線-----------------
新建
ZWCheaderView.h
ZWCheaderView.m
同樣在ZWCheaderView.h中公布兩個(gè)相同的方法
- (instancetype)initWithFrame:(CGRect)frame;
- (void)showWith:(NSArray *)imageViewUrlArray;
在ZWCheaderView.m中聲明兩個(gè)屬性
//滾動(dòng)視圖
@property(nonatomic,strong)ZWCHeaderViewScrollView *bannerScrView;
//小圓點(diǎn)控制器
@property(nonatomic,strong)UIPageControl *bannerpageControl;
bannerpageControl懶加載
-(UIPageControl *)bannerpageControl{
if (_bannerpageControl == nil) {
_bannerpageControl = [[UIPageControl alloc]initWithFrame:CGRectMake(CGRectGetWidth(self.frame)-80, CGRectGetHeight(self.frame)-30, 40, 20)];
}
return _bannerpageControl;
}
整理一下思路:
需要設(shè)置一個(gè)KVO 當(dāng)ScrollView的currentPage變化時(shí)說明滾動(dòng)視圖翻頁了,那么用kvo設(shè)置小圓點(diǎn)到相應(yīng)的位置盆赤。
下面的代碼我想小白都應(yīng)該看得懂了贾富。
主要思路就是:
1、將滾動(dòng)視圖添加到這個(gè)View中
2牺六、將pageControl添加到這個(gè)視圖中
3颤枪、通過KVO來監(jiān)控滾動(dòng)視圖的翻頁狀況,并同步設(shè)置小圓點(diǎn)的位置淑际。
4畏纲、消除KVO,消除定時(shí)器春缕。
重寫init方法,并設(shè)置一些屬性
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.bannerScrView = [[ZWCHeaderViewScrollView alloc]initWithFrame:frame];
[self addSubview:self.bannerpageControl];
//設(shè)置KVO
[self.bannerScrView addObserver:self forKeyPath:@"currentPageInteger" options:NSKeyValueObservingOptionNew context:nil];
[self addSubview:self.bannerScrView];
//將小圓點(diǎn)設(shè)置在圖層最上層防止被ScrollView蓋住
[self bringSubviewToFront:self.bannerpageControl];
}
return self;
}
在show方法中調(diào)用ZWCHeaderViewScrollView.h中的show方法盗胀,并吧數(shù)據(jù)傳過去同時(shí)設(shè)置一些pageControl的屬性。
- (void)showWith:(NSArray *)imageViewUrlArray{
[self.bannerScrView showWith:imageViewUrlArray];
self.bannerpageControl.numberOfPages = imageViewUrlArray.count;
self.bannerpageControl.userInteractionEnabled = NO;
self.bannerpageControl.currentPage = 0;
self.bannerpageControl.currentPageIndicatorTintColor = [UIColor redColor];
self.bannerpageControl.pageIndicatorTintColor = [UIColor yellowColor];
}
實(shí)現(xiàn)KVO的回調(diào)方法锄贼,對(duì)pageControl進(jìn)行實(shí)時(shí)的設(shè)置票灰。
//kvo監(jiān)控翻頁動(dòng)作
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"currentPageInteger"]) {
self.bannerpageControl.currentPage = [[change valueForKey:@"new"] integerValue];
NSLog(@"當(dāng)前頁已改變------%@",[change valueForKey:@"new"]);
}
}
最后在ZWCheaderView的dealloc中移除KVO,在ZWCHeaderViewScrollView的dealloc中停止定時(shí)器。這里就涉及到我上面說的兩個(gè)可能有坑的問題咱娶,希望大神可以指教米间。
ZWCheaderView.m中
- (void)dealloc
{
//移除KVO强品,注意檢查這里會(huì)不會(huì)移除不完全
[_bannerScrView removeObserver:self forKeyPath:@"currentPageIntegere"];
}
ZWCHeaderViewScrollView.m中
-(void)dealloc{
//停止定時(shí)器膘侮,防止crash
[_rotateTimer invalidate];
}
好了到這里就完了。謝謝大家一直可以看完的榛,如文中有錯(cuò)誤的地方或者可以改進(jìn)的地方希望可以指正琼了。