collectionView非整屏滾動(dòng)(卡片式效果)

卡片效果

一.問(wèn)題說(shuō)明

在使用 collectionView 時(shí),遇到了下面的問(wèn)題.滾動(dòng)并非全屏的.如果是全屏則使用isPagingEnabled屬性就可以解決.基本上一些常見(jiàn)的輪播圖也可以這么處理.先在發(fā)現(xiàn)如果設(shè)置該屬性,滾動(dòng)的距離是屏幕的寬.
下面說(shuō)一下思路:

二.解決思路

2.1布局

首先先完成基本布局

// 繼承FlowLayout 可以減少一部分布局麻煩
class CredirCardLayout: UICollectionViewFlowLayout {
    // 用于存放每個(gè) item 的樣式
    fileprivate lazy var attrsArray: [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
    override init() {
        super.init()
        // 設(shè)置布局為水平滾動(dòng) 
        scrollDirection = .horizontal
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // 布局準(zhǔn)備工作,主要用于計(jì)算每個(gè) item 的位置
    override func prepare() {
        super.prepare()
        let itemCount = collectionView?.numberOfItems(inSection: 0)
        let itemW: CGFloat = ScreenW * 0.84
        let itemH: CGFloat = 250
        let margin: CGFloat = -30
        let left: CGFloat = (ScreenW - itemW - 2 * margin) / 2.0

        for index in 0..<itemCount! {
            let indexPath = IndexPath(item: index, section: 0)
            let atts = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            let x = (itemW + margin) * CGFloat(index) +  margin + left
            
            atts.frame = CGRect(x: x, y: 0, width: itemW, height: itemH)
            attrsArray.append(atts)
        }
    }
    
    // 為每個(gè) item 布局
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let size = collectionView?.frame.size
        let origin = collectionView?.contentOffset
        // 顯示區(qū)域
        let visiableRect = CGRect(x: (origin?.x)!, y: (origin?.y)!, width: (size?.width)!, height: (size?.height)!)
        
        let centerX = (collectionView?.contentOffset.x)! + (collectionView?.frame.width)! * 0.5
        
        for att in attrsArray {
            if visiableRect.intersects(att.frame) {
                // 獲取中心點(diǎn)
                let itemCenterX = att.center.x
                
                // 計(jì)算縮小的比例. fabsf是取絕對(duì)值的函數(shù)
                let scale = 1 - (fabsf(Float(itemCenterX - centerX))) / 1000.0
                                
                att.transform = CGAffineTransform(scaleX: CGFloat(scale), y: CGFloat(scale))
            }
        }
        
        return attrsArray
    }
    
    // ContentSize大小
    override var collectionViewContentSize: CGSize {
        return CGSize(width: (attrsArray.last?.frame.maxX)! + 30, height: 0)
    }
    
    // 當(dāng)邊界改變時(shí)調(diào)用此方法,可以頻繁計(jì)算 item 的大小 
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

到這里基本的 layout 布局就基本完成.

2.2當(dāng)滾動(dòng)停止時(shí),停留到中間
extension CredirCardLayout {
  // 滾動(dòng)將要停止時(shí)調(diào)用
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        
        let lastRect = CGRect(origin: proposedContentOffset, size: (collectionView?.frame.size)!)
        
        // 屏幕中心的位置
        let centerX = proposedContentOffset.x + (collectionView?.frame.width)! * 0.5
        
        // 取出范圍內(nèi)的屬性
        let array = layoutAttributesForElements(in: lastRect)
        var  adjustOffsetX: CGFloat = CGFloat(MAXFLOAT)
        
        for att in array! {
            if fabsf(Float(att.center.x - centerX)) < fabsf(Float(adjustOffsetX)) {
                adjustOffsetX = att.center.x - centerX
            }
        }
        return CGPoint(x: proposedContentOffset.x + adjustOffsetX, y: proposedContentOffset.y)
    }

}
2.3 設(shè)置滾動(dòng)頁(yè)

到這里你可能會(huì)發(fā)現(xiàn)當(dāng)滾動(dòng)時(shí),會(huì)滑動(dòng)很多頁(yè).這樣子肯定不行.
先說(shuō)一下思路,再看解決方案:
思路一: 通過(guò)判斷左劃,還是右劃.設(shè)置滾動(dòng)的 offset.x
這里我們會(huì)遇到幾個(gè) scrollerView 的代理方法

// 當(dāng)開(kāi)始拖動(dòng)時(shí),調(diào)用,此時(shí)記錄位置,便于后面進(jìn)行比較
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        offsetX = scrollView.contentOffset.x
}

    // 當(dāng)將要結(jié)束時(shí)調(diào)用, 此處可以判斷是左劃還是右劃    
 func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
      if offsetX > scrollView.contentOffset.x { // 右劃

      } else { // 左劃

       }   
}
    
    // 滑動(dòng)減速時(shí)調(diào)用
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
   if offsetX > scrollView.contentOffset.x { // 右劃 
           
   } else { // 左劃

   }
}
    
 // 滾動(dòng)時(shí)調(diào)用會(huì)頻繁調(diào)用
func scrollViewDidScroll(_ scrollView: UIScrollView) {
     
}

通過(guò)這幾個(gè)方法測(cè)試會(huì)發(fā)現(xiàn)問(wèn)題.當(dāng)手的滑動(dòng)的力度不同時(shí),調(diào)用的方法是不樣的.所以判斷起來(lái)極端的麻煩.

思路二: 如何處理手拖動(dòng)速度的問(wèn)題
由于上面已經(jīng)處理了當(dāng)滾動(dòng)停止時(shí)一定會(huì)停在 item 的中心,所以現(xiàn)在考慮的就是怎樣可以區(qū)分手劃的力度呢.通過(guò)測(cè)試觸發(fā)的手勢(shì)發(fā)現(xiàn)通過(guò)手勢(shì)的移動(dòng)是很難判斷.最后只能讓滾動(dòng)慢下來(lái).

最終解決方案:設(shè)置decelerationRate

 collecton.decelerationRate = 0.5

decelerationRate這個(gè)屬性的取值范圍是0-1;用于決定用戶(hù)舉起手指后減速率。通過(guò)設(shè)定滾動(dòng)的減速率,來(lái)限制 offset. 當(dāng)然這種方法有些取巧.暫時(shí)還沒(méi)有想到太好的計(jì)算方式.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容