collectionView實現(xiàn)無限循環(huán)滾動卡片

github源碼地址

效果展示
show.gif

前言

去年因為項目中有個切換學(xué)校的功能,要求以卡片浮動效果展示,并且能夠無限循環(huán)滾動。

之前找了個demo它是通過自定義view動畫實現(xiàn)的,卡片數(shù)量多的時候會比較卡頓,所以研究了一下,自己造了一個,現(xiàn)在把實現(xiàn)的思路分享一下~

好,開始正題~~~
技術(shù)調(diào)研

在實現(xiàn)之前,首先我想到的是如何實現(xiàn)無限循環(huán)的問題,以及多數(shù)量時如何復(fù)用的問題。關(guān)于view的無限循環(huán)滾動和復(fù)用問題,最經(jīng)典的就是輪播圖了,現(xiàn)在比較常用的兩種方法就是UIScrollView或者UICollectionView實現(xiàn)的。

然后我決定選用UICollectionView,因為我們只需要給它提供數(shù)據(jù)源驅(qū)動,它自己就可以復(fù)用,實現(xiàn)應(yīng)該比較簡單。并且UICollectionViewlayout的靈活性非常強,完全可以自定義出很多酷炫的效果。

橫向滾動

設(shè)置滾動方向為水平,禁用分頁屬性(默認(rèn)就是false),這樣就可以橫向流暢滑動了

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
collectionView.isPagingEnabled = false // default NO

無限循環(huán)

定義數(shù)組imageArr來存儲圖片作為數(shù)據(jù)源,在每次滾動結(jié)束后,都重新定位到第一張。

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
   collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at:.centeredHorizontally, animated: false)
}

但是每次重定位的時候都會出現(xiàn)明顯的突兀效果,所以我們可以把這個數(shù)組中的圖片復(fù)制100份、200份、1000份。。。這樣就有足夠多的數(shù)據(jù)來供用戶滑動期間來展示了。

但是直接存儲圖片的話,這個數(shù)組肯定會占用很大的內(nèi)存,所以我們開辟一個新的數(shù)組indexArr,來存儲imageArr中圖片的下標(biāo),因為只是存儲的是int,所以占用的內(nèi)存是非常小的。

// 初始化數(shù)據(jù)源
imageArr = ["num_1", "num_2", "num_3", "num_4", "num_5"]
for _ in 0 ..< 100 {
    for j in 0 ..< imageArr.count {
        indexArr.append(j)
    }
}

比如imageArr中是["pic1", "pic2", "pic3"],那么indexArr中就是[0,1,2,0,1,2,0,1,2...],這樣就可以把數(shù)據(jù)源設(shè)置為indexArr。

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return indexArr.count
}

代碼初始時我們把collectionView定位到中間那組(第50組,假設(shè)共設(shè)置了100組),每次滾動結(jié)束后也再次定位到中間組的第N張(N:上次滑動結(jié)束時的那張)。

// 定位到 第50組(中間那組)
override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.scrollToItem(at: IndexPath(item: 50 * imageArr.count, section: 0) , at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
}

每次動畫停止時,也重新定位到 第50組(中間那組) 模型

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    collectionView.scrollToItem(at: IndexPath(item: 50 * imageArr.count, section: 0) , at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
}

每張卡片(cell)的數(shù)據(jù)填充

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(CyclicCardCell.self), for: indexPath) as! CyclicCardCell
    let index = indexArr[indexPath.row]
    cell.index = index
    cell.cardImgView.image = UIImage(named: imageArr[index])
    cell.cardNameLabel.text = "奔跑吧,小蝸牛~"
    return cell
}

滾動動畫的平滑過渡

在每次重定位的時候,雖然設(shè)置的是回到中間組的對應(yīng)下標(biāo)的那個cell,并且animated也是設(shè)置的false,但是依然可以看出動畫有些不連貫、突兀,顯得不流暢自然。

這就需要我們自定義UICollectionViewFlowLayout,來重寫targetContentOffset方法,通過遍歷目標(biāo)區(qū)域中的cell,來計算出距離中心點最近的cell,把它調(diào)整到中間,實現(xiàn)平緩流暢的滑動結(jié)束的效果。

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        
        let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width:  self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height)
        // 目標(biāo)區(qū)域中包含的cell
        let attriArray = super.layoutAttributesForElements(in: targetRect)! as [UICollectionViewLayoutAttributes]
        // collectionView落在屏幕中點的x坐標(biāo)
        let horizontalCenterX = proposedContentOffset.x + (self.collectionView!.bounds.width / 2.0)
        var offsetAdjustment = CGFloat(MAXFLOAT)
        for layoutAttributes in attriArray {
            let itemHorizontalCenterX = layoutAttributes.center.x
            // 找出離中心點最近的
            if(abs(itemHorizontalCenterX-horizontalCenterX) < abs(offsetAdjustment)){
                offsetAdjustment = itemHorizontalCenterX-horizontalCenterX
            }
        }
        
        //返回collectionView最終停留的位置
        return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
    }

卡片的縮放動畫

在自定義的UICollectionViewFlowLayout中重寫layoutAttributesForElements方法,通過該方法,可以獲取到可視范圍內(nèi)的cell。

然后在cell的滑動過程中,通過cell偏移的距離來進行尺寸的縮放系數(shù)的設(shè)置,處于最中心的系數(shù)為1,則為原本的大小,隨著中心距離的偏移,系數(shù)會逐漸變小為0.98,0.95,0.8...

因此卡片也會隨之變小,從而達到在滾動過程中,滾動至中間的卡片最大,旁邊的變小的效果。

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        let array = super.layoutAttributesForElements(in: rect)
        var visibleRect = CGRect()
        visibleRect.origin = self.collectionView!.contentOffset
        visibleRect.size = self.collectionView!.bounds.size
        
        for attributes in array!{
            let distance = visibleRect.midX - attributes.center.x
            let normalizedDistance = abs(distance/ActiveDistance)
            let zoom = 1 - ScaleFactor * normalizedDistance
            attributes.transform3D = CATransform3DMakeScale(1.0, zoom, 1.0)
            attributes.zIndex = 1
//            let alpha = 1 - normalizedDistance
//            attributes.alpha = alpha
        }
        return array
    }

OK,至此主要的核心實現(xiàn)就完成了,可能有些地方思路表述的可能不是太清楚,大家可以去github上下載源碼看下,有不對的地方,歡迎指正~

9月28號更新:

  • 因為這個文章是第一次寫的,所以之前表述的不大好,今天重新修改了一下,希望能對大家有所幫助??

  • 剛寫了一個類似的重疊卡片滾動的動畫,有興趣的可以看下:

重疊卡片滾動動畫
ScrollCard.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,200評論 4 61
  • 雨,滴嗒滴嗒地拍打著玻璃,像是在彈奏一首悅耳動聽的小曲,撥動著我的心弦。抿了一口手上溫?zé)岬目Х取?吹綁Ρ谏系奈遥?..
    今在否閱讀 217評論 0 0
  • 又好多天沒有更新每日一畫,不是我沒有堅持每日一畫,而是忙得沒有時間每日一寫。 文字這個東西,它沒有思維跳躍得那么快...
    嘰哩咕嚕AMY貓閱讀 355評論 4 4
  • 濃香一脈勝春華, 晶瑩莼桂香萬家. 湖心偶有扁舟至, 迤儷情懷最嗟呀. 吾嘆西子風(fēng)情雋, 四時晴雨不相厭. 來年若...
    君子闕閱讀 249評論 1 1
  • 1 貨幣政策:貼現(xiàn)率的調(diào)整、銀行法定儲備金率的調(diào)整、國債。 2 財政政策:通過(財政支出)與(稅收政策)的變動來影...
    無邊小豬閱讀 376評論 0 1