Bg:
1)有一段時間沒有寫文章了,最近事兒比較多,今天有人在技術群里面問我使用UIScrollview實現無限循環輪播的思想(3個UIImageView實現),我當時給了他一篇博客,不過好像這位朋友看的不是很懂,所以我寫了一個小Demo打算寫這篇文章去講解下,幫助有需要的朋友們,所以我盡量把能寫的注釋都寫詳細了,把思想寫全面了,讓大家看一遍基本就明白了,里面關于如何好的封裝一個控件的細節這里就不實現了,以講解實現為主哦
2)另外這種循環利用的思想也是面試可能會問到的,說不定還是加分項哦
3)另外這個文章說的是3個UIImageView,其實2個imageview完全可以實現(點擊這里看2個imageview實現),實時判斷向左向右,然后改變imageview的frame的x/y即可,當然還可以完全使用collectionview去實現,這個也比較流行(點擊這里看collectionview實現)
- 先看下效果圖
kobe.gif
- 這個效果圖也沒什么特別,大家都看到過無數次了,包括這個無線輪播,大家想必也都了解過,所以這次我們不實現什么特別的效果,主要是通過這個小功能,給有需要的朋友講解下無線輪播思想
使用3個imageview實現無線輪播的大致原理
- 將3個imageview添加到scrollview上面,scrollview的
contensize
是3個imageview的寬度
,設置scrollview一開始初始的偏移量為一個imageview寬度
,因為里面有3個UIImageView,所以scrollview默認顯示的就是中間的那個imageview,并且關鍵就是讓屏幕顯示的始終就是中間的這個imagview - 使用3個
imageview
來回更換圖片,并在每一次更換圖片后立即再設置scrollview偏移量還為一個imagview的寬度,也就是讓scrollview滾動后再滾回原來默認的位置,這樣就可以達到始終顯示中間那個imageview的效果 - 看到過其他博客里面有這樣描述過這個原理
ps:例如要使用三個UIImageView循環顯示5張圖片
1)由于中間的imageview是顯示在屏幕上的,它需要在啟動時默認顯示第1張圖片,那么左邊的imagview
自然就需要顯示最后一張圖片,右邊的imagview自然要顯示第二張圖片了.所以一開始肯定默認放圖片5、
圖片1、圖片2,當前顯示中間的UIImageView,也就是圖片1
2)如果用戶手指向左滑動,那么就會顯示圖片2,當圖片2顯示完整后迅速重新設置左中右三個UIImageView
的內容為圖片1、圖片2、圖片3,然后馬上設置contentOffset再次為一開始默認的一個imageview寬度,
讓它滾回默認一開始的位置,以此來達到一直顯示的是中間的UIImageView的效果,此刻中間那個imagview
顯示的也就是圖片2
3)繼續向左滑動看到圖片3,當圖片3滾動完成迅速重新設置3個UIImageView的內容為圖片2、圖片3、圖片
4,然后通過設置contentOffset依然顯示中間的那個UIImageView,此刻也就是圖片3
5)當然,向右滑動原理完全一樣,如此操作就給用戶一種循環的錯覺,而且圖片多的話不占用過多內存
- 為此我做了一個動態圖,以此來動態描述下這個原理
scroll.gif
部分代碼實現:
- 大致原理就是上述那些,文字比較多,但是是核心思想,可以借助代碼再去理解一下,代碼里面注釋也是非常詳細的
- (void)creatUI
{
//初始化scrollview
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.scrollView.contentSize = CGSizeMake(view_WIDTH * 3, view_HEIGHT);
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.pagingEnabled = YES;
self.scrollView.bounces = NO;
//設置scrollview一開始的偏移量為一個寬度,因為里面有3個UIImageView,所以scrollview默認顯示的就是中間的那個imageview
self.scrollView.contentOffset = CGPointMake(view_WIDTH, 0);
self.scrollView.delegate = self;
[self addSubview:self.scrollView];
//初始化imageview
_imageViews = [NSMutableArray array];
//創建三個imageView作為循環復用的載體,圖片將循環加載在這三個imageView上面
for (NSInteger i = 0; i < 3; i++) {
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(view_WIDTH * i, 0, view_WIDTH,view_HEIGHT);
//(self.dataArray.count - 1 + i)%self.dataArray.count也可以達到讓一開始3個imageview分別顯示最后一張<-->第一張<-->第二張圖片,但是讓大家理解起來會有一定難度,所以采用下面最簡單的方法直接設置
//imageView.tag = (self.dataArray.count - 1 + i)%self.dataArray.count;
//3個imageview一開始需要的圖片分別對應圖片數組的圖片索引應該是imageview[0].index-->images.count-1,imageview[1].index-->0,imageview[2].index-->1
NSInteger index = 0;
if (i == 0) index = _imagesArray.count - 1;
if (i == 1) index = 0;
if (i == 2) index = 1;
//把index賦值給imageview的tag值,這樣方便在后面方法中通過imageview的tag值直接拿到index,我們就可以輕松從圖片數組獲取對應的圖片然后顯示到imageview上面,有媒介的作用
imageView.tag = index;
imageView.userInteractionEnabled = YES;
//這里給imageview添加了一個單擊手勢,通過block回調處理了imageview點擊的監聽事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageViewClicked:)];
[imageView addGestureRecognizer:tap];
//設置imageView上的image圖片,2個方法使用哪一個設置都可以,關于2個方法的選擇可以看下面詳細注釋
[self setImageWithImageView:imageView];
[self setImageView:imageView atIndex:index];
//將imageView加入數組中,方便隨后取用
[_imageViews addObject:imageView];
[self.scrollView addSubview:imageView];
}
//初始化pageControl,最后添加,這樣它會顯示在最前面,不會被遮擋
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.scrollView.frame) - 30, view_WIDTH, 30)];
self.pageControl.numberOfPages = _imagesArray.count;
self.pageControl.currentPage = 0;
[self addSubview:self.pageControl];
}
/*
這里給imageview設置圖片有2個選擇,第一可以使用這個方法,傳遞一個需要設置的imageview以及對應的index
好處:一眼就可以讓大家明白這個方法內部的實現功能,易與閱讀和理解
相對于下面那個方法的壞處:其實我們完全可以不傳遞index這個參數,我們完全可以把index賦值給imageview的tag,這樣我們只用傳遞一個imageview過來,就可以既拿到imageview,又可以通過imageview的tag拿到index
總結:2個方法都可以,看大家喜歡哪一種,哪一種順手好理解就使用哪一種
*/
- (void)setImageView:(UIImageView *)imageView atIndex:(NSInteger)index
{
//根據實時計算得出的index,從圖片數組里面取值,然后賦值給對應左中右3個imageview
UIImage *image = (UIImage *)_imagesArray[index];
imageView.image = image;
}
- (void)setImageWithImageView:(UIImageView *)imageView{
//根據imageView的tag值給imageView設置image
// UIImage *image = (UIImage *)self.dataArray[imageView.tag];
// imageView.image = image;
}
//定時器調用的方法
- (void)nextPage
{
//NSLog(@"定時器的%f",_scrollView.contentOffset.x);
//定時器方法都是相當于向左滑動,偏移量是增大的,原本偏移量是一倍的寬度,定時器方法執行一次,偏移量就要增大一個寬度,這樣也就是setContentOffset:CGPointMake(VIEW_WIDTH * 2, 0),相當于設置偏移量是2倍寬度
//執行了setContentOffset:方法,系統會自動調用scrollViewDidEndScrollingAnimation:方法,在這個方法里面再設置回偏移量等于一倍的寬度,同時更換各個imageview的圖片,那么還是相當于中間的那個imageview顯示在屏幕上
[self.scrollView setContentOffset:CGPointMake(view_WIDTH * 2, 0) animated:YES];
}
#pragma mark - 更新圖片和分頁控件的當前頁
- (void)updateImageViewsAndPageControl {
//先判斷出scrollview的操作行為是向左向右還是不動
//定義一個flag,目前是讓scrollview向左向右滑動的時候索引對應的+1或者-1
int flag = 0;
if (self.scrollView.contentOffset.x > view_WIDTH)
{//手指向左滑動
flag = 1;
}
else if (self.scrollView.contentOffset.x == 0)//原本偏移量是一個寬度,現在==0了,那么就是手指向右滑動了
{//手指向右滑動
flag = -1;
}
else
{//除了向左向右之外就是沒有移動,那么不需要任何操作,直接返回
return;
}
// NSInteger index = 0;
//修改imageViews中的imageView的tag值,從而修改imageView上顯示的image,pageControl的頁碼
for (UIImageView *imageView in _imageViews) {
/*
(1)當屏幕中間那個imageview顯示最后一張圖片時,右邊的ImageView,也即下一張圖片應該是顯示最開始的那一張圖片(第0張);
(2)當屏幕中間顯示最開始的那一張圖片(第0張)時,左邊的ImageView,也即上一張圖片應該是最后一張圖片。
*/
NSInteger index = imageView.tag + flag ;
if (index < 0) {
index = self.pageControl.numberOfPages - 1;
} else if (index >= self.pageControl.numberOfPages) {
index = 0;
}
imageView.tag = index;
//更新每一頁上的image
[self setImageWithImageView:imageView];
[self setImageView:imageView atIndex:index];
}
//更新pageControl顯示的頁碼,也就是中間那個imageview的tag值
self.pageControl.currentPage = [_imageViews[1] tag];
//使用無動畫的效果快速切換,也就是把scrollview的偏移量還設置成一個imageview的寬度
//這里是通過設置scrollview的偏移量讓其來回滑動,時刻更換imageview的圖片,每換一次,就立即讓scrollview以無動畫的方式再回到偏移量為一個imageview寬度的偏移量位置,即還是顯示的中間那個imageview,以此給用戶產生一種來回切換的錯覺,實質一直是在顯示中間那個imageview
self.scrollView.contentOffset = CGPointMake(view_WIDTH, 0);
}
- 其他的就是在scrollview的幾個代理方法里面要么開啟定時器,要么關閉定時器,要么就是調用
updateImageViewsAndPageControl
方法更新圖片以及分頁控件的狀態了,這里就不再描述了,詳細的建議可以看代碼完整參考理解,另外這些是個人理解,不足之處歡迎指正,感謝支持 - 代碼可以點擊這里查看