自定义UICollectionView布局
来源:互联网 发布:球头铣刀怎么编程 编辑:程序博客网 时间:2024/05/17 09:18
记录ray上学习自定义UICollectionView布局学习笔记
自定义UICollectionView布局
UICollectionViewFlowLayout
UICollectionViewFlowLayout
是UICollectionView
的默认的布局方式
UICollectionViewDelegateFlowLayout
代理方法
1.func collectionView(_:layout:sizeForItemAt:) -> CGSize
item的size
2.func collectionView(_:layout:insetForSectionAt:) -> UIEdgeInsets
section的inset,相当于是内容的margin
3.func collectionView(_:layout:minimumLineSpacingForSectionAt:) -> CGFloat
行间距
4.func collectionView(_:layout:minimumInteritemSpacingForSectionAt:) -> CGFloat
item之间的距离
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
- UICollectionView之自定义布局
- 自定义UICollectionView布局-入门
- 自定义UICollectionView的布局
- UICollectionView自定义布局
- UICollectionView自定义布局
- UICollectionView自定义流水布局
- UICollectionView自定义布局类
- UICollectionView自定义布局
- 自定义UICollectionView布局
- 自定义UICollectionView布局
- UICollectionView的自定义布局
- 自定义UICollectionView布局-线性布局
- UICollectionView(二)自定义布局UICollectionViewLayout
- UICollectionView详解之自定义布局
- 自定义UICollectionView布局(类似集五福)
- iOS之UICollectionView自定义布局
- iOS之UICollectionView自定义布局
- UICollectionView 自定义布局教程: Pinterest
- 产品经理之PM的逻辑工具流程图-全栈工程师熊盼
- visual studio运行时库MT、MTd、MD、MDd的研究
- A*B NTT快速数论变换
- 二叉树-输出树中从根到每个叶子节点的路径
- CMake 入门实战
- 自定义UICollectionView布局
- Pattern类与Matcher方法的验证
- java 基础数据结构
- android camera相关
- Network in Network
- 行、列递增的二维数组数字查找
- 思维导图课程
- 500. Keyboard Row
- Ubuntu修改DNS