兩種方式實現輪播圖

奔跑的鴻發表於2022-02-11

概述

  • 輪播圖可以用UIScrollView或UICollectionView來實現。
  • 用UIScrollView實現輪播圖的思路是:
    給圖片陣列的第一個元素(下標0)新增最後一張圖片,之後再往末尾新增第一張圖片,這樣陣列就增加了2張圖片,第一個元素和倒數第二個元素是最後一張圖片,最後一個元素和第二個元素是第一張圖片。
    根據圖片陣列的個數建立UIImageView個數,每個UIImageView佔據螢幕寬度。首次展示圖片時,scrollView定位到(contentOffset)陣列的第二個元素,展示第一張圖片內容。當滾動到最後一個元素時,讓scrollView定位到第二個元素。當滾動到第一個元素時,讓scrollView定位到倒數第二個元素。
    這種實現方式的特點:有多少張圖片就要建立多少個UIImageView控制元件。
  • 用UICollectionView實現輪播圖的思路是:
    處理圖片陣列的方式和UIScrollView相同,都是在圖片陣列的前和後各加入一張圖片。然後自定義一個view,UICollectionView是該view的子控制元件,佔滿該view的bounds,UICollectionViewCell也佔滿該view的bounds,UICollectionViewCell上面新增UIImageView。
    首次展示圖片時,collectionView定位到陣列的第二個元素,展示第一張圖片內容。當滾動到最後一個元素時,讓collectionView定位到第二個元素。當滾動到第一個元素時,讓collectionView定位到倒數第二個元素。
    這種實現方式的特點:無論多少圖片,最多隻建立3個cell,省記憶體。當輪播圖只有一張圖片時,cell只建立一個。當輪播圖兩張及兩張以上時,cell建立3個,圖片在這3個cell之間複用。
  • 無論是用UIScrollView還是用UICollectionView方式實現輪播,在橫向滾動圖片時,contentOffSet.x都在以螢幕寬度的大小改變,利用這一特徵,當圖片滾動到控制元件的左右邊界時,調整contentOffset就可以形成迴圈滾動的假象。

具體實現

  • 以下是用UICollectionView實現輪播的案例
    效果圖
兩種方式實現輪播圖

自定義HRCycleView,新增到控制器上。控制器的主要程式碼

- (void)viewDidLoad {
    [super viewDidLoad];
    
    HRCycleView *cycleView = [[HRCycleView alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, 200)];
    cycleView.data = @[@"num_1",@"num_2",@"num_3",@"num_4",@"num_5"];
    [self.view addSubview:cycleView];
}

HRCycleView事先建立好子控制元件UICollectionView和UIPageControl,提供data屬性用於接收圖片陣列。
處理迴圈播放的關鍵在於collectionView的代理方法-collectionView:didEndDisplayingCell:forItemAtIndexPath:,當cell完全移出螢幕時,該方法會獲得回撥,可在該方法中及時調整contentOffSet,讓使用者永遠無法感知到UICollectionView的左右邊界,具體程式碼如下:

#import "HRCycleView.h"
#import "HRCollectionViewCell.h"
    
//輪播間隔
static CGFloat ScrollInterval = 3.0f;
    
@interface HRCycleView ()<UICollectionViewDelegate,UICollectionViewDataSource>
    
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) UIPageControl *pageControl;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) NSIndexPath *nextIndexPath;
@end
    
@implementation HRCycleView
            
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        _autoPage = YES;
        [self buildUI];
    }
    return self;
}
    
static NSString *cellID = @"HRCollectionViewCell";
- (void)buildUI {
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    layout.itemSize = CGSizeMake(self.bounds.size.width, self.bounds.size.height);
    layout.minimumLineSpacing = 0;
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    self.collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
    self.collectionView.delegate = self;
    self.collectionView.dataSource = self;
    self.collectionView.pagingEnabled = true;
    self.collectionView.backgroundColor = [UIColor clearColor];
    [self.collectionView registerClass:[HRCollectionViewCell class] forCellWithReuseIdentifier:cellID];
    self.collectionView.showsHorizontalScrollIndicator = false;
    [self addSubview:self.collectionView];
    
    CGFloat controlHeight = 35.0f;
    self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, self.bounds.size.height - controlHeight, self.bounds.size.width, controlHeight)];
    self.pageControl.pageIndicatorTintColor = [UIColor lightGrayColor];
    self.pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
    [self addSubview:self.pageControl];
}
    
#pragma mark - CollectionViewDelegate&DataSource
    
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.dataArray.count;
}
    
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    HRCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
    cell.imageName = self.dataArray[indexPath.row];
    NSLog(@"offset.x:%.1f, cell地址:%p", collectionView.contentOffset.x, cell);
    return cell;
}
    
//cell剛移入螢幕時回撥,indexPath對應剛移入螢幕的cell下標
-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
    self.nextIndexPath = indexPath;
}
    
//cell完全移出螢幕時回撥,indexPath對應完全移出螢幕的cell下標
-(void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
        
    if (self.nextIndexPath != indexPath) {
        [self adjustScrollLocation];
    }
}
    
#pragma mark - UIScrollViewDelegate
//手指鬆開拖拽那一時刻呼叫
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    NSLog(@"scrollViewWillBeginDragging---");
    //停止定時器
    [self.timer setFireDate:[NSDate distantFuture]];
}
    
//手指鬆開拖拽時呼叫
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    //間隔3s繼續輪播
    if (_autoPage) {
        self.timer.fireDate = [NSDate dateWithTimeIntervalSinceNow:ScrollInterval];
    }
}
    
//調整迴圈顯示的定位
- (void)adjustScrollLocation {
    NSInteger page = self.collectionView.contentOffset.x/self.collectionView.bounds.size.width;
    if (page == 0) {//滾動到最左邊
        self.collectionView.contentOffset = CGPointMake(self.collectionView.bounds.size.width * (self.dataArray.count - 2), 0);
        self.pageControl.currentPage = self.dataArray.count-3;
    }else if (page == self.dataArray.count - 1){//滾動到最右邊
        self.collectionView.contentOffset = CGPointMake(self.collectionView.bounds.size.width, 0);
        self.pageControl.currentPage = 0;
    }else{
        self.pageControl.currentPage = page - 1;
    }
}
    
#pragma mark - Setter
//設定資料時在第一個之前和最後一個之後分別插入資料
- (void)setData:(NSArray<NSString *> *)data {
    self.dataArray = [NSMutableArray arrayWithArray:data];
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }
    
    if (data.count > 1) {
        [self.dataArray addObject:data.firstObject];
        [self.dataArray insertObject:data.lastObject atIndex:0];
        [self.collectionView setContentOffset:CGPointMake(self.collectionView.bounds.size.width, 0)];
            
        self.timer = [NSTimer scheduledTimerWithTimeInterval:ScrollInterval target:self selector:@selector(showNext) userInfo:nil repeats:true];
        if(_autoPage == NO) {
            self.timer.fireDate = [NSDate distantFuture];
        }
    }
    self.pageControl.numberOfPages = data.count<=1 ? 0 : data.count;
    self.pageControl.currentPage = 0;
    [self.collectionView reloadData];
}
    
- (void)setAutoPage:(BOOL)autoPage {
    _autoPage = autoPage;
    NSDate *fireDate = autoPage ? [NSDate dateWithTimeIntervalSinceNow:ScrollInterval] : [NSDate distantFuture];
    self.timer.fireDate = fireDate;
}
    
//自動顯示下一個
- (void)showNext {
    //手指拖拽是禁止自動輪播
    if (self.collectionView.isDragging) {return;}
//    CGFloat targetX =  self.collectionView.contentOffset.x + self.collectionView.bounds.size.width;
//    [self.collectionView setContentOffset:CGPointMake(targetX, 0) animated:true];
    
    int index = self.collectionView.contentOffset.x/self.collectionView.frame.size.width;
    [self.collectionView setContentOffset:CGPointMake((index+1)*self.collectionView.bounds.size.width, 0) animated:YES];
}
    
- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

HRCollectionViewCell上面展示UIImageView,程式碼如下

@interface HRCollectionViewCell ()
@property (nonatomic, strong) UIImageView *imageView;
@end
    
@implementation HRCollectionViewCell
    
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self buildUI];
    }
    return self;
}
    
- (void)buildUI {
    self.imageView = [[UIImageView alloc] initWithFrame:self.bounds];
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.contentView addSubview:self.imageView];
}
    
-(void)setImageName:(NSString *)imageName{
    UIImage *image = [UIImage imageNamed:imageName];
    self.imageView.image = image;
}
@end

用UIScrollView實現輪播圖的方式與HRCycleView程式碼很相似,具體實現和完整程式碼可檢視:CycleCollectionView

相關文章