为何要再写图片轮播控件
当然图片轮播控件这种东西真是被写烂了,这里再写一次的原因有三点:一是目前已知的图片轮播控件都在重复同样的功能点(类似于简书客户端的轮播),其实开发者对于图片轮播控件还有很多其它的需求;二是目前我并没有看到比较好的代码,同时基于开发者对于轮播控件的复杂需求,代码也会变得复杂起来(上千行的代码实现);三是我在学校里组织的兴趣小组打算同时编写iOS、Android、Web(基于JS)三个平台的代码,提供一致的特性和服务,并将其开源。
version 0.1所实现的功能和提供的特性
输入:用户提供控件的frame,控件所要播放的images
输出:一个图片轮播组件,能够定时切换图片。
详情请见需求文档
着手实现version 0.1
实现原理请参照
私有属性列表
@interface LZPPictureCarousel ()
@property (nonatomic, strong, nonnull) NSArray<UIImage *> *images;
@property (nonatomic, strong, nonnull) UIScrollView *imageScrollView;
@property (nonatomic, strong, nonnull) NSArray *imageViewArray;
@property (nonatomic, strong, nonnull) UIPageControl *pageControl;
@property (nonatomic, strong, nullable) NSTimer *timer; // null when timer is invalidate
@end
images:被展示的图片所组成的数组
imageScrollView:可滚动区域
imageViewArray:代码实现中的imageView数组
pageControl:页码指示器
timer:计时器,用于定时切换图片
为每一个私有属性提供getter,在getter中只做逻辑初始化,以imageScrollView为例
- (UIScrollView *)imageScrollView
{
if (!_imageScrollView) {
_imageScrollView = ({
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
scrollView.backgroundColor = [UIColor lightGrayColor];
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.bounces = NO;
scrollView.pagingEnabled = YES;
scrollView;
});
}
return _imageScrollView;
}
实例初始化逻辑
- (instancetype)initWithFrame:(CGRect)frame andImages:(nonnull NSArray<UIImage *> *)images
{
// security checking...
if (images == nil || images.count <= 0) {
return nil;
}
for (id obj in images) {
if (![obj isMemberOfClass:[UIImage class]]) {
return nil;
}
}
// custom initializer
self = [super initWithFrame:frame];
if (self) {
self.images = images;
[self customInitialization];
}
return self;
}
- (void)customInitialization
{
[self addSubview:self.imageScrollView];
[self addImageViewToScrollView];
[self addPageControl];
}
图片切换逻辑
根据设计原理,定时器触发时,scrollView要做的动作很简单,就是向左滑动一页。滑动后根据scrollView的偏移量来调整pageControl和scrollView的偏移量
- (void)moveToNextPage
{
CGFloat contentOffsetX = self.imageScrollView.contentOffset.x + self.imageScrollView.frame.size.width;
[UIView animateWithDuration:0.5 animations:^{
[self.imageScrollView setContentOffset:CGPointMake(contentOffsetX, 0)];
} completion:^(BOOL finished) {
[self adjustPageIndicatorAndContentOffset];
}];
}
- (void)adjustPageIndicatorAndContentOffset
{
NSInteger currentPage = floorf(self.imageScrollView.contentOffset.x / self.imageScrollView.frame.size.width + 0.5);
if (currentPage == 0) {
// the last page
[self.imageScrollView setContentOffset:CGPointMake(self.imageScrollView.frame.size.width * self.images.count, 0)];
self.pageControl.currentPage = self.images.count;
} else if (currentPage == self.images.count + 1) {
// the first page
[self.imageScrollView setContentOffset:CGPointMake(self.imageScrollView.frame.size.width, 0)];
self.pageControl.currentPage = 0;
} else {
self.pageControl.currentPage = currentPage - 1;
}
}
让图片动起来
定时器大法好,废话不多说,直接上代码
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[self startPlaying];
}
- (void)startPlaying
{
[self.imageScrollView setContentOffset:CGPointMake(self.imageScrollView.frame.size.width, 0)];
[self.timer fire];
}
- (void)stopPlaying
{
[self.timer invalidate];
self.timer = nil;
}
性能优化
考虑定时器(及其所触发的动画)带来的性能消耗,在控件被移除或隐藏时,应该invalidate定时器
- (void)removeFromSuperview
{
[super removeFromSuperview];
[self stopPlaying];
}
- (void)setHidden:(BOOL)hidden
{
[super setHidden:hidden];
if (hidden) {
[self stopPlaying];
} else {
[self startPlaying];
}
}
- (void)dealloc
{
if (!_timer) {
[_timer invalidate];
}
}
鲁棒性
其它的文章里应该给出了使用三个imageView实现功能做到内存方面的性能优化的问题。这里因为只是0.1版本,很多需求点都还没有实现,所以暂时不考虑内存方面的优化。后续版本应该会采用合适的方案优化内存。如果你想提前弄清楚如何实现内存上的优化,你可以访问其它作者的博客,譬如如何快速封装一个轮播广告,让代码更优雅!
代码和Demo已上传至Github,欢迎大家使用。