美文网首页
swift CollectioinView 居中放大 无限循环

swift CollectioinView 居中放大 无限循环

作者: 呦释原点 | 来源:发表于2021-04-09 15:52 被阅读0次

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

    自白
    以前弄过无限循环滚动的,好长时间没弄了,想从网上直接找一个来用, 找到的确实把数据条目重复发大做到的,cell数据多的时候会在内存中占着一大块数据,而且我是在首页用,内存根本释放不掉,感觉不好。 还是花时间实现了自己以前的做法。(一直没管理自己的代码库, 管理自己的代码库是多么重要)

    无限滚动原理
    假设有 a、b、c 三条数据,在collectionview中的数据是 c、a、b、c、a 共5条数据,当向左滚动,滚动到第4条数据c,滚动结束后,无动画滚动到第1条数据c,就可以继续向左滚动了;当向右滚动,滚动到第2条数据a,滚动结束后,无动画滚动到第5条数据a,就可以继续向右滚动了

    本来代码也是基于前辈的这个XLCardSwitch改的, 修改后的代码贴出来,以后用😁
    懒到一定程度了

    
    import UIKit
    
    //滚动切换回调方法
    typealias XLCardScollIndexChangeBlock = (Int) -> Void
    
    //布局类
    class XLCardSwitchFlowLayout: UICollectionViewFlowLayout {
        //卡片和父视图宽度比例
        let cardWidthScale: CGFloat = 1.0 // 0.7
        //卡片和父视图高度比例
        let cardHeightScale: CGFloat = 1.0 // 0.8
        //滚动到中间的调方法
        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)
            //复制布局,以下操作,在复制布局中处理
            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
            
            //最大移动距离,计算范围是移动出屏幕前的距离
            let maxApart: CGFloat  = ((self.collectionView?.bounds.size.width)! + self.itemWidth())/2.0
            
            //刷新cell缩放   现在不缩放了 只判断是否滚动到中间
            for attributes: UICollectionViewLayoutAttributes in attributesArr {
                //获取cell中心和屏幕中心的距离
                let apart: CGFloat = abs(attributes.center.x - centerX)
    // 放到的注释了, 不用了, 只用居中的效果
    //            //移动进度 -1~0~1
    //            let progress: CGFloat = apart/maxApart
    //            //在屏幕外的cell不处理
    //            if (abs(progress) > 1) {continue}
    //            //根据余弦函数,弧度在 -π/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
        }
        
        //设置左右缩进
        func insetX() -> CGFloat {
            let insetX: CGFloat = ((self.collectionView?.bounds.size.width)! - self.itemWidth())/2.0
            return insetX
        }
        
        //上下缩进
        func insetY() -> CGFloat {
            let insetY: CGFloat = ((self.collectionView?.bounds.size.height)! - self.itemHeight())/2.0
            return insetY
        }
        
        //是否实时刷新布局
        override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true
        }
    }
    
    //代理
    @objc protocol XLCardSwitchDelegate: NSObjectProtocol {
        //滑动切换到新的位置回调
        @objc optional func cardSwitchDidScrollToIndex(index: Int) -> ()
        //手动点击了
        @objc optional func cardSwitchDidSelectedAtIndex(index: Int) -> ()
    }
    
    //数据源
    @objc protocol XLCardSwitchDataSource: NSObjectProtocol {
        //卡片的个数
        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)
            
            //添加回调方法
            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:自动滚动
        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:自动布局
        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) {
            //执行代理方法
            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
            }
            //最小滚动距离
            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)
        }
        
        //滚动到中间
        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)
        }
        
        //手指拖动开始
        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
            if isAutoScroll {
                removeTimer()
            }
            if (!self.pagingEnabled) { return }
            _dragStartX = scrollView.contentOffset.x
            _dragAtIndex = self.selectedIndex
        }
        
        //手指拖动停止
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            if isAutoScroll {
                addTimer()
            }
            if (!self.pagingEnabled) { return }
            _dragEndX = scrollView.contentOffset.x
            //在主线程执行居中方法
            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:执行代理方法
        //回调滚动方法
        func delegateUpdateScrollIndex(index: Int) -> () {
            guard let delegate = self.delegate else { return }
            if (delegate.responds(to: #selector(delegate.cardSwitchDidScrollToIndex(index:)))) {
                delegate.cardSwitchDidScrollToIndex?(index: index)
            }
        }
        
        //回调点击方法
        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:数据源相关方法
        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
            }
        }
    }
    
    

    使用
    和人家的使用一样,只是多了是否无限滚动,是否自动滚动,滚动时间的方法

    //滚动卡片
        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)  // 这个是避免父视图滚动冲突的😝,和本章知识无关
            temp.isAutoScroll(autoScroll: true)
            //注册cell
            temp.register(nib: UINib(nibName: "ItemCell", bundle: nil), forCellWithReuseIdentifier: "ItemCell")
            return temp
        }()
    

    相关文章

      网友评论

          本文标题:swift CollectioinView 居中放大 无限循环

          本文链接:https://www.haomeiwen.com/subject/xcaikltx.html