64-Swift之流水布局(UICollectionView)
一 、 流水布局的介绍
在App 的开发中。流水布局是一种显示形式,它类似与生产线的传送带和商品的组合图显示的用户的视野内。流水布局又有人称画廊布局或画廊效果。
二、 本简书的样本例子如下:
4.gif三 、 如何编写流水布局呢?
我们实现流水布局的控件选择有好多。我们这里选择 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!)
}