1. 程式人生 > >iOS-無限迴圈輪播器(註釋詳細到沒有之一)

iOS-無限迴圈輪播器(註釋詳細到沒有之一)

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方法更新圖片以及分頁控制元件的狀態了,這裡就不再描述了,詳細的建議可以看程式碼完整參考理解,另外這些是個人理解,不足之處歡迎指正,感謝支援
  • 程式碼可以點選這裡檢視