swift CollectioinView 居中放大 無限循環(huán)滾動(dòng)

居中放大 參考:https://blog.csdn.net/u013282507/article/details/54136812

自白
以前弄過無限循環(huán)滾動(dòng)的捆姜,好長(zhǎng)時(shí)間沒弄了性雄,想從網(wǎng)上直接找一個(gè)來用, 找到的確實(shí)把數(shù)據(jù)條目重復(fù)發(fā)大做到的懂衩,cell數(shù)據(jù)多的時(shí)候會(huì)在內(nèi)存中占著一大塊數(shù)據(jù)华坦,而且我是在首頁用愿吹,內(nèi)存根本釋放不掉,感覺不好惜姐。 還是花時(shí)間實(shí)現(xiàn)了自己以前的做法犁跪。(一直沒管理自己的代碼庫, 管理自己的代碼庫是多么重要)

無限滾動(dòng)原理
假設(shè)有 a歹袁、b坷衍、c 三條數(shù)據(jù),在collectionview中的數(shù)據(jù)是 c条舔、a枫耳、b、c孟抗、a 共5條數(shù)據(jù)迁杨,當(dāng)向左滾動(dòng),滾動(dòng)到第4條數(shù)據(jù)c凄硼,滾動(dòng)結(jié)束后铅协,無動(dòng)畫滾動(dòng)到第1條數(shù)據(jù)c,就可以繼續(xù)向左滾動(dòng)了摊沉;當(dāng)向右滾動(dòng)狐史,滾動(dòng)到第2條數(shù)據(jù)a,滾動(dòng)結(jié)束后说墨,無動(dòng)畫滾動(dòng)到第5條數(shù)據(jù)a骏全,就可以繼續(xù)向右滾動(dòng)了

本來代碼也是基于前輩的這個(gè)XLCardSwitch改的, 修改后的代碼貼出來,以后用??
懶到一定程度了


import UIKit

//滾動(dòng)切換回調(diào)方法
typealias XLCardScollIndexChangeBlock = (Int) -> Void

//布局類
class XLCardSwitchFlowLayout: UICollectionViewFlowLayout {
    //卡片和父視圖寬度比例
    let cardWidthScale: CGFloat = 1.0 // 0.7
    //卡片和父視圖高度比例
    let cardHeightScale: CGFloat = 1.0 // 0.8
    //滾動(dòng)到中間的調(diào)方法
    var indexChangeBlock: XLCardScollIndexChangeBlock?
    
    override func prepare() {
        self.scrollDirection = UICollectionView.ScrollDirection.horizontal
        self.sectionInset = UIEdgeInsets(top: self.insetY(), left: self.insetX(), bottom: self.insetY(), right: self.insetX())
        self.itemSize = CGSize(width: self.itemWidth(), height: self.itemHeight())
        self.minimumLineSpacing = 5
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        //獲取cell的布局
        let originalAttributesArr = super.layoutAttributesForElements(in: rect)
        //復(fù)制布局,以下操作尼斧,在復(fù)制布局中處理
        var attributesArr: Array<UICollectionViewLayoutAttributes> = Array.init()
        for attr: UICollectionViewLayoutAttributes in originalAttributesArr! {
            attributesArr.append(attr.copy() as! UICollectionViewLayoutAttributes)
        }
        
        //屏幕中線
        let centerX: CGFloat =  (self.collectionView?.contentOffset.x)! + (self.collectionView?.bounds.size.width)!/2.0
        
        //最大移動(dòng)距離姜贡,計(jì)算范圍是移動(dòng)出屏幕前的距離
        let maxApart: CGFloat  = ((self.collectionView?.bounds.size.width)! + self.itemWidth())/2.0
        
        //刷新cell縮放   現(xiàn)在不縮放了 只判斷是否滾動(dòng)到中間
        for attributes: UICollectionViewLayoutAttributes in attributesArr {
            //獲取cell中心和屏幕中心的距離
            let apart: CGFloat = abs(attributes.center.x - centerX)
// 放到的注釋了, 不用了棺棵, 只用居中的效果
//            //移動(dòng)進(jìn)度 -1~0~1
//            let progress: CGFloat = apart/maxApart
//            //在屏幕外的cell不處理
//            if (abs(progress) > 1) {continue}
//            //根據(jù)余弦函數(shù)鲁豪,弧度在 -π/4 到 π/4,即 scale在 √2/2~1~√2/2 間變化
//            let scale: CGFloat = abs(cos(progress * CGFloat(Double.pi/4)))
//            //縮放大小
//            attributes.transform = CGAffineTransform.init(scaleX: scale, y: scale)
            //更新中間位
            if (apart <= self.itemWidth()/2.0) {
                self.indexChangeBlock?(attributes.indexPath.row)
            }
        }
        return attributesArr
    }
    
    //MARK -
    //MARK 配置方法
    //卡片寬度
    func itemWidth() -> CGFloat {
//        return (self.collectionView?.bounds.size.width)! * cardWidthScale
        return (self.collectionView?.bounds.size.width)! - 50
    }
    
    //卡片高度
    func itemHeight() -> CGFloat {
        return (self.collectionView?.bounds.size.height)! * cardHeightScale
    }
    
    //設(shè)置左右縮進(jìn)
    func insetX() -> CGFloat {
        let insetX: CGFloat = ((self.collectionView?.bounds.size.width)! - self.itemWidth())/2.0
        return insetX
    }
    
    //上下縮進(jìn)
    func insetY() -> CGFloat {
        let insetY: CGFloat = ((self.collectionView?.bounds.size.height)! - self.itemHeight())/2.0
        return insetY
    }
    
    //是否實(shí)時(shí)刷新布局
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

//代理
@objc protocol XLCardSwitchDelegate: NSObjectProtocol {
    //滑動(dòng)切換到新的位置回調(diào)
    @objc optional func cardSwitchDidScrollToIndex(index: Int) -> ()
    //手動(dòng)點(diǎn)擊了
    @objc optional func cardSwitchDidSelectedAtIndex(index: Int) -> ()
}

//數(shù)據(jù)源
@objc protocol XLCardSwitchDataSource: NSObjectProtocol {
    //卡片的個(gè)數(shù)
    func cardSwitchNumberOfCard() -> (Int)
    //卡片cell
    func cardSwitchCellForItemAtIndex(index: Int) -> (UICollectionViewCell)
}

//展示類
class XLCardSwitch: UIView ,UICollectionViewDelegate,UICollectionViewDataSource {
    //公有屬性
    weak var delegate: XLCardSwitchDelegate?
    weak var dataSource: XLCardSwitchDataSource?
    var selectedIndex: Int = 0
    var pagingEnabled: Bool = false
    //私有屬性
    private var _dragStartX: CGFloat = 0
    private var _dragEndX: CGFloat = 0
    private var _dragAtIndex: Int = 0
    
    private let flowlayout = XLCardSwitchFlowLayout()
    
    private var isInfinite = false
    private var isAutoScroll = false
    private var scrollTimeInterval: TimeInterval = 3.0
    private var timer: Timer?
    
    private lazy var _collectionView: UICollectionView = {
        let view = UICollectionView(frame: .zero, collectionViewLayout: flowlayout)
        view.delegate = self
        view.dataSource = self
        view.backgroundColor = UIColor.clear
        view.showsHorizontalScrollIndicator = false
        return view
    }()
    
   
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.buildUI()
    }
    
    func buildUI() {
        self.addSubview(_collectionView)
        
        //添加回調(diào)方法
        flowlayout.indexChangeBlock = { (index) -> () in
            if self.selectedIndex != index {
                self.selectedIndex = index
                // 3 0 1 2 3 0
                if self.isInfinite {
                    let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0  // 4
                    if index == 0 {
                        self.delegateUpdateScrollIndex(index: num - 1)
                    }else if index == num + 1 {
                        self.delegateUpdateScrollIndex(index: 0)
                    } else {
                        self.delegateUpdateScrollIndex(index: index-1)
                    }
                } else {
                    self.delegateUpdateScrollIndex(index: index)
                }
//                self.delegateUpdateScrollIndex(index: index)
            }
        }
        
    }
    
    func reloadData() {
        _collectionView.reloadData()
        if isInfinite {
            self.autoScrollFixToPosition(index: 1)
        }
        if isAutoScroll {
            removeTimer()
            addTimer()
        }
    }
    
    func setScrollViewTag(tag: Int) {
        _collectionView.tag = tag
    }
    
    
    //MARK:自動(dòng)滾動(dòng)
    func isAutoScroll(autoScroll: Bool) {
        isAutoScroll = autoScroll
        if isAutoScroll {
            isInfinite(infinite: true)
        }
    }
    func isInfinite(infinite: Bool) {
        isInfinite = infinite
    }
    func scrollTimeIntrval(timeInterval: TimeInterval) {
        scrollTimeInterval = timeInterval
    }
    func addTimer() {
        let timer = Timer.scheduledTimer(timeInterval: scrollTimeInterval, target: self, selector: #selector(nextpage), userInfo: nil, repeats: true)
        RunLoop.main.add(timer, forMode: .commonModes)
        self.timer = timer
    }
    func removeTimer() {
        self.timer?.invalidate()
        self.timer = nil
    }
    
    @objc private func nextpage() {
        switchToIndex(index: self.selectedIndex + 1)
    }
    
    //MARK:自動(dòng)布局
    override func layoutSubviews() {
        super.layoutSubviews()
        _collectionView.frame = self.bounds
    }

    //MARK:-
    //MARK:CollectionView方法
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if let num = self.dataSource?.cardSwitchNumberOfCard(), num > 0 {
            if num == 1 {
                isInfinite = false
                isAutoScroll = false
                return num
            }
            if isInfinite {
                return  num + 2    // 3 0 1 2 3 0 布局cell順序
            } else {
                return num
            }
        } else {
            return 0
        }
//        return (self.dataSource?.cardSwitchNumberOfCard()) ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if isInfinite {
            let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0
            if indexPath.row == 0 {
                return (self.dataSource?.cardSwitchCellForItemAtIndex(index: num - 1))!
            }else if indexPath.row == num + 1 {
                return (self.dataSource?.cardSwitchCellForItemAtIndex(index: 0))!
            } else {
                return (self.dataSource?.cardSwitchCellForItemAtIndex(index: indexPath.row - 1))!
            }
        } else {
            return (self.dataSource?.cardSwitchCellForItemAtIndex(index: indexPath.row))!
        }
//        return (self.dataSource?.cardSwitchCellForItemAtIndex(index: indexPath.row))!
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        //執(zhí)行代理方法
        selectedIndex = indexPath.row
        self.scrollToCenterAnimated(animated: true)
        if isInfinite {
            let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0
            if indexPath.row == 0 {
                self.delegateSelectedAtIndex(index: num-1)
            }else if indexPath.row == num + 1 {
                self.delegateSelectedAtIndex(index: 0)
            } else {
                self.delegateSelectedAtIndex(index: indexPath.row - 1)
            }
        } else {
            self.delegateSelectedAtIndex(index: indexPath.row)
        }
//        self.delegateSelectedAtIndex(index: indexPath.row)
    }
    
    //MARK:-
    //MARK:ScrollViewDelegate
    @objc func fixCellToCenter() -> () {
        if self.selectedIndex != _dragAtIndex {
            self.scrollToCenterAnimated(animated: true)
            return
        }
        //最小滾動(dòng)距離
        let dragMiniDistance: CGFloat = self.bounds.size.width/20.0
        if _dragStartX - _dragEndX >= dragMiniDistance {
            self.selectedIndex -= 1//向右
        }else if _dragEndX - _dragStartX >= dragMiniDistance {
            self.selectedIndex += 1 //向左
        }
        
        let maxIndex: Int  = (_collectionView.numberOfItems(inSection: 0)) - 1
        self.selectedIndex = max(self.selectedIndex, 0)
        self.selectedIndex = min(self.selectedIndex, maxIndex)
        self.scrollToCenterAnimated(animated: true)
    }
    
    //滾動(dòng)到中間
    func scrollToCenterAnimated(animated: Bool) -> () {
//        _collectionView.scrollToItem(at: IndexPath.init(row:self.selectedIndex, section: 0), at: UICollectionView.ScrollPosition.centeredHorizontally, animated: true)
        _collectionView.scrollToItem(at: IndexPath.init(row: self.selectedIndex, section: 0), at: UICollectionViewScrollPosition.centeredHorizontally, animated: animated)
    }
    
    //手指拖動(dòng)開始
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        if isAutoScroll {
            removeTimer()
        }
        if (!self.pagingEnabled) { return }
        _dragStartX = scrollView.contentOffset.x
        _dragAtIndex = self.selectedIndex
    }
    
    //手指拖動(dòng)停止
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if isAutoScroll {
            addTimer()
        }
        if (!self.pagingEnabled) { return }
        _dragEndX = scrollView.contentOffset.x
        //在主線程執(zhí)行居中方法
        DispatchQueue.main.async {
            self.fixCellToCenter()
        }
    }
    
    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        // 3 0 1 2 3 0
        if self.isInfinite {
            let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0  // 4
            let index = self.selectedIndex
            if index == 0 {
                self.autoScrollFixToPosition(index: num)
            }else if index == num + 1 {
                self.autoScrollFixToPosition(index: 1)
            } else {
            }
        }
    }
    
    //MARK:-
    //MARK:執(zhí)行代理方法
    //回調(diào)滾動(dòng)方法
    func delegateUpdateScrollIndex(index: Int) -> () {
        guard let delegate = self.delegate else { return }
        if (delegate.responds(to: #selector(delegate.cardSwitchDidScrollToIndex(index:)))) {
            delegate.cardSwitchDidScrollToIndex?(index: index)
        }
    }
    
    //回調(diào)點(diǎn)擊方法
    func delegateSelectedAtIndex(index: Int) -> () {
        guard let delegate = self.delegate else { return }
        if (delegate.responds(to: #selector(delegate.cardSwitchDidSelectedAtIndex(index:)))) {
            delegate.cardSwitchDidSelectedAtIndex?(index: index)
        }
    }
    
    //MARK:-
    //MARK:切換位置方法
    func switchToIndex(index: Int) -> () {
        DispatchQueue.main.async {
            self.selectedIndex = index
            self.scrollToCenterAnimated(animated: true)
        }
    }
    
    func autoScrollFixToPosition(index: Int) -> () {
//        DispatchQueue.main.asyncAfter(deadline: .now()+1) {
//            self.selectedIndex = index
//            self.scrollToCenterAnimated(animated: false)
//        }
        DispatchQueue.main.async {
            self.selectedIndex = index
            self.scrollToCenterAnimated(animated: false)
        }
    }
    
    //向前切換
    func switchPrevious() -> () {
        guard let index = currentIndex() else { return }
        var targetIndex = index - 1
        if !isInfinite {
            targetIndex = max(0, targetIndex)
        }
        self.switchToIndex(index: targetIndex)
    }
    
    //向后切換
    func switchNext() -> () {
        guard let index = currentIndex() else { return }
        var targetIndex = index + 1
        if !isInfinite {
            let maxIndex = (self.dataSource?.cardSwitchNumberOfCard())! - 1
            targetIndex = min(maxIndex, targetIndex)
        }
//        var maxIndex = (self.dataSource?.cardSwitchNumberOfCard())! - 1
//        targetIndex = min(maxIndex, targetIndex)
        
        self.switchToIndex(index: targetIndex)
    }
    
    func currentIndex() -> Int? {
        let x = _collectionView.contentOffset.x + _collectionView.bounds.width/2
        let index = _collectionView.indexPathForItem(at: CGPoint(x: x, y: _collectionView.bounds.height/2))?.item
        if isInfinite {
            let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0
            if index == 0 {
                return num - 1
            }else if index == num + 1 {
                return 0
            } else {
                return ((index ?? 1) - 1)
            }
        } else {
            return index
        }
    }
    
    //MARK:-
    //MARK:數(shù)據(jù)源相關(guān)方法
    open func register(cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String) {
        _collectionView.register(cellClass, forCellWithReuseIdentifier: identifier)
    }
    
    open func register(nib: UINib?, forCellWithReuseIdentifier identifier: String) {
        _collectionView.register(nib, forCellWithReuseIdentifier: identifier)
    }
    
    open func dequeueReusableCell(withReuseIdentifier identifier: String, for index: Int) -> UICollectionViewCell {
        return _collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: IndexPath(row: index, section: 0))
    }
    
    deinit {
        if self.timer != nil {
            self.timer?.invalidate()
            self.timer = nil
        }
    }
}

使用
和人家的使用一樣潘悼,只是多了是否無限滾動(dòng),是否自動(dòng)滾動(dòng)爬橡,滾動(dòng)時(shí)間的方法

//滾動(dòng)卡片
    lazy var cardSwitch: XLCardSwitch = {
        let temp = XLCardSwitch.init()
        temp.frame = CGRect(x: 0, y: 55, width: ScreenWidth, height: 139)
        temp.pagingEnabled = true
        temp.dataSource = self
        temp.delegate = self
        temp.setScrollViewTag(tag: 9090)  // 這個(gè)是避免父視圖滾動(dòng)沖突的??,和本章知識(shí)無關(guān)
        temp.isAutoScroll(autoScroll: true)
        //注冊(cè)cell
        temp.register(nib: UINib(nibName: "ItemCell", bundle: nil), forCellWithReuseIdentifier: "ItemCell")
        return temp
    }()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棒动,一起剝皮案震驚了整個(gè)濱河市糙申,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌船惨,老刑警劉巖柜裸,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粱锐,居然都是意外死亡疙挺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門怜浅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铐然,“玉大人,你說我怎么就攤上這事恶座〔笫睿” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵跨琳,是天一觀的道長(zhǎng)自点。 經(jīng)常有香客問我,道長(zhǎng)脉让,這世上最難降的妖魔是什么桂敛? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮溅潜,結(jié)果婚禮上术唬,老公的妹妹穿的比我還像新娘。我一直安慰自己伟恶,他們只是感情好碴开,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著博秫,像睡著了一般潦牛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挡育,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天巴碗,我揣著相機(jī)與錄音,去河邊找鬼即寒。 笑死橡淆,一個(gè)胖子當(dāng)著我的面吹牛召噩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逸爵,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼具滴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了师倔?” 一聲冷哼從身側(cè)響起构韵,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趋艘,沒想到半個(gè)月后疲恢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓷胧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年显拳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搓萧。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杂数,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矛绘,到底是詐尸還是另有隱情耍休,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布货矮,位于F島的核電站羊精,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏囚玫。R本人自食惡果不足惜喧锦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抓督。 院中可真熱鬧燃少,春花似錦、人聲如沸铃在。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽定铜。三九已至阳液,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間揣炕,已是汗流浹背帘皿。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留畸陡,地道東北人鹰溜。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓虽填,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親曹动。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斋日,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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