為何要再寫圖片輪播控件
當(dāng)然圖片輪播控件這種東西真是被寫爛了,這里再寫一次的原因有三點(diǎn):一是目前已知的圖片輪播控件都在重復(fù)同樣的功能點(diǎn)(類似于簡(jiǎn)書客戶端的輪播),其實(shí)開發(fā)者對(duì)于圖片輪播控件還有很多其它的需求;二是目前我并沒有看到比較好的代碼,同時(shí)基于開發(fā)者對(duì)于輪播控件的復(fù)雜需求,代碼也會(huì)變得復(fù)雜起來(上千行的代碼實(shí)現(xiàn));三是我在學(xué)校里組織的興趣小組打算同時(shí)編寫iOS、Android、Web(基于JS)三個(gè)平臺(tái)的代碼,提供一致的特性和服務(wù),并將其開源。
version 0.1所實(shí)現(xiàn)的功能和提供的特性
輸入:用戶提供控件的frame,控件所要播放的images
輸出:一個(gè)圖片輪播組件,能夠定時(shí)切換圖片。
詳情請(qǐng)見需求文檔
著手實(shí)現(xiàn)version 0.1
實(shí)現(xiàn)原理請(qǐng)參照
私有屬性列表
@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:被展示的圖片所組成的數(shù)組
imageScrollView:可滾動(dòng)區(qū)域
imageViewArray:代碼實(shí)現(xiàn)中的imageView數(shù)組
pageControl:頁(yè)碼指示器
timer:計(jì)時(shí)器,用于定時(shí)切換圖片
為每一個(gè)私有屬性提供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;
}
實(shí)例初始化邏輯
- (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];
}
圖片切換邏輯
根據(jù)設(shè)計(jì)原理,定時(shí)器觸發(fā)時(shí),scrollView要做的動(dòng)作很簡(jiǎn)單,就是向左滑動(dòng)一頁(yè)?;瑒?dòng)后根據(jù)scrollView的偏移量來調(diào)整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;
}
}
讓圖片動(dòng)起來
定時(shí)器大法好,廢話不多說,直接上代碼
- (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;
}
性能優(yōu)化
考慮定時(shí)器(及其所觸發(fā)的動(dòng)畫)帶來的性能消耗,在控件被移除或隱藏時(shí),應(yīng)該invalidate定時(shí)器
- (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];
}
}
魯棒性
其它的文章里應(yīng)該給出了使用三個(gè)imageView實(shí)現(xiàn)功能做到內(nèi)存方面的性能優(yōu)化的問題。這里因?yàn)橹皇?.1版本,很多需求點(diǎn)都還沒有實(shí)現(xiàn),所以暫時(shí)不考慮內(nèi)存方面的優(yōu)化。后續(xù)版本應(yīng)該會(huì)采用合適的方案優(yōu)化內(nèi)存。如果你想提前弄清楚如何實(shí)現(xiàn)內(nèi)存上的優(yōu)化,你可以訪問其它作者的博客,譬如如何快速封裝一個(gè)輪播廣告,讓代碼更優(yōu)雅!
代碼和Demo已上傳至Github,歡迎大家使用。