上一篇介紹了UICollectionView的基本使用,這篇介紹下如何實(shí)現(xiàn)如Keep勛章的滑動(dòng)自動(dòng)縮放的交互效果,即Gallery畫廊效果哄陶,只需要利用layout即可輕松實(shí)現(xiàn)琳省。
我們先按上一篇?jiǎng)?chuàng)建UICollectionView以顯示數(shù)據(jù),代碼如下:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 100, height: 100)
let collectionView = UICollectionView(frame: CGRectMake(0, 100, view.bounds.width, 150), collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.register(UINib(nibName: "ItemCell", bundle: nil), forCellWithReuseIdentifier: "ItemCell")
view.addSubview(collectionView)
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 30
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ItemCell", for: indexPath) as! ItemCell
cell.imageView.image = UIImage(named: "\(indexPath.row)")
return cell
}
}
因?yàn)槲覀冞€是像上一篇一樣用的UICollectionViewFlowLayout嗤形,運(yùn)行后精偿,目前只是簡單的平鋪效果:
接下來我們要替換layout為我們自己創(chuàng)建的UICollectionViewLayout子類。因?yàn)榭傮w上我們還是網(wǎng)格布局赋兵,可以直接繼承自UICollectionViewFlowLayout笔咽,可節(jié)省大量代碼:
先上完整代碼,再解釋
import UIKit
class LineLayout: UICollectionViewFlowLayout {
let ActiveDistance = 80.0;
let ScaleFactor = 1.0;
var seletedIndex = 0
override init() {
super.init()
scrollDirection = .horizontal
minimumLineSpacing = 20
itemSize = CGSize(width: 110, height: 110)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 調(diào)整選中的cell居中對(duì)齊
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
//proposedContentOffset是沒有設(shè)置對(duì)齊時(shí)本應(yīng)該停下的位置
guard let collectionView = collectionView else {return proposedContentOffset}
//targetRect是collectionView當(dāng)前顯示在屏幕的區(qū)域霹期,array保存此區(qū)域內(nèi)的所有cell布局信息(UICollectionViewLayoutAttributes)
let collectionW = collectionView.bounds.width
let collectionH = collectionView.bounds.height
var offsetAdjustment = 10000.0
let horizontalCenter = proposedContentOffset.x + collectionW / 2
let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: collectionW, height: collectionH)
guard let array = super.layoutAttributesForElements(in: targetRect) else {return proposedContentOffset}
//對(duì)當(dāng)前屏幕中的UICollectionViewLayoutAttributes逐個(gè)與屏幕中心進(jìn)行比較叶组,找出最接近中心的一個(gè)
var itemHorizontalCenter = 0.0
for layoutAttributes in array {
itemHorizontalCenter = layoutAttributes.center.x
if (abs(itemHorizontalCenter - horizontalCenter) < abs(offsetAdjustment)) {
offsetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
//返回調(diào)整后的偏移位置
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y
)
}
/** 有效距離:當(dāng)item的中間x距離屏幕的中間x在ActiveDistance以內(nèi),開始放大, 其它情況都是縮小 */
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let array = super.layoutAttributesForElements(in: rect)
guard let collectionView = collectionView,let array = array else {return array}
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
// 遍歷布局屬性
for attribute in array {
//如果cell在屏幕上則進(jìn)行縮放處理
if CGRectIntersectsRect(attribute.frame, rect) {
// 距離中點(diǎn)的距離
let distance = CGRectGetMidX(visibleRect) - attribute.center.x
let normalizedDistance = distance / ActiveDistance
if (abs(distance) < ActiveDistance) {
let zoom = 1 + ScaleFactor * (1 - abs(normalizedDistance))
attribute.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0)
attribute.zIndex = 1
attribute.alpha = max(1 - normalizedDistance, 0.6)
seletedIndex = attribute.indexPath.row
}else {
attribute.alpha = 0.6
}
}
}
return array
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
true
}
}
1、init方法
在init方法中历造,我們?cè)O(shè)置了collectionView的滑動(dòng)方向甩十、cell間距、cell的大小
2吭产、實(shí)現(xiàn)滑動(dòng)后cell自動(dòng)居中
通過重寫如下方法:
func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
其中proposedContentOffset參數(shù)為滑動(dòng)結(jié)束后侣监,collectionView最終的偏移位置,返回值為我們期待的偏移位置臣淤。即我們只要通過本該偏移的位置橄霉,通過計(jì)算找到此時(shí)離屏幕中點(diǎn)最近的cell,重新計(jì)算偏移位置并返回邑蒋,就可以讓最靠近中點(diǎn)的cell完全居中了姓蜂。
3、中間的cell放大效果
通過重寫以下方法:
func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
參數(shù)rect為當(dāng)前需要展示的所有cell所在collectionview的區(qū)域寺董,我們只需要獲取到這區(qū)域內(nèi)的所有cell的布局信息覆糟,并做對(duì)應(yīng)的縮放、透明度的修改后遮咖,作為返回值返回即可滩字。
最后,將原來的UICollectionViewFlowLayout替換為LineLayout即可:
override func viewDidLoad() {
super.viewDidLoad()
let layout = LineLayout()
let collectionView = UICollectionView(frame: CGRectMake(0, 100, view.bounds.width, 150), collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.register(UINib(nibName: "ItemCell", bundle: nil), forCellWithReuseIdentifier: "ItemCell")
view.addSubview(collectionView)
}
運(yùn)行后效果如下: