iOS Developer

轮播大法——SDCycleScrollView 源码思路解析

2017-07-05  本文已影响218人  吴佩在天涯

SDCycleScrollView 轮播图源码解析

一、开篇

轮播图控件,在 iOS 应用上是有很多应用的。

这个第三方框架,在 github 上有 4000 个 star。

出于好奇,研究了一下。

不要看,这是有 4000 个 star 的第三方库,它的实现原理其实是可以用一句话概括的:

UICollectionView 的重用机制 和 数学的 取模运算

总体思路:

利用 UICollectionView 的重用机制,保证图片切换的流畅。

利用重用机制,在设计的时候,有意让 item 的数量是需要展示的图片数量的 100 倍。

例如:需要轮播的图片数量是 5 张,那么 UICollectionView 的 item 的总数 _totalItemsCount 其实是 5X100 = 500 个。

在初始化布局子控件的时候,laySubview 方法里面直接是调用:

scrollToItemAtIndexPath: atScrollPosition:animated: 方法,

设置默认的显示图片的 item 是 _totalItemsCount X 0.5 ,也就是第 250 个,对应显示数组里面的第 0 张图片。

500 个 item 的排列大概是这样的:

0 1 2 3 4; 0 1 2 3 4;........ 0 1 2 3 4; 0 1 2 3 4;

一共有 100 组 0 到 4 的数组图片。

乘以 0.5 ,是为了可以显示在 500 item 的中间位置。

这样的话,左右滑动都是可以看到 item 的。

这个框架里面严格来说:只是实现了永久自动循环轮播,没有实现永久手动循环轮播。

这里用语言描述实在是太困难了。

所以,我索性把代码敲了一遍,相较于原有框架更容易理解清楚。

点击这里,下载示例代码

二、 SDCycleScrollView 类详解

SDCycleScrollView 框架里面最重要的类就是 SDCycleScrollView ,理解了这个类,基本思想就完全可以搞定。

下面开始介绍。

SDCycleScrollView 继承自 UIView ,本质是一个 UIView 。

我的代码里面与框架里面对应的类名是: WPCycleScrollView。

请读者下载我的代码,对照阅读。

下面是 WPCycleScrollView.m 文件的

1、主要属性:

      @interface WPCycleScrollView ()              <UICollectionViewDelegate,UICollectionViewDataSource>

      @property (nonatomic, weak) UICollectionView *mainView;// 显示图片的 collectionView

      @property (nonatomic, weak) UICollectionViewFlowLayout *flowLayout;// 布局属性

      @property (nonatomic, strong) NSArray *imageGroup;//图片数组

      @property (nonatomic, assign) NSInteger totalItems;// item 的数量

@property (nonatomic, weak) NSTimer *timer;// 定时器

@end

2、布局子控件的方法:

- (void)layoutSubviews {
    
    [super layoutSubviews];
    
    _mainView.frame = self.bounds;

    if (_mainView.contentOffset.x == 0 && _totalItems > 0) {
        NSInteger targeIndex = 0;
        if (self.infiniteLoop) {//无限循环
            // 如果是无限循环,应该默认把 collection 的 item 滑动到 中间位置。
            // 注意:此处 totalItems 的数值,其实是图片数组数量的 100 倍。
            // 乘以 0.5 ,正好是取得中间位置的 item 。图片也恰好是图片数组里面的第 0 个。
            targeIndex = _totalItems * 0.5;
        }else {
            targeIndex = 0;
        }

        //默认的图片位置
        [_mainView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:targeIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
    }
}

注意:

默认显示的是 UICollectionView 的中间的 item,这样就避免了一开始滑动就滑到了尽头。

这是设计巧妙的地方。

3、设置图片数组,同时设置 item 数量

- (void)setImageGroup:(NSArray *)imageGroup {
    
    _imageGroup = imageGroup;
    
    // 这一句是关键,如果是无限循环,就让 item 的总数乘以 100 。
    
    _totalItems = self.infiniteLoop ? imageGroup.count * 100 : imageGroup.count;
    
    if (_imageGroup.count > 1) {
        self.mainView.scrollEnabled = YES;
        //处理是否自动滑动,定时器问题
        [self setAutoScroll:self.autoScroll];
    }else{
        self.mainView.scrollEnabled = NO;
        [self setAutoScroll:NO];
    }
    
    
    
    [self.mainView reloadData];
    
}

4、计算当前的页码

    - (NSInteger)currentIndex {
        
        if (_mainView.frame.size.width == 0 || _mainView.frame.size
            .height == 0) {
            return 0;
        }
        
        NSInteger index = 0;
        
        if (_flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) {//水平滑动
            index = (_mainView.contentOffset.x + _flowLayout.itemSize.width * 0.5) / _flowLayout.itemSize.width;
        }else{
            index = (_mainView.contentOffset.y + _flowLayout.itemSize.height * 0.5)/ _flowLayout.itemSize.height;
        }
        return MAX(0,index);
    }

注意:

此处的运算方法。都是加了 0.5 的。这里是实现四舍五入的计算。
也就是说,偏移量一旦大于一半的宽度或者高度,就增加或者减少一个页码。

5、item 方法

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    WPCycleScrollViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];

    // 利用取余运算,使得图片数组里面的图片,是一组一组的排列的。
    long itemIndex = [self pageControlIndexWithCurrentCellIndex:indexPath.item];
    
    cell.img = [UIImage imageNamed:self.imageGroup[itemIndex]];
    
    return cell;
    
}

- (int)pageControlIndexWithCurrentCellIndex:(NSInteger)index {
    return (int)index % self.imageGroup.count;
}

上面的方法是设置,UICollectionView 的 item 。

需要注意的是:

这里的 % 运算,使得图片数组里面的图片,可以实现
0 1 2 3 4; 0 1 2 3 4 .... 的循环

具体细节,还需要各位去体会。我已经把代码精简了。应该是一目了然。

三、小结

不得不佩服,作者对于 iOS 重用机制的灵活应用。

最难理解的部分,其实是为什么作者要把 item 的数量,在需要显示的图片数量的基础上乘以 100 呢?

其实,我自己测试过,只要是 10 的倍数都是可以的。

你可以自己乘以 10 或者 1000 ,甚至是 10000 都是没有问题。

这里的数值大小,根本不会影响 APP 最好的渲染。

因为,重用机制,并不会同时生成那么多 item,而是循环利用。

在我看来,最少只要两个 item ,其实已经实现了 item 的切换。

这个原理和 UITableView 的 cell 重用机制是一样的。

只要是 10 的倍数就是可以的,这样是便于乘以 0.5 的时候,定位的 item 刚好是数组图片的第一个。

这个很巧妙。欢迎大家,和我交流。

再次放上我的代码,如果大家觉得写得不错,请 star 一下。谢谢。

[点击这里,下载示例代码](https://github.com/wuxiaopei/
WPCycleScrollView)

上一篇下一篇

猜你喜欢

热点阅读