前言
市面上绝大部分的APP被打开之后映入眼帘的都是一个美轮美奂的轮播器,所以能做出一个符合需求、高效的轮播器成为了一个程序员的必备技能。所以今天的这篇博客就来谈谈轮播器这个看似简单的控件其中蕴含的道理。
正文
首先我们来分析一下该如何去实现一个类似下图的轮播器(图片数量、URL由服务器返回):
策略一:UIScrollView->UIImageView->NSTimer轮询 这算是常规的策略,但是如果仔细想想,如果服务器返回给你50图片是不是就需要创建50个UIImageView来做容器。这种性能肯定不是最优的。其次所有的UIImageView是按照次序排列,脑补一下如果到最后一张图片要重新回到第一张图片你的话,UIScrollView会被升拉到第一个位置。效果太差!
策略二:想想UITableViewCell的重复利用,我们也可以重复利用其中的UIImageView。对于我们来说轮播器三个UIImageView就足够用了,分为当前视野中的CenterImageView、前一张LeftImageView、后一张RightImageView。不同的是他们之间的图片切换,下面我们就尝试去做高效的轮播器。
代码实现
- 初始化所需控件:
2.写一个专门控制没次滑动结束去计算左中右三张序号并加载成对应的图片。
1 /** 这里我起名叫reloadAllImageView */ 2 3 -(void)reloadAllImageView{ 4 5 CGPoint offset = _backScrollView.contentOffset; /** 获取到scroll的X轴偏移量 */ 6 7 8 9 if (offset.x == 2 * DeviceWidth){ /** 这里有两种边界情况要处理 (1).由first—>last (2)last->first */ 10 11 /** (2) */ 12 13 _centerImageIndex = (_centerImageIndex + 1)%3; /** 3代表图片的数量,这里要主页类型的强转换。_centerImageIndex(NSUInteger)用于记录当前图片序号*/ 14 15 _pageControl.currentPage = (_pageControl.currentPage + 1)%3; 16 17 } 18 19 else if (offset.x == 0){ 20 21 /** (1) */ 22 23 if (_centerImageIndex == 0) { /** 一种特殊情况 当_centerImageIndex等于0的时候 去计算(_centerImageIndex - 1) % 3并不是我们想要的结果 */ 24 25 _centerImageIndex = 3; /** 尝试了很久,计算类型转换也就只有三张图片会有问题。如果有兴趣的朋友可以进行深入研究 */ 26 27 } /** -1 % 3 = 0 如果都是有符号的结果是-1,如果按照无符号处理的话结果是0。难道计算过程应该先向前借位再进行计算? */ 28 29 _centerImageIndex = (_centerImageIndex - 1) % 3; 30 31 _pageControl.currentPage = (_pageControl.currentPage - 1)%3; 32 33 } 34 35 _centerImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"图%d.jpg",_centerImageIndex + 1]]; 36 37 NSUInteger leftImageViewIndex = (_centerImageIndex - 1)%3; 38 39 NSUInteger rightImageViewIndex = (_centerImageIndex + 1)%3; 40 41 if (leftImageViewIndex == 0 && _centerImageIndex == 0) { /** 同上暂时处理计算特殊情况 */ 42 43 leftImageViewIndex = 2; 44 45 } 46 47 _leftImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"图%d.jpg",leftImageViewIndex + 1]]; 48 49 _rightImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"图%d.jpg",rightImageViewIndex + 1]]; 50 51 }
3.现在轮播器应该可以在边界正常切换了。现在需要再加上一个计时器来自动滑动即可:
1 /** 声明一个定时器 */ /** 用weak的原因:self如果强拥有了Timer,之后你要设置计时器的Traget和选择子selector的时候,Timer又会保留目标对象直到失效。产生保留环 */ 2 3 @property(nonatomic,weak)NSTimer * timer; 4 5 /** 定时器初始化 */ 6 7 -(void)initTimer 8 9 { 10 11 self.timer = [NSTimer scheduledTimerWithTimeInterval:animationTime target:self selector:@selector(updateImageView:) userInfo:nil repeats:YES]; 12 13 } 14 15 -(void)updateImageView:(NSTimer *)timer 16 17 { 18 19 [_backScrollView setContentOffset:CGPointMake(DeviceWidth*2, 0) animated:YES]; 20 21 [NSTimer scheduledTimerWithTimeInterval:0.4f target:self selector:@selector(scrollViewDidEndDecelerating:) userInfo:nil repeats:NO]; 22 23 } 24 25 /** 计算加载所有图片然后移动的中间视野 */ 26 27 -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 28 29 //重新加载图片 30 31 [self reloadAllImageView]; 32 33 //移动到当前视野 34 35 [_backScrollView setContentOffset:CGPointMake(DeviceWidth, 0)]; 36 37 //设置脚标 38 39 _pageControl.currentPage = _centerImageIndex; 40 41 }
4.自此轮播器大概雏形已经搞定了。剩下的就是需要搞定计时器和用户滑动操作的互斥事件处理。
/**记录一个bool值用于确定滑动操作的愿意你。YES,计时器触发,NO则为用户触发*/ (1).计时器的触发事件中,肯定是由计时器触发的滑动。所以这里bool值为YES (2).加载替换图片的时候我们要进行判断(如果是用户触发的时候我们要将计时器取消并从空为0) if (!bool) { [self.timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:animationTime]]; } bool = NO;
结尾
由此轮播器就实现了,但是其中还是有问题需要解决<1>.限制必须要三张图片不然会crash <2>没有封装成单独的scrllview以供使用。后续可能会对这些问题加以思考并重新优化。也许尝试用UICollection来做也是个很好的想法。
最后经过写这篇博客也有一个目的,其实任何一个看似简单的功能要深入挖掘的话还是有很说知识的,也发现了自身的不足。最后如果各位大神们看到了博客有任何想法意见的欢迎下面留言。大家一起探讨一起进步。谢谢各位!