Mosaic布局(类似于瀑布流)
来源:互联网 发布:淘宝龙腾国际名品真假 编辑:程序博客网 时间:2024/06/05 18:05
在Custom Collection View Layout讲到了一个mosaic布局,类似于常见的瀑布流
最终的效果如下:
Mosaic布局(类似于瀑布流)
文档摘录
官方文档内容Creating Custom Layouts
理解核心布局的过程
在布局处理的过程中,collectionview调用布局对象指定方法,其调用的顺序如下:
1.使用prepareLayout方法执行提供布局信息所需的前期计算
2.使用collectionViewContentSize方法返回内容区域的大小
3.使用layoutAttributesForElementsInRect:方法方法返回指定矩形中的单元格和视图的属性
创建布局属性
布局属性是 UICollectionViewLayoutAttributes的实例。使用如下的方法创建:
- layoutAttributesForCellWithIndexPath:
- layoutAttributesForSupplementaryViewOfKind:withIndexPath:
- layoutAttributesForDecorationViewOfKind:withIndexPath:
如果标准属性类不符合您的应用需求,则可以将其子类化并扩展,以存储有关每个视图的其他信息。当对布局属性进行子类化时,需要实现用于比较自定义属性的isEqual:
方法,因为collectionview在其某些操作中使用此方法。另外还需要遵守NSCopying
协议,实现copyWithZone:
方法。
除了定义attributes
子类之外,你的UICollectionReusableView
对象还需要实现apply(_:)
方法,以便它们可以在布局时应用任何自定义属性。
UICollectionViewLayoutAttributes类中的包含的属性:
var frame: CGRect
var bounds: CGRect
var center: CGPoint
var size: CGSize
var transform3D: CATransform3D
var transform: CGAffineTransform
var alpha: CGFloat
var zIndex: Int
var isHidden: Bool
为给定矩形中的item提供布局属性
在布局过程的最后一步,集合视图调用您的布局对象的layoutAttributesForElementsInRect:
方法。该方法的目的是为每个cell以及与指定矩形相交的每个supplementary或decoration视图提供布局属性。对于大的可滚动内容区域,集合视图可能只是要求当前可见的该内容区域的部分中的item的属性。
由于layoutAttributesForElementsInRect:
方法在prepareLayout
方法后调用,所以你应该已拥有创建或return 所需attributes的信息。layoutAttributesForElementsInRect:
方法的实现遵循如下的步骤:
- 遍历由
prepareLayout
方法生成的数据,以访问缓存的attributes或创建新的attributes。 - 检查每个item的frame,看是否与
layoutAttributesForElementsInRect:
中的矩形相交 - 对于每个相交item,将相应的
UICollectionViewLayoutAttributes
对象添加到数组。 - 将布局属性数组返回
根据你如何管理布局信息,你可以在prepareLayout
方法中创建UICollectionViewLayoutAttributes
对象,或者等到在layoutAttributesForElementsInRect:
方法中创建。但需要记住的是,缓存布局信息有许多的好处,为cell重复计算新的布局属性是一项昂贵的操作,可能会对应用程序的性能造成明显的不利影响。
根据需求提供布局的属性
collection view定期询问你的布局对象为正式布局流程之外的单个item提供属性。例如,在为项目配置插入和删除动画时,collection view会询问此信息。你的布局对象必须准备好为每个cell,supplementary view和decoration view提供布局属性。你可以通过覆盖以下方法来执行此操作:
- layoutAttributesForItemAtIndexPath:
- layoutAttributesForSupplementaryViewOfKind:atIndexPath:
- layoutAttributesForDecorationViewOfKind:atIndexPath:
每个自定义的布局类都被期望实现layoutAttributesForItemAtIndexPath:
方法
Mosaic布局实现过程
为什么要自定义布局,而不使用UICollectionViewFlowLayout
? UICollectionViewFlowLayout
的行高按一行最大的item计算,其余的cell则居中,而且还要注意line space。所以自定义布局,继承自UICollectionViewLayout
类似于上一章中将的多列布局
在上面的效果图中,图片的高度和描述文字的高度是动态,可通过代理对象来获取
定义一个代理对象MosaicLayoutDelegate
protocol MosaicLayoutDelegate { //image图片的高度 func collectionView(_ collectionView: UICollectionView, heightForImageAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat //描述文字label的高度 func collectionView(_ collectionView: UICollectionView, heightForDescriptionAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat }
另外需继承UICollectionViewLayoutAttributes
,创建一个MosaicLayoutAttributes
自定义属性,在其中添加一个变量imageHeight
,来保存图片的动态高度
class MosaicLayoutAttributes: UICollectionViewLayoutAttributes { //图片的高度 var imageHeight: CGFloat = 0 //重写copy方法 override func copy(with zone: NSZone?) -> Any { let copy = super.copy(with: zone) as! MosaicLayoutAttributes copy.imageHeight = imageHeight return copy } //重写isEqual方法 override func isEqual(_ object: Any?) -> Bool { if let attributes = object as? MosaicLayoutAttributes { if attributes.imageHeight == imageHeight { return super.isEqual(object) } } return false }}
有了图片高度,就要在定义的cell中改变图片高度的约束。
如下在RoundedCharacterCell
中,重写apply(_:)
方法,使用imageHeight
class RoundedCharacterCell: UICollectionViewCell { @IBOutlet weak var characterImage: UIImageView! @IBOutlet weak var characterTitle: UILabel! @IBOutlet weak var characterInfo: UILabel! //图片高度约束 @IBOutlet weak var imageViewHeightConstraint: NSLayoutConstraint! var character: Characters? { didSet { if let theCharacter = character { characterImage.image = UIImage(named: theCharacter.name) characterTitle.text = theCharacter.title characterInfo.text = theCharacter.description } } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layer.cornerRadius = 12.0 self.layer.borderWidth = 3 self.layer.borderColor = UIColor(red: 0.5, green: 0.47, blue: 0.25, alpha: 1.0).cgColor } override func prepareForReuse() { super.prepareForReuse() characterImage.image = nil characterTitle.text = "" characterInfo.text = "" } //重写apply方法 override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { super.apply(layoutAttributes) let attributes = layoutAttributes as! MosaicLayoutAttributes imageViewHeightConstraint.constant = attributes.imageHeight }}
在定义的布局MosaicViewLayout
中,重写如下的方法和属性:
1.重写class var layoutAttributesClass
,表示使用的attribute为MosaicLayoutAttributes
类型
2.重写prepare()
方法,做初始化计算
3.重写var collectionViewContentSize
,返回内容区域的大小
4.重写layoutAttributesForElements(in rect: CGRect)
方法
class MosaicViewLayout: UICollectionViewLayout { //代理对象 var delegate: MosaicLayoutDelegate! //几列 var numberOfColumns = 0 var cellPadding: CGFloat = 0 //保存计算的属性 var cache = [MosaicLayoutAttributes]() //内容区域的高度 fileprivate var contentHeight: CGFloat = 0 //宽度 fileprivate var width: CGFloat { get { let insets = collectionView!.contentInset return collectionView!.bounds.width - (insets.left + insets.right) } } //内容区域的大小 override var collectionViewContentSize : CGSize { return CGSize(width: width, height: contentHeight) } //表示attribute是MosaicLayoutAttributes类 override class var layoutAttributesClass : AnyClass { return MosaicLayoutAttributes.self } //初始化计算 override func prepare() { if cache.isEmpty { let columnWidth = width / CGFloat(numberOfColumns) var xOffsets = [CGFloat]() for column in 0..<numberOfColumns { xOffsets.append(CGFloat(column) * columnWidth) } var yOffsets = [CGFloat](repeating: 0, count: numberOfColumns) var column = 0 for item in 0..<collectionView!.numberOfItems(inSection: 0) { let indexPath = IndexPath(item: item, section: 0) //cell的宽度 let width = columnWidth - (cellPadding * 2) //图片的宽度 let imageHeight = delegate.collectionView(collectionView!, heightForImageAtIndexPath: indexPath, withWidth: width) //描述文字的高度 let descriptionHeight = delegate.collectionView(collectionView!, heightForDescriptionAtIndexPath: indexPath, withWidth: width) //cell的高度 let height = cellPadding + imageHeight + descriptionHeight + cellPadding let frame = CGRect(x: xOffsets[column], y: yOffsets[column], width: columnWidth, height: height) let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding) let attributes = MosaicLayoutAttributes(forCellWith: indexPath) attributes.frame = insetFrame //更新imageHeight的值 attributes.imageHeight = imageHeight cache.append(attributes) contentHeight = max(contentHeight, frame.maxY) yOffsets[column] = yOffsets[column] + height column = column >= (numberOfColumns - 1) ? 0 : column + 1 } } } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var layoutAttributes = [UICollectionViewLayoutAttributes]() for attributes in cache { if attributes.frame.intersects(rect) {//相交 layoutAttributes.append(attributes) } } return layoutAttributes }}
在控制器中设置layout的代理对象等其它属性
let layout = collectionViewLayout as! MosaicViewLayoutlayout.delegate = selflayout.numberOfColumns = 2layout.cellPadding = 5
实现MosaicLayoutDelegate
方法,返回图片和文字的高度
在计算图片的高度时使用了AVFoundation
的AVMakeRect(aspectRatio: CGSize, insideRect boundingRect: CGRect)
方法
extension MasterViewController: MosaicLayoutDelegate { func collectionView(_ collectionView: UICollectionView, heightForImageAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat { let character = charactersData[indexPath.item] let image = UIImage(named: character.name) let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT)) let rect = AVMakeRect(aspectRatio: image!.size, insideRect: boundingRect) return rect.height } func collectionView(_ collectionView: UICollectionView, heightForDescriptionAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat { let character = charactersData[indexPath.item] let descriptionHeight = heightForText(character.description, width: width-24) let height = 4 + 17 + 4 + descriptionHeight + 12 return height } func heightForText(_ text: String, width: CGFloat) -> CGFloat { let font = UIFont.systemFont(ofSize: 10) let rect = NSString(string: text).boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil) return ceil(rect.height) }}
- Mosaic布局(类似于瀑布流)
- Android 瀑布流列表(类似于ListView)
- 网站错落有致的布局(瀑布流)
- 瀑布流(自定义布局实现)
- 瀑布流图片布局
- RecyclerView布局+瀑布流
- 瀑布流式布局浅析
- 网站瀑布流式布局
- 整理 collectionView 瀑布流 布局
- 自定义瀑布流的布局
- 瀑布布局流--原生js
- 浮动布局实现瀑布流
- Android 瀑布流式布局
- 网站布局中的瀑布流式布局
- Masonry瀑布流式布局库(中文翻译)
- MOSAIC
- mosaic
- 页面布局 --- 瀑布布局
- ZOJ
- BLE 协议栈介绍
- spring-data-mongo aggregate结果大于16m时处理方法
- Shadowsocks的搭建及使用
- ueditor jsp版本 后端配置项没有正常加载,上传插件不能正常使用!
- Mosaic布局(类似于瀑布流)
- Tomcat配置虚拟路径,使上传文件与服务器分离
- 二分查找
- MTK Kernel启动流程源码解析 5 start_kernel 下
- 深度学习
- BLE 广播报文解析
- Spring boot jpa 获取list前10条记录
- HashMap原理
- UbuntuBackports