
一.問(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ì)算方式.