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: 方法的实现遵循如下的步骤:

  1. 遍历由prepareLayout方法生成的数据,以访问缓存的attributes或创建新的attributes。
  2. 检查每个item的frame,看是否与layoutAttributesForElementsInRect:中的矩形相交
  3. 对于每个相交item,将相应的UICollectionViewLayoutAttributes对象添加到数组。
  4. 将布局属性数组返回

根据你如何管理布局信息,你可以在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方法,返回图片和文字的高度
在计算图片的高度时使用了AVFoundationAVMakeRect(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)    }}
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 艾灸后吃水果了怎么办 刮痧后能洗澡了怎么办 膝盖筋扭了肿了怎么办 骑行之后膝盖疼怎么办 膝盖软骨磨没了怎么办 腿上膝盖长骨刺怎么办 打羽毛球后膝盖疼怎么办 打完羽毛球膝盖疼怎么办 骑动感单车后膝盖痛怎么办 踩完动感单车右膝盖痛怎么办 跑步机走步损伤膝盖怎么办 健身把膝盖伤了怎么办 膝盖后面的腿窝筋酸疼怎么办 跳绳把膝盖伤了怎么办 鼻塞嗓子肿呼吸困难怎么办 鼻子内上火肿了怎么办 鼻子又红又肿怎么办 在公司班组人员不听话怎么办 熬了一夜睡不着怎么办 有人看我脚走路怎么办 穿牛仔裤裆部有三角怎么办 我的电脑打cffps不稳定怎么办 生米煮成了熟饭该怎么办 做完下蹲腿疼该怎么办 下蹲腿的筋酸痛怎么办 躺平了 腿酸痛怎么办 500上下蹲后腿痛怎么办 蹲完马步后站不起来怎么办? 车在水里熄火了怎么办 脚磕到了很疼怎么办 破腹产4年了腰疼怎么办 蹲起之后腿疼怎么办 深蹲起跳伤腰部怎么办 蹲起膝盖有响声怎么办 腰突然不能弯了怎么办 蚂蚱吃了会过敏怎么办 孕妇能吃蚂蚱菜怎么办 孕妇吃了蚂蚁菜怎么办 白果很硬的时候怎么办 有痔疮吃了胡椒怎么办 吃紫菜多了难受怎么办