1. 程式人生 > >iOS 使用UICollectionView實現輪播圖

iOS 使用UICollectionView實現輪播圖

一般來說實現輪播圖的基礎控制元件有兩個,UIScrollView或UICollectionView,二者選一,我更願意用UICollectionView,至於原因,讀者們發揮想象力吧,我只是在看輪播圖的時候第一個就想到了UICollectionView,僅此而已。

既然第一個想到的是UICollectionView,那它對我來說應該就是最合適的。思考一下下面的問題,然後擼程式碼吧。

問題1
怎樣實現順滑的滾動,使得從最後一個右滑時很自然的顯示第一個以及從第一個左滑時很自然的顯示最後一個?

首先,我們來分析一下這個問題。
假設我們有n個item要輪播(n >= 1),之所以n不取0,是因為這樣沒什麼意義。
分情況討論:


1、n <= 1
這個情況最簡單嘛,始終只顯示一個item或者啥都不顯示,既不需要左滑也不需要右滑。

2、n > 1
– 由問題“第一個item[0]向右滑動顯示最後一個item[n-1]” 得出:item[0]之前有個item,它就是item[n-1]。
– 由問題“最後一個item[n-1]向左滑動顯示第一個item[0]”得出:item[n-1]後面有個item,它就是item[0]。
UI上的item的順序如下圖
這裡寫圖片描述

因此為了解決問題1,在設定items時我們得出瞭如下邏輯
if (items.count > 1) {
//在陣列的頭部插入最後一個item,在陣列的尾部加上第一個item
//使得新的陣列元素個數等於原陣列元素個數+2
}

demo程式碼如下:

- (void)setItems:(NSArray *)items
{
    if (items.count > 1) {
        NSMutableArray  * arr = [items mutableCopy];
        [arr addObject:[items firstObject]];
        [arr insertObject:[items lastObject] atIndex:0];
        _items = arr;
    } else {
        _items = [items copy];
    }
}

問題2
怎樣控制item切換間隔?

最容易想到的方式就是NSTimer啦,間隔timeSpace秒執行一次切換操作,滾動到下個item。
說起來容易,但是實現起來稍有麻煩,因為在解決問題1的時候咱們對items陣列進行了修改,但是不要怕,因為原理很簡單。下面分情況討論一下:

1、從items[0]右滑切到items[n-1],如下圖
這裡寫圖片描述
當滾動結束,item[n-1]完全顯示後,我們需要手動的修改collectionView的偏移量,顯示紅色框框的那個item[n-1]。
這個偏移量也很好計算width * ( _items.count - 2)

2、從items[n-1]左滑切到item[0],如下圖
這裡寫圖片描述
當滾動結束,item[0]完全顯示後,我們也需要手動的修改collectionView的偏移量,顯示紅色框框的那個item[0]。
這個偏移量更容易看出來,就是width自己了

第二個問題也搞定了,還有什麼問題呢?
到目前為止,搞定了這兩個問題之後,一個簡單的輪播圖已經完成了。

然而,作為一枚追求完美的猿,怎麼可以這樣草草了事呢?而且做的又不是國企的專案~

優化1

新增一個小點點的控制元件,用來表示當前顯示的是第幾個item。

這個就是UIpageControl啦。

timer方法執行的時候,很好計算下一個要顯示的哪個item,這種情況下設定currentPage並不難,所以在這裡就略過了。

當手指滑動切換item的時候,我們也要得到將要顯示的是哪個item,在此我要告訴你一個UIScrollView的代理方法

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset
{
    //targetContentOffset->x即為scrollView在x軸的目標偏移量
    //x/width就是目標索引了,但是要記得轉化成實際的索引。
}

通過這個代理方法我們能預先知道collectionView將要停在哪裡,這樣的話我們只需要那這個座標的x除以其寬度,就可以知道索引了。

詳細的處理過程在這裡就不說了,有興趣的話可以看程式碼。文章最後我會放上程式碼連結。

優化2

timer控制的自動滾動方法與手勢滾動的衝突

在這裡我學習了網易雲音樂的效果,實現方式如下:
當有手勢的時候就把timer關掉;
當滾動完成後再啟用timer;
為了讓當前這個item顯示時間達到timeSpace秒,使用了GCD的dispatch_after延遲timeSpace秒後fire timer。

此項優化會導致一個問題,如果連續滑動,會導致呼叫了好幾次dispatch_after,導致所做的延遲沒有意義。因此我使用了NSInvocationOperation來做相關控制,理由很簡單,因為我可以隨時cancel掉一個不想要的操作。

優化3

像網易雲音樂一樣,我不想使用者滑得很快,在一個item還未顯示完全就開始下一次的滑動

這個問題可以通過userInteractionEnabled這個屬性控制。

最後附上程式碼地址,大家可以拿去直接用,當然點選回撥程式碼中未實現,需要自行新增。喜歡的話記得star哦