iOS教你轻松打造瀑布流Layout

前言 :

在写这篇文章之前, 先祝贺自己, 属于我的GitHub终于来了. 这也是我的GitHub的第一份代码, 以下文章的代码均可以在Demo clone或下载. 欢迎大家给予意见. 觉得写得不错的也请不要吝惜你们的star.



瀑布流

先普及下什么叫瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。



UICollectionView

我们知道, UICollectionView是苹果推出的继UITableView的又一个非常十分及其牛逼的UI控件, 除了有UITableView的缓存池, 重用机制外, 更能由你自主打造item(tableView中称cell, collectionView中称item)的显示布局, 只要你给它传一个layout布局属性, 他就能按照你的意愿去显示item.



UICollectionViewLayout

这里我就用自定义类JRWaterFallLayout继承UICollectionViewLayout来写瀑布流布局.

  • 需要手动实现的4个方法
方法 说明
– (void)prepareLayout collectionView第一次布局的时候和布局失效的时候会调用该方法, 需要注意的是子类记得要调用super
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect 返回rect范围内所有元素的布局属性的数组
– (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 返回indexPath位置上的元素的布局属性
– (CGSize)collectionViewContentSize 返回collectionView的滚动范围

注意 : 因为layoutAttributesForElementsInRect方法调用十分频繁, 所以布局属性的数组应该只计算一次保存起来而不是每次调用该方法的时候重新计算



 

代理

提供接口给外界修改一些瀑布流布局的参数, 例如显示的列数, 列距, 行距, 边缘距(UIEdgeInsets), 假如代理不实现该方法, 则用默认的参数.. 最最重要的还是item的高度!! 因为每个图片(item)的高度是由图片的宽高比和itemWidth来共同决定的. 所以itemHeight必须由代理来决定.这里展示几个代理方法 :

代理方法 说明
@required  
– (CGFloat)waterFallLayout:(JRWaterFallLayout *)waterFallLayout heightForItemAtIndex:(NSUInteger)index width:(CGFloat)width 返回index位置下的item的高度
@optional  
– (NSUInteger)columnCountOfWaterFallLayout:(JRWaterFallLayout *)waterFallLayout 返回瀑布流显示的列数
– (CGFloat)rowMarginOfWaterFallLayout:(JRWaterFallLayout *)waterFallLayout 返回行间距
– (CGFloat)columnMarginOfWaterFallLayout:(JRWaterFallLayout *)waterFallLayout 返回列间距
– (UIEdgeInsets)edgeInsetsOfWaterFallLayout:(JRWaterFallLayout *)waterFallLayout 返回边缘间距

注意 : 由于上面所说的, layoutAttributesForElementsInRect方法调用十分频繁, 所以代理方法势必也会频繁调用. 但是并不是所有代理方法都是@required的, 所以在调用@optional的代理方法时需要如下代码那样每次都判断代理是否响应了该选择子, 以防代理没有实现该方法, 调用导致程序崩溃

if ( [self.delegate

respondsToSelector:@selector(columnCountOfWaterFallLayout:)] {

_columnCount = [self.delegate columnCountOfWaterFallLayout:self];

} else {

_columnCount = JRDefaultColumnCount;

}

每次都这样判断显然效率很低, 我们可以在prepareLayout方法中进行一次性判断, 然后用一个flags结构体存储起来, 那么下次我们在调用的时候直接对flag进行判断即可. 如下 :

struct { // 记录代理是否响应选择子

BOOL didRespondColumnCount : 1; // 这里的1是用1个字节存储

BOOL didRespondColumnMargin : 1;

BOOL didRespondRowMargin : 1;

BOOL didRespondEdgeInsets : 1;

} _delegateFlags;-

(void)setupDelegateFlags{

_delegateFlags.didRespondColumnCount = [self.delegate respondsToSelector:@selector(columnCountOfWaterFallLayout:)];

_delegateFlags.didRespondColumnMargin = [self.delegate respondsToSelector:@selector(columnMarginOfWaterFallLayout:)];

_delegateFlags.didRespondRowMargin = [self.delegate respondsToSelector:@selector(rowMarginOfWaterFallLayout:)];

_delegateFlags.didRespondEdgeInsets = [self.delegate respondsToSelector:@selector(edgeInsetsOfWaterFallLayout:)];

}

// 那么下次调用方法的时候就变成下面那么优雅了

_columnCount = _delegateFlags.didRespondColumnCount ? [self.delegate columnCountOfWaterFallLayout:self] : JRDefaultColumnCount;



整个瀑布流layout最重要的是找到item摆放的位置. 正是layoutAttributesForItemAtIndexPath方法要做的是. 下面开始说说找这个item位置的思路



瀑布流layout思路

这里本人一共需要用到2个可变数组和一个assign属性, 一个用来记录每列的高度, 一个用来记录所有itemAttributes. assign用来记录高度最大的列的高度

/** itemAttributes数组 */

@property (nonatomic, strong) NSMutableArray *attrsArray;

/** 每列的高度数组 */

@property (nonatomic, strong) NSMutableArray *columnHeights;

/** 最大Y值 */

property (nonatomic, assign) CGFloat maxY;

而在prepareLayout方法中, 以上2个数组都是要清空的, 因为网络请求的新数据到了, collectionView要重新布局的时候如果不清空, 继续往里边加东西的话, 会导致item的布局就全部乱套了..

接下里要处理的就是layoutAttributesForItemAtIndexPath方法中每个item该怎么布局了. 思路很简单

  • 创建一个UICollectionViewLayoutAttributes对象
  • 根据collectionView的width及行间距等几个参数, 计算出item的宽度
  • 找到最短列的列号
  • 根据列号计算item的x值, y值, 询问代理拿到item的高度
  • 设置UICollectionViewLayoutAttributes对象的frame属性
  • 返回UICollectionViewLayoutAttributes对象

问题主要出在怎么计算出x, y, width, height上. 看图说话.

详细的计算步骤可以看Demo.



最后

理论上只要你有足够强大的算法计算能力, 什么显示布局都能写出来. collectionViewLayout并不止于瀑布流!

时间: 2024-10-16 06:09:59

iOS教你轻松打造瀑布流Layout的相关文章

iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作)

iOS开发UI篇—自定义瀑布流控件(蘑菇街数据刷新操作) 一.简单说明 使用数据刷新框架: 该框架提供了两种刷新的方法,一个是使用block回调(存在循环引用问题,_ _weak),一个是使用调用. 问题:在进行下拉刷新之前,应该要清空之前的所有数据(在刷新数据这个方法中). 移除正在显示的cell: (1)把字典中的所有的值,都从屏幕上移除 (2)清除字典中的所有元素 (3)清除cell的frame,每个位置的cell的frame都要重新计算 (4)清除可复用的缓存池. 该部分的代码如下: 1

一步步教你js原生瀑布流效果实现

一步步教你js原生瀑布流效果实现 什么是瀑布流效果 首先,让我们先看一段动画: 在动画中,我们不难发现,这个动画有以下特点: 1.所有的图片的宽度都是一样的 2.所有的图片的高度是不一样的 3.图片一张挨着一张竖直排列 4.鼠标向下滚动,一直不停的加载图片 5.浏览器的宽度改变,图片的列数会进行相应的更改 那么这种效果类似现实生活中的瀑布,所以我们叫它瀑布流的效果. Js原生瀑布流效果的实现 从上述分析中,我们可以把整个效果分为以下四个部分: html+css界面搭建 瀑布流效果 浏览器向下滚动

iOS开发UI篇—自定义瀑布流控件(基本实现)

iOS开发UI篇—自定义瀑布流控件(基本实现) 一.基本实现 说明:在View加载的时候,刷新数据. 1.实现代码 YYViewController.m文件 1 // 2 // YYViewController.m 3 // 06-瀑布流 4 // 5 // Created by apple on 14-7-28. 6 // Copyright (c) 2014年 wendingding. All rights reserved. 7 // 8 9 #import "YYViewControll

iOS开发UI篇—自定义瀑布流控件(蘑菇街实现)

iOS开发UI篇—自定义瀑布流控件(蘑菇街瀑布流) 一.简单说明 关于瀑布流 1.是使用UIScrollView实现的 2.刷新数据(reloadData)方法里面做哪些事情 3.layoutSubviews方法里面做哪些事情 4.模仿UItableView进行设计 完善: 瀑布流控件第一次显示到屏幕上的时候自动的向数据源索要数据,而不需要手动调用.这需要监听View的显示,View的显示有一个方法,叫做willMoveToSuperview:在该方法中直接刷新一次数据即可. 二.把自定义的瀑布

iOS 两个tableview的 瀑布流

iOS 两个tableview的 瀑布流1. [代码]Objective-C     ////  DocViewController.m//  getrightbutton////  Created by 隋文涛 on 12-12-9.//  Copyright (c) 2012年 隋文涛. All rights reserved.// #import "DocViewController.h"#define heightofimage(image) image.size.height

iOS开发UI篇—自定义瀑布流控件(接口设计)

iOS开发UI篇—自定义瀑布流控件(接口设计) 一.简单说明 1.关于瀑布流 电商应用要展示商品信息通常是通过瀑布流的方式,因为每个商品的展示图片,长度和商都都不太一样. 如果不用瀑布流的话,展示这样的格子数据,还有一种办法是使用九宫格. 但利用九宫格有一个缺点,那就是每个格子的宽高是一样的,如果一定要使用九宫格来展示,那么展示的商品图片可能会变形. 为了保证商品图片能够按照原来的宽高比进行展示,一般采用的是瀑布流的方式. 2.瀑布流的特点: 由很多的格子组成,但是每个格子的宽度和高速都是不确定

iOS开发UI篇—自定义瀑布流控件(cell的循环利用)

iOS开发UI篇—自定义瀑布流控件(cell的循环利用) 一.简单说明 当滚动的时候,向数据源要cell. 当UIScrollView滚动的时候会调用layoutSubviews在tableView中也是一样的,因此,可以用这个方法来监听scrollView的滚动,可以在在这个地方向数据源索要对应位置的cell(frame在屏幕上的cell). 示例: 当scrollView在屏幕上滚动的时候,离开屏幕的cell应该放到缓存池中去,询问即将(已经)进入到屏幕的cell,对于还没有进入到屏幕的ce

[iOS高级] UICollectionView实现瀑布流效果

UICollectionView在2012年被提出,已经不是什么新技术了,在此只是做一下简单的实现. 集合视图:UICollectionView UICollectionView和UITableView类似,它也是datasource和delegate设计模式的:datasource为view提供数据源,告诉view要显?示些什么东?以及如何显示它们,delegate提供一些样式的?细节以及?户交互的响应. 在collectionView中,对于cell的布局比较复杂,专?使?了?个类来对col

瀑布流代码,简洁版 带分页

接上一篇   瀑布流代码,简洁版 的功能之上添加分页的功能 Index.cshtml @using PagedList.Mvc @model PagedList.StaticPagedList<int> @{ ViewBag.Title = "瀑布流"; Layout = "~/Views/Shared/_Layout.cshtml"; } @section header{ <script src="~/Scripts/jquery-ui