CollectionView水平和竖直瀑布流的实现
最近在项目中需要实现一个水平的瀑布流(即每个Cell的高度是固定的,但是长度是不固定的),因为需要重写系统
UICollectionViewLayout中的一些方法通过计算去实现手动布局,所以本着代码可复用的原则(其实就是懒,不想再写一遍而已~~),干脆把水平和竖直模式都集成到一个文件中,通过protocol去控制瀑布流的显示模式。
首先我们需要了解一下UICollectionViewLayout是个啥玩意,为什么通过它可以进行瀑布流的实现呢?在苹果官方Api Reference中我们可以看到,UICollectionViewLayout的作用就是生成集合视图的布局信息。UICollectionViewFlowLayout对UICollectionViewLayout进行了扩充,UICollectionViewFlowLayout不但可以重写页面的布局,而且它还有一些属性可以用于方便的设置诸如cell之间的间距,cell和边界的间距的值,因此我们通过实现一个类继承自UICollectionViewFlowLayout不但可以重写UICollectionViewLayout中的布局方法,还可以快捷的设置cell的间距。接下来我们直接上干货。
在开始之前我们通过Category来添加一些扩展的方法,以便于我们获取UICollectionView的布局信息。
typedef NS_ENUM(NSInteger, CGYFlowLayoutType)
{
CGYFlowLayoutTypeHorizontal = 1, //水平显示模式,UICollectionView中的元素宽度不同,高度相同
CGYFlowLayoutTypeVertical, //竖直现实模式,UICOllectionView中的元素宽度相同,高度不同
};
@protocol CGYFlowLayoutdataSource <NSObject>
@required
/**
指定UICollectionView的显示模式
@return CGYFlowLayoutType 瀑布流的展现方式
*/
- (CGYFlowLayoutType)CGYCollectionViewFlowLayoutType;
/**
水平模式下数组中传入的是每个元素的宽度,竖直模式下传入元素的高度
@return 包含Cell高度或宽度的数组
*/
- (NSArray *)CGYFlowLayoutElementsSize;
/**
UICollectionView的宽度
@return UICollectioncView的宽度
*/
- (CGFloat)CGYFlowLayoutWidth;
@optional
/**
Cell高度不定时需要实现此方法,制定CollectionView中显示的列数
@return UICollectionView的列数
*/
- (NSInteger)CGYFlowLayoutVerticalNumber;
/**
Cell宽度不定时需要实现此方法,元素的自定义高度
@return UICollectionView中每个元素的高度
*/
- (CGFloat)CGYFlowLayoutHorizontalCommonHeight;
@end
@interface CGYFlowLayout ()
{
NSArray *elementsSize; //布局元素尺寸存储数组
NSInteger verticalNumber; //竖直不规则布局列数
NSMutableArray *_arrayPosition; //元素布局位置存储数组
NSMutableDictionary *_dicVerticalHeight; //对应列高度存储字典 CGFloat flowLayoutWidth; //UICollectionView的宽度 CGFloat horizontalElementsHeight; //水平模式下每行元素的高度 CGFloat HorizontalHeight; //UICollectionView的总高度 CGYFlowLayoutType flowLayoutType; //布局模式
}
在声明一些属性用于存储计算相关的数据,准备工作完成以后就可以开始我们的布局实现了。
首先,UICollectionViewLayout中有一个prepareLayout方法,这个方法在第一次需要布局时立刻被调用,之后在每次布局失效之前被调用,在这个方法里正好适合我们写计算布局信息的逻辑代码。
- (void)prepareLayout
{
[super prepareLayout]; [self collectionViewFlowLayoutSource]; switch (flowLayoutType) { //根据布局方式选择进行的方法
case CGYFlowLayoutTypeHorizontal:
{
[self flowLayoutHorizontal];
}
break;
case CGYFlowLayoutTypeVertical:
[self flowLayoutVertical];
}
default:
break;
}
}
Cell元素高度相同,宽度不同布局计算方法:
#pragma mark - CGYFlowLayout 布局方法实现
//水平不规则布局方式实现
- (void)flowLayoutHorizontal
{
[_arrayPosition removeAllObjects];
CGFloat positionX = self.sectionInset.left;
HorizontalHeight = self.sectionInset.top;
CGFloat elementsWidth;
for (NSInteger i = 0 ; i < [elementsSize count] ; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
elementsWidth = [[elementsSize objectAtIndex:i] floatValue];
NSAssert(elementsWidth>flowLayoutWidth, @"这个元素比CollectionView的宽度还宽");
if ((positionX + self.sectionInset.right + elementsWidth) > flowLayoutWidth)
{
positionX = self.sectionInset.left;
HorizontalHeight = HorizontalHeight + horizontalElementsHeight + self.minimumInteritemSpacing;
attr.frame = CGRectMake(positionX, HorizontalHeight, elementsWidth, horizontalElementsHeight);
positionX = positionX + elementsWidth + self.minimumLineSpacing;
}
else
{
attr.frame = CGRectMake(positionX, HorizontalHeight, elementsWidth, horizontalElementsHeight);
positionX = positionX + elementsWidth + self.minimumLineSpacing;
}
[_arrayPosition addObject:attr];
}
HorizontalHeight = HorizontalHeight + horizontalElementsHeight;
}
在Cell元素高度固定时,代码实现较为简单,只需要判断当前行的宽度是否能够容下下一个Cell元素插入就行了,如果下一个元素插入后行宽超过了UICollectionView的宽度,就将Cell插入到下一行,最后计算出布局的总高度信息。在每次插入Cell元素后,通过_arrayPosition数组记录下Cell元素的位置信息。
Cell元素宽度相同,高度不相同时计算相对来说复杂一些,我们需要判断找出每次插入后最短的那一列,将下一个元素插入到最短的列中。我们通过一个NSMutableDictionary来存储每次插入后的列高,NSMutableDictionary的key值就是第几列,value就是该列的高度,在每次插入后都刷新该列的高度值。
//竖直不规则布局方式实现
- (void)flowLayoutVertical
{
[_arrayPosition removeAllObjects]; //重新布局时移除所有 CGFloat positionX; //元素布局X坐标
CGFloat positionY; //元素布局Y坐标
CGFloat elementsHeight; //元素高度
NSInteger minHeightPosition; //最小高度行位置 _dicVerticalHeight = [[NSMutableDictionary alloc] init];
for(NSInteger i = ; i < verticalNumber ; i++)
{
//初始化高度字典
[_dicVerticalHeight setValue:@"" forKey:[NSString stringWithFormat:@"%ld",(long)i]];
} CGFloat elementsWidth = (flowLayoutWidth - self.minimumLineSpacing*(verticalNumber - )-self.sectionInset.left - self.sectionInset.right + )/verticalNumber; for (NSInteger i = ; i < [elementsSize count] ; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:]; UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; minHeightPosition = [self MinHeightPosition];
positionX = self.sectionInset.left + (elementsWidth + self.minimumLineSpacing)*minHeightPosition;
positionY =self.sectionInset.top + [[_dicVerticalHeight valueForKey:[NSString stringWithFormat:@"%ld",(long)minHeightPosition]] floatValue];
elementsHeight = [[elementsSize objectAtIndex:i] floatValue]; attr.frame = CGRectMake(positionX, positionY, elementsWidth, elementsHeight); [_dicVerticalHeight setValue:[NSNumber numberWithFloat:(positionY + elementsHeight + self.minimumInteritemSpacing)] forKey:[NSString stringWithFormat:@"%ld",(long)minHeightPosition]]; [_arrayPosition addObject:attr];
}
}
当我们在prepareLayout方法中计算出整个UIcollectionView中所有cell的position后,我们可以通过选择重写layoutAttributesForElementsInRect:(CGRect)rect方法返回一个包含所有cell布局信息的数组。
//返回计算好的布局数组信息
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return _arrayPosition;
}
现在我们拿到了所有cell的布局信息可以完事了吗?很抱歉,答案是NO!!!UICollectionViewLayout强硬的规定了,当你重写了它的时候,你就必须要实现一个叫做collectionViewContentSize的方法,这个方法确定了UICollectionView可滑动的范围,我试了一下,果然不重写它我们的CollectionView是动不了的。。。
- (CGSize)collectionViewContentSize
{
if (flowLayoutType == CGYFlowLayoutTypeVertical)
{
return CGSizeMake(flowLayoutWidth, [self maxHeightPosition]);
}
else
{
return CGSizeMake(flowLayoutWidth, HorizontalHeight);
}
}
当UICollectionView的元素高度固定时,返回值就是我们记录下来的HorizontalHeight,但是当元素高度不固定时,我们就要从所有列中找出高度最高的那一列,并返回那一列的高度信息,这样才可以保证在滑动时每个Cell都是可见的。
代码的github地址是:https://github.com/cgy-tiaopi/CGYUICollectionViewFlowLayout 有问题的地方还请大神拍砖指教。
CollectionView水平和竖直瀑布流的实现的更多相关文章
- border-radius的水平和竖直半径
通常我们设置border-radius都只区分四个角的, 如border-radius: 1em 2em. 其实每个角的border-radius都由两部分组成, 水平半径和竖直半径. 要设置水平和竖 ...
- 设置UIScrollView只可以水平或者竖直滚动
UIScrollView里边包含多个UIWebView: 可以通过设置contentSize的值,设置其width为UIScrollerView可视区域的宽度:即UIScrollView的width, ...
- Winform中设置ZedGraph曲线图的水平与竖直参考线
场景 Winforn中设置ZedGraph曲线图的属性.坐标轴属性.刻度属性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...
- ListView 水平滑动 水平和竖直滑动
效果 Activity public class MainActivity extends Activity { @Override protected void onCreate(B ...
- android实现超酷的腾讯视频首页和垂直水平网格瀑布流一揽子效果
代码地址如下:http://www.demodashi.com/demo/13381.html 先来一波demo截图 实现ListView.GridView.瀑布流 1.导入RecyclerView的 ...
- 用collectionview实现瀑布流-转(后面附demo,供参考)
算法总体思路 先说一下总体上的思路.既然图片的大小.位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayou ...
- 用collectionview实现瀑布流-转
算法总体思路 先说一下总体上的思路.既然图片的大小.位置各不一样,我们很自然地会想到需要算出每个item的frame,然后把这些frame赋值给当前item的UICollectionViewLayou ...
- Android RecyclerView(瀑布流)水平/垂直方向分割线
Android RecyclerView(瀑布流)水平/垂直方向分割线 Android RecyclerView不像过去的ListView那样随意的设置水平方向的分割线,如果要实现Recycle ...
- 【iOS开发】collectionView 瀑布流实现
一.效果展示 二.思路分析 1> 布局的基本流程 当设置好collectionView的布局方式之后(UICollectionViewFlowLayout),当系统开始布局的时候,会调用 pre ...
随机推荐
- 【转载学习前辈的经验】-- Mistakes I made (as a developer) 我(作为一名开发者)所犯过的错误
我 2006 年开始工作,至今已经 10 年.10 年是个里程碑,我开始回顾自己曾经犯过的错误,以及我希望从同行那里得到什么类型的忠告.一切都在快速改变,10 年了,我不能确定这些秘诀是否还有用. 不 ...
- EF Core 杂记
本系列文章,将介绍本人在学习和使用EF Core的过程中的收获与心得. 或许有的地方讲的错误 欢迎大家批评指出. 1.EF Core 数据库迁移(Migration)
- 蒙特卡洛模拟入门的几个小例子(R语言实现)
嗯,第一个例子是怎么用蒙特卡洛模拟求pi的值:第二个是用蒙特卡洛模拟求解定积分:第三个是用蒙特卡洛模拟证券市场求解其收益:第四个是用蒙特卡洛模拟验证OLS的参数的无偏性:然后还要R是如何求导,计算导数 ...
- Sql Server 学习链接
避免SQL全表查询:http://www.dedecms.com/knowledge/data-base/sql-server/2012/0821/11698.html?jdfwkey=n3nuq
- Open Xml 读取Excel中的图片
在我的一个项目中,需要分析客户提供的Excel, 读出其中的图片信息(显示在Excel的第几行,第几列,以及图片本身). 网络上有许多使用Open Xml插入图片到Word,Excel的文章, 但 ...
- check用户协议
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- static修饰符
static修饰符表示静态的,可修饰字段.方法.内部类,其修饰的成员属于类,也就是说static修饰的资源属于类级别,而不是对象级别. static的正真作用:用来区别字段,方法,内部类,初始化代码块 ...
- 天河微信小程序入门《三》:打通任督二脉,前后台互通
原文链接:http://www.wxapp-union.com/forum.php?mod=viewthread&tid=505&extra=page%3D1 天河君在申请到https ...
- Gerrit增加SSL证书
在http的基础上增加SSL 配置gerrit.config文件 [gerrit] basePath = git canonicalWebUrl = https://172.16.99.212/ .. ...
- 通过ip查询对方位置
我们这里使用的是baidumap的高精度IP定位API的接口,具体说明可以参考baidu提供的文档 使用前需要申请一个AK密钥类似于Kgcx......................xxl8w的样式 ...