博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CollectionView水平和竖直瀑布流的实现
阅读量:5972 次
发布时间:2019-06-19

本文共 6857 字,大约阅读时间需要 22 分钟。

  最近在项目中需要实现一个水平的瀑布流(即每个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方法,这个方法在第一次需要布局时立刻被调用,之后在每次布局失效之前被调用,在这个方法里正好适合我们写计算布局信息的逻辑代码。

1 - (void)prepareLayout 2 { 3     [super prepareLayout]; 4      5     [self collectionViewFlowLayoutSource];         6      7     switch (flowLayoutType) {                           //根据布局方式选择进行的方法 8         case CGYFlowLayoutTypeHorizontal: 9         {10             [self flowLayoutHorizontal];11         }12             break;13         case CGYFlowLayoutTypeVertical:14 15             [self flowLayoutVertical];16         }17         default:18             break;19     }20 }

   

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 = 0 ; i < verticalNumber ; i++)    {        //初始化高度字典        [_dicVerticalHeight setValue:@"0" forKey:[NSString stringWithFormat:@"%ld",(long)i]];    }        CGFloat elementsWidth = (flowLayoutWidth - self.minimumLineSpacing*(verticalNumber - 1)-self.sectionInset.left - self.sectionInset.right + 1)/verticalNumber;        for (NSInteger i = 0 ; i < [elementsSize count] ; i++)    {        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];                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://www.cnblogs.com/tiaopi/p/6147231.html

你可能感兴趣的文章
Eclipse Java @Override 报错
查看>>
linux的日志服务器关于屏蔽一些关键字的方法
查看>>
mysql多实例实例化数据库
查看>>
javascript 操作DOM元素样式
查看>>
HBase 笔记3
查看>>
【Linux】Linux 在线安装yum
查看>>
Atom 编辑器系列视频课程
查看>>
[原][osgearth]osgearthviewer读取earth文件,代码解析(earth文件读取的一帧)
查看>>
mybatis update返回值的意义
查看>>
expdp 详解及实例
查看>>
通过IP判断登录地址
查看>>
深入浅出JavaScript (五) 详解Document.write()方法
查看>>
Beta冲刺——day6
查看>>
在一个程序中调用另一个程序并且传输数据到选择屏幕执行这个程序
查看>>
代码生成工具Database2Sharp中增加视图的代码生成以及主从表界面生成功能
查看>>
关于在VS2005中编写DLL遇到 C4251 警告的解决办法
查看>>
提高信息安全意识对网络勒索病毒说不
查看>>
maya pyside 多个窗口实例 报错 解决
查看>>
我的友情链接
查看>>
我的友情链接
查看>>