一 、 流水布局的介紹
在App 的開發中。流水布局是一種顯示形式,它類似與生產線的傳送帶和商品的組合圖顯示的用戶的視野內。流水布局又有人稱畫廊布局或畫廊效果。
二、 本簡書的樣本例子如下:
三 、 如何編寫流水布局呢?
我們實現流水布局的控件選擇有好多。我們這里選擇 UICollectionView 為實現流水布局的底層組件。要使用 UICollectionView 來實現流水布局,我們就要重新定義 UICollectionView 的布局文件。我們要繼承 UICollectionViewFlowLayout ,并且要重寫 UICollectionViewFlowLayout 類的幾個方法。如下:
func prepare() ===》 : 重新初始化Cell的布局。每次刷新將重新調用該方法。在重寫的時候,要首先調用該方法的父類的該方法。
func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) ===》 : 當CollectionView的顯示范圍發生變化的時候,是否需要重新布局。如果重新布局就會調用 1. func prepare() 2. override func layoutAttributesForElements(in rect: CGRect) 兩個方法.
func layoutAttributesForElements(in rect: CGRect) ===》 : 返回設置好的布局數組。
func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint ===》 保持Cell每次滑動后停止在UICollectionView的中間。
四、 流水布局重新定義和一些方法的重新
1、 override func prepare() 的重寫
// MARK: 布局初始化
override func prepare() {
super.prepare()
// 設置Cell的位置
let marginX = ( (self.collectionView?.frame.size.width)! - self.itemSize.width ) * 0.5
self.collectionView?.contentInset = UIEdgeInsets.init(top: 0, left: marginX, bottom: 0, right: marginX)
}
2 、 func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) 的方法的重寫
// MARK: 當CollectionView的顯示范圍發生變化的時候,是否需要重新布局。如果重新布局就會調用 1. func prepare() 2. override func layoutAttributesForElements(in rect: CGRect) 兩個方法
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
3、 func layoutAttributesForElements(in rect: CGRect) 的重寫
// MARK: 返回設置好的布局數組
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// 創建系統默認的 UICollectionViewLayoutAttributes
let DefaultAttributes = super.layoutAttributesForElements(in: rect)
// 計算Item在UICollectionView上的 X 軸的大小
let CollectionViewCenterX = (self.collectionView?.contentOffset.x)! + (self.collectionView?.frame.width)! * 0.5
for item in DefaultAttributes! {
// 計算Cell的中心點距離CollectionView的中心點的距離(如果Cell在中間,我們看到的就是不縮放的效果)
let Delta = abs(item.center.x - CollectionViewCenterX)
// 計算Cell 的縮放值
let Scal = 1.0 - Delta / (self.collectionView?.frame.width)!
// 設置Cell 滾動時的縮放比例
item.transform = CGAffineTransform(scaleX: Scal, y: Scal)
}
return DefaultAttributes
}
4、 func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint 方法的重寫
// MARK: 保持Cell每次滑動后停止在UICollectionView的中間
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// 計算展示Cell的矩形框大小
var rect = CGRect.init()
rect.origin.y = 0
rect.origin.x = proposedContentOffset.x
rect.size = (self.collectionView?.frame.size)!
var ContentOffset = proposedContentOffset.x
// 計算CollectionView 中點Cell的偏移的 X 坐標值
let CollectionViewCenterX = proposedContentOffset.x + (self.collectionView?.frame.width)! * 0.5
var minDelta = MAXFLOAT
for item:UICollectionViewLayoutAttributes in super.layoutAttributesForElements(in: rect)!{
if CGFloat(abs(minDelta)) > abs(item.center.x - CollectionViewCenterX) {
minDelta = Float(item.center.x - CollectionViewCenterX)
}
}
ContentOffset += CGFloat(minDelta)
return CGPoint.init(x: ContentOffset, y: proposedContentOffset.y)
}
五、 流水布局的使用
1、 創建UICollectionView對象
// MARK: 創建UICollectionView對象
func createCollectionView() -> Void{
// TODO: 創建布局對象
let FlowingwaterLayout = FlowingwaterCollectionViewLayout.init()
// TDOD: 設置方向
FlowingwaterLayout.scrollDirection = .horizontal
FlowingwaterLayout.itemSize = CGSize.init(width: 200, height: 380)
let FlowingwaterCollectionView = UICollectionView.init(frame: CGRect.init(x: 0, y: 100, width: view.frame.width, height: 400), collectionViewLayout: FlowingwaterLayout)
// TODO: 設置代理
FlowingwaterCollectionView.delegate = self
FlowingwaterCollectionView.dataSource = self
// TODO: 渲染到視圖
self.view.addSubview(FlowingwaterCollectionView)
// TODO: 注冊Cell
FlowingwaterCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "NetWork小賤")
}
2、 UICollectionViewDelegate / UICollectionViewDataSource / UICollectionViewFlowLayout 的代理實現
// MARK: UICollectionViewDelegate / UICollectionViewDataSource / UICollectionViewFlowLayout 的代理實現
// TODO: 返回Cell的個數
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
// TODO: 創建Cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 獲取Cell
let Cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NetWork小賤", for: indexPath)
Cell.contentView.backgroundColor = UIColor.white
for item in Cell.contentView.subviews {
item.removeFromSuperview()
}
// 圖像
let imageV = UIImageView.init(frame: CGRect.init(x: 0, y: 0, width: Cell.bounds.width, height: Cell.bounds.height - 60))
imageV.contentMode = .scaleToFill
imageV.image = UIImage.init(named:(dataArray![indexPath.row] as! NSDictionary)["image"] as! String)
Cell.contentView.addSubview(imageV)
// 標題
let titleL = UILabel.init(frame: CGRect.init(x: 5, y: imageV.frame.maxY + 5, width: Cell.bounds.width - 10, height: 30))
titleL.font = UIFont.systemFont(ofSize: 18)
titleL.text = ((dataArray![indexPath.row] as! NSDictionary)["title"] as! String)
Cell.contentView.addSubview(titleL)
// 售價
let priceL = UILabel.init(frame: CGRect.init(x: 5, y: titleL.frame.maxY - 5, width: Cell.bounds.width - 10, height: 30))
priceL.textColor = UIColor.red
priceL.font = UIFont.boldSystemFont(ofSize: 17)
priceL.text = String.init(format: "售價:¥ %@", (dataArray![indexPath.row] as! NSDictionary)["price"] as! String)
Cell.contentView.addSubview(priceL)
return Cell
}
// TODO: 選擇哪個Cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
3、 數據加載
// MARK: 獲取數據
func loadData() -> Void {
let path = Bundle.main.path(forResource: "data", ofType: "plist")
dataArray = NSArray.init(contentsOfFile: path!)
}