高效圖片輪播,兩個(gè)imageView實(shí)現(xiàn)
該輪播框架的優(yōu)勢(shì):
- 文件少,代碼簡(jiǎn)潔
- 不依賴任何其他第三方庫(kù),耦合度低
- 同時(shí)支持本地圖片及網(wǎng)絡(luò)圖片
- 可修改分頁(yè)控件位置,顯示或隱藏
- 自定義分頁(yè)控件的圖片,就是這么個(gè)性
- 自帶圖片緩存,一次加載,永久使用
- 性能好,占用內(nèi)存少,輪播流暢
實(shí)際使用
我們先看demo,代碼如下
運(yùn)行效果
輪播實(shí)現(xiàn)步驟
接下來(lái),筆者將從各方面逐一分析。
層級(jí)結(jié)構(gòu)
最底層是一個(gè)UIView,上面有一個(gè)UIScrollView以及UIPageControl,scrollView上有兩個(gè)UIImageView,imageView寬高 = scrollview寬高 = view寬高
輪播原理
假設(shè)輪播控件的寬度為x高度為y,我們?cè)O(shè)置scrollview的contentSize.width為3x,并讓scrollview的水平偏移量為x,既顯示最中間內(nèi)容
- scrollView.contentSize = CGSizeMake(3x, y);
- scrollView.contentOffset = CGPointMake(x, 0);
將imageView添加到scrollview內(nèi)容視圖的中間位置
接下來(lái)使用代理方法scrollViewDidScroll來(lái)監(jiān)聽(tīng)scrollview的滾動(dòng),定義一個(gè)枚舉變量來(lái)記錄滾動(dòng)的方向
- typedef enum{
- DirecNone,
- DirecLeft,
- DirecRight
- } Direction;@property (nonatomic, assign) Direction direction;
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView { self.direction = scrollView.contentOffset.x >x? DirecLeft : DirecRight;
- }
使用KVO來(lái)監(jiān)聽(tīng)direction屬性值的改變
- [self addObserver:self forKeyPath:@"direction" options:NSKeyValueObservingOptionNew context:nil];
判斷滾動(dòng)的方向,當(dāng)偏移量大于x,表示左移,則將otherImageView加在右邊,偏移量小于x,表示右移,則將otherImageView加在左邊
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { //self.currIndex表示當(dāng)前顯示圖片的索引,self.nextIndex表示將要顯示圖片的索引
- //_images為圖片數(shù)組
- if(change[NSKeyValueChangeNewKey] == change[NSKeyValueChangeOldKey]) return; if ([change[NSKeyValueChangeNewKey] intValue] == DirecRight) { self.otherImageView.frame = CGRectMake(0, 0, self.width, self.height); selfself.nextIndex = self.currIndex - 1; if (self.nextIndex < 0) self.nextIndex = _images.count – 1;
- } else if ([change[NSKeyValueChangeNewKey] intValue] == DirecLeft){ self.otherImageView.frame = CGRectMake(CGRectGetMaxX(_currImageView.frame), 0, self.width, self.height); self.nextIndex = (self.currIndex + 1) % _images.count;
- } selfself.otherImageView.image = self.images[self.nextIndex];
- }
通過(guò)代理方法scrollViewDidEndDecelerating來(lái)監(jiān)聽(tīng)滾動(dòng)結(jié)束,結(jié)束后,會(huì)變成以下兩種情況:
此時(shí),scrollview的偏移量為0或者2x,我們通過(guò)代碼再次將scrollview的偏移量設(shè)置為x,并將currImageView的圖片修改為otherImageView的圖片
- - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
- [self pauseScroll];
- }
- - (void)pauseScroll { self.direction = DirecNone;//清空滾動(dòng)方向
- //判斷最終是滾到了右邊還是左邊
- int index = self.scrollView.contentOffset.x / x; if (index == 1) return; //等于1表示最后沒(méi)有滾動(dòng),返回不做任何操作
- selfself.currIndex = self.nextIndex;//當(dāng)前圖片索引改變
- selfself.pageControl.currentPage = self.currIndex; self.currImageView.frame = CGRectMake(x, 0, x, y); selfself.currImageView.image = self.otherImageView.image; self.scrollView.contentOffset = CGPointMake(x, 0);
- }
那么我們看到的還是currImageView,只不過(guò)展示的是下一張圖片,如圖,又變成了最初的效果
自動(dòng)滾動(dòng)
輪播的功能實(shí)現(xiàn)了,接下來(lái)添加定時(shí)器讓它自動(dòng)滾動(dòng),相當(dāng)簡(jiǎn)單
- - (void)startTimer { //如果只有一張圖片,則直接返回,不開(kāi)啟定時(shí)器
- if (_images.count <= 1) return; //如果定時(shí)器已開(kāi)啟,先停止再重新開(kāi)啟
- if (self.timer) [self stopTimer]; self.timer = [NSTimer timerWithTimeInterval:self.time target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
- [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
- }
- - (void)nextPage { //動(dòng)畫(huà)改變scrollview的偏移量就可以實(shí)現(xiàn)自動(dòng)滾動(dòng)
- [self.scrollView setContentOffset:CGPointMake(self.width * 2, 0) animated:YES];
- }
注意:setContentOffset:animated:方法執(zhí)行完畢后不會(huì)調(diào)用scrollview的scrollViewDidEndDecelerating方法,但是會(huì)調(diào)用scrollViewDidEndScrollingAnimation方法,因此我們要在該方法中調(diào)用pauseScroll
- - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
- [self pauseScroll];
- }
拖拽時(shí)停止自動(dòng)滾動(dòng)
當(dāng)我們手動(dòng)拖拽圖片時(shí),需要停止自動(dòng)滾動(dòng),此時(shí)我們只需要讓定時(shí)器失效就行了,當(dāng)停止拖拽時(shí),重新啟動(dòng)定時(shí)器
- - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
- [self.timer invalidate];
- }
- - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
- [self startTimer];
- }
加載圖片
實(shí)際開(kāi)發(fā)中,我們很少會(huì)輪播本地圖片,大部分都是服務(wù)器獲取的,也有可能既有本地圖片,也有網(wǎng)絡(luò)圖片,那要如何來(lái)加載呢?
定義4個(gè)屬性
- NSArray imageArray:暴露在.h文件中,外界將要加載的圖片或路徑數(shù)組賦值給該屬性
- NSMutableArray images:用來(lái)存放圖片的數(shù)組
- NSMutableDictionary imageDic:用來(lái)緩存圖片的字典,key為URL
- NSMutableDictionary operationDic:用來(lái)保存下載操作的字典,key為URL
判斷外界傳入的是圖片還是路徑,如果是圖片,直接加入圖片數(shù)組中,如果是路徑,先添加一個(gè)占位圖片,然后根據(jù)路徑去下載圖片
- _images = [NSMutableArray array];for (int i = 0; i < imageArray.count; i++) { if ([imageArray[i] isKindOfClass:[UIImage class]]) {
- [_images addObject:imageArray[i]];//如果是圖片,直接添加到images中
- } else if ([imageArray[i] isKindOfClass:[NSString class]]){
- [_images addObject:[UIImage imageNamed:@"placeholder"]];//如果是路徑,添加一個(gè)占位圖片到images中
- [self downloadImages:i]; //下載網(wǎng)絡(luò)圖片
- }
- }
下載圖片,先從緩存中取,如果有,則替換之前的占位圖片,如果沒(méi)有,去沙盒中取,如果有,替換占位圖片,并添加到緩存中,如果沒(méi)有,開(kāi)啟異步線程下載
- - (void)downloadImages:(int)index { NSString *key = _imageArray[index]; //從字典緩存中取圖片
- UIImage *image = [self.imageDic objectForKey:key]; if (image) {
- _images[index] = image;//如果圖片存在,則直接替換之前的占位圖片
- }else{ //字典中沒(méi)有從沙盒中取圖片
- NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *path = [cache stringByAppendingPathComponent:[key lastPathComponent]]; NSData *data = [NSData dataWithContentsOfFile:path]; if (data) { //沙盒中有,替換占位圖片,并加入字典緩存中
- image = [UIImage imageWithData:data];
- _images[index] = image;
- [self.imageDic setObject:image forKey:key];
- }else{ //字典沙盒都沒(méi)有,下載圖片
- NSBlockOperation *download = [self.operationDic objectForKey:key];//查看下載操作是否存在
- if (!download) {//不存在
- //創(chuàng)建一個(gè)隊(duì)列,默認(rèn)為并發(fā)隊(duì)列
- NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //創(chuàng)建一個(gè)下載操作
- download = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:key]; NSData *data = [NSData dataWithContentsOfURL:url]; if (data) { //下載完成后,替換占位圖片,存入字典并寫(xiě)入沙盒,將下載操作從字典中移除掉
- UIImage *image = [UIImage imageWithData:data];
- [self.imageDic setObject:image forKey:key]; self.images[index] = image; //如果只有一張圖片,需要在主線程主動(dòng)去修改currImageView的值
- if (_images.count == 1) [_currImageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
- [data writeToFile:path atomically:YES];
- [self.operationDic removeObjectForKey:key];
- }
- }];
- [queue addOperation:download];
- [self.operationDic setObject:download forKey:key];//將下載操作加入字典
- }
- }
- }
- }
監(jiān)聽(tīng)圖片點(diǎn)擊
當(dāng)圖片被點(diǎn)擊的時(shí)候,我們往往需要執(zhí)行某些操作,因此需要監(jiān)聽(tīng)圖片的點(diǎn)擊,思路如下
1.定義一個(gè)block屬性暴露給外界void(^imageClickBlock)(NSInteger index)
(不會(huì)block的可以用代理,或者看這里)
2.設(shè)置currImageView的userInteractionEnabled為YES
3.給currImageView添加一個(gè)點(diǎn)擊的手勢(shì)
4.在手勢(shì)方法里調(diào)用block,并傳入圖片索引
結(jié)束語(yǔ)
上面是筆者的主要思路以及部分代碼,需要源碼的請(qǐng)前往筆者的github下載:https://github.com/codingZero/XRCarouselView,記得獻(xiàn)上你的星星哦