自定义UICollectionView布局

来源:互联网 发布:球头铣刀怎么编程 编辑:程序博客网 时间:2024/05/17 09:18

记录ray上学习自定义UICollectionView布局学习笔记

自定义UICollectionView布局

UICollectionViewFlowLayout

UICollectionViewFlowLayoutUICollectionView的默认的布局方式

UICollectionViewDelegateFlowLayout代理方法

1.func collectionView(_:layout:sizeForItemAt:) -> CGSizeitem的size
2.func collectionView(_:layout:insetForSectionAt:) -> UIEdgeInsetssection的inset,相当于是内容的margin
3.func collectionView(_:layout:minimumLineSpacingForSectionAt:) -> CGFloat行间距
4.func collectionView(_:layout:minimumInteritemSpacingForSectionAt:) -> CGFloatitem之间的距离

Carousel布局的实现

1.最开始的布局,就是一个FlowLayout,其效果如下:

这里写图片描述

2.根据item中心距离collection view中距离,调整item的alpha和scale,其计算的原理大致如下:

这里写图片描述

调整了alpha和scale后,其显示结果如下:

这里写图片描述

3.设置item之间的间距为负值,并设置布局的attribute的zIndex,使其看起来有层次,最中间的item在最上层

这里写图片描述

继承UICollectionViewFlowLayout

继承UICollectionViewFlowLayout创建一个类似于carousel的布局。重写一些关键方法。

本例主要实现过程:
1.继承UICollectionViewFlowLayout
2.变换cell的scale和alpha的值
3.设置line spacing,这样item就可以重叠
4.修改cell的形状,添加一个border
5.让cell居中

1.重写override func layoutAttributesForElements(in:) -> [UICollectionViewLayoutAttributes]方法,根据距离view中心的距离,调整scale和alpha
2.重写override func shouldInvalidateLayout(forBoundsChange:) -> Bool方法,来更新布局
3.重写override func targetContentOffset(forProposedContentOffset:withScrollingVelocity:) -> CGPoint方法,返回滚动停止的位置
4.要注意的是第一个item和最后一个item也可以居中显示,所以要调整sectionInset

自定义的CharacterFlowLayout

class CharacterFlowLayout: UICollectionViewFlowLayout {    var standardItemAlpha: CGFloat = 0.5    var standardItemScale: CGFloat = 0.5    var isSetup = false    //During each layout update, the collection view calls this method first to give your layout object a chance to prepare for the upcoming layout operation.    override func prepare() {        super.prepare()        if isSetup == false {            setupCollectionView()            isSetup = true        }    }    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {        let attributes = super.layoutAttributesForElements(in: rect)        var attributesCopy = [UICollectionViewLayoutAttributes]()        for itemAttributes in attributes! {            let itemAttributesCopy = itemAttributes.copy() as! UICollectionViewLayoutAttributes            changeLayoutAttributes(itemAttributesCopy)            attributesCopy.append(itemAttributesCopy)        }        return attributesCopy    }    override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {        return true    }    //改变布局属性    func changeLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) {        let collectionCenter = collectionView!.frame.size.height/2        let offset = collectionView!.contentOffset.y        //相当于item中心点距离屏幕的距离        let normalizedCenter = attributes.center.y - offset        //最大的distance: 上一个item高度一半+lineSpacing+下一个item高度一半        let maxDistance = self.itemSize.height + self.minimumLineSpacing        //item的中心点距离屏幕中心的距离        let distance = min(abs(collectionCenter - normalizedCenter), maxDistance)        let ratio = (maxDistance - distance)/maxDistance        //调整alpha和scale和zIndex        let alpha = ratio * (1 - self.standardItemAlpha) + self.standardItemAlpha        let scale = ratio * (1 - self.standardItemScale) + self.standardItemScale        attributes.alpha = alpha        attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)        attributes.zIndex = Int(alpha * 10)    }    //Returns the point at which to stop scrolling.    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {        print("\(proposedContentOffset.x) : \(proposedContentOffset.y)")        let layoutAttributes = self.layoutAttributesForElements(in: collectionView!.bounds)        let center = collectionView!.bounds.size.height / 2        let proposedContentOffsetCenterOrigin = proposedContentOffset.y + center        //获取距离中心最近item的layoutAttributes        let closest = layoutAttributes!.sorted { abs($0.center.y - proposedContentOffsetCenterOrigin) < abs($1.center.y - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes()        let targetContentOffset = CGPoint(x: proposedContentOffset.x, y: floor(closest.center.y - center))        return targetContentOffset    }    //主要是设置sectionInset    func setupCollectionView() {        self.collectionView!.decelerationRate = UIScrollViewDecelerationRateFast        let collectionSize = collectionView!.bounds.size        let yInset = (collectionSize.height - self.itemSize.height) / 2        let xInset = (collectionSize.width - self.itemSize.width) / 2        self.sectionInset = UIEdgeInsetsMake(yInset, xInset, yInset, xInset)    }}

效果如下:

旋转木马的效果

可拉伸的Header

继承UICollectionViewFlowLayout,重写override func layoutAttributesForElements(in:) -> [UICollectionViewLayoutAttributes]方法,通过检查representedElementKind判断是否为section header,然后改变section header的高度

class StretchyHeaderLayout: UICollectionViewFlowLayout {  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {    let layoutAttributes = super.layoutAttributesForElements(in: rect)! as [UICollectionViewLayoutAttributes]    let offset = collectionView!.contentOffset    if (offset.y < 0) {      let deltaY = fabs(offset.y)      for attributes in layoutAttributes {        if let elementKind = attributes.representedElementKind {          if elementKind == UICollectionElementKindSectionHeader {            var frame = attributes.frame            frame.size.height = max(0, headerReferenceSize.height + deltaY)            frame.origin.y = frame.minY - deltaY            attributes.frame = frame          }        }      }    }    return layoutAttributes  }  override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {    return true  }}

效果如下:

这里写图片描述

UICollectionViewLayout

UICollectionViewLayout是一个抽象的基类。
在继承UICollectionViewLayout之前,首先要考虑的是使用UICollectionViewFlowLayout
下列情形可以考虑使用UICollectionViewLayout
1.不是网格布局
2.需要在多个方向滚动
3.cell的位置频繁的改变

HOW UICOLLECTIONVIEWLAYOUT WORKS

  • Handful of methods provide core behavior
  • Size of the scrollable content area
  • Attributes for cells and views position
  • Collection view works with custom layout to manage layout process

在布局的过程中,collectionView调用你布局对象的三个方法的顺序如下:
1.override func prepare()执行初始的计算
2.override var collectionViewContentSize : CGSize返回整个content区域的大小
3.override func layoutAttributesForElements(in:) -> [UICollectionViewLayoutAttributes]指定矩形区域内cell和view的attribute的集合

如下的多行布局,使用了UICollectionViewLayout

class CustomViewLayout: UICollectionViewLayout {    var numberOfColumns = 0    var cache = [UICollectionViewLayoutAttributes]()    fileprivate var contentHeight: CGFloat = 0    fileprivate let cellHeight: CGFloat = 300.0    fileprivate var width: CGFloat {        get{            return collectionView!.bounds.width        }    }    override func prepare() {        if cache.isEmpty {            let columnWidth = width / CGFloat(numberOfColumns)            var xOffsets = [CGFloat]()//x方向的偏移量            for column in 0..<numberOfColumns {                xOffsets.append(CGFloat(column) * columnWidth)            }            //y方向的偏移量            var yOffsets = [CGFloat](repeating: 0, count: numberOfColumns)            var column = 0            for item in 0..<collectionView!.numberOfItems(inSection: 0) {                //修改frame                let indexPath = IndexPath(item: item, section: 0)                let frame = CGRect(x: xOffsets[column], y: yOffsets[column], width: columnWidth, height: cellHeight)                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)                attributes.frame = frame                cache.append(attributes)                contentHeight = max(contentHeight, frame.maxY)                yOffsets[column] = yOffsets[column] + cellHeight                column = column >= (numberOfColumns - 1) ? 0 : column+1            }        }    }    override var collectionViewContentSize: CGSize{        return CGSize(width: width, height: contentHeight)    }    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {        var layoutAttributes = [UICollectionViewLayoutAttributes]()        for attributes in cache {            if attributes.frame.intersects(rect) {//rect相交                layoutAttributes.append(attributes)            }        }        return layoutAttributes    }}

效果如下:

多行布局

多个collection view布局

步骤:
1.创建layout
2.配置layout
3.使当前layout无效
4.设置layout

  @IBAction func carouselLayoutButtonAction(_ sender: UIBarButtonItem) {    let layout = CarouselViewLayout()    layout.delegate = self    layout.numberOfColumns = 1    layout.cellPadding = -20    collectionView!.contentInset = UIEdgeInsets(top: 5, left: 100, bottom: 10, right: 100)    collectionView?.collectionViewLayout.invalidateLayout()    collectionView?.setCollectionViewLayout(layout, animated: true)    collectionView?.reloadData()  }

注意重写layoutAttributesForItem方法

 override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {    return cache[indexPath.item]  }

效果如下:

这里写图片描述

参考

  • Course Lessons: Custom Collection View Layout
  • Collection View Programming Guide for iOS
0 0
原创粉丝点击