模仿IMDb和格瓦拉的图片集混合排布展示效果.


iMDb和格瓦拉的图片集排布,自己抽时间进行了一次尝试,使用自定义UICollectionViewFlowLayout来进行布局,目前点击事件效果还没有完善,下面是一张相关计算图,字比较丑,别喷我.

下面贴出来自定义布局的.h和.m文件,第一次写,不会使用高端工具,以后会继续加油,有好用的工具,可以推荐给我,在这里感谢大家了.

FJSPicMixCollectionViewLayout.h

#import "BQImageModel.h"

@interface FJSPicMixCollectionViewLayout : UICollectionViewFlowLayout

@property (nonatomic,strong)NSMutableArray * modelArray;

@property (nonatomic,assign)BOOL isHeaderRefresh;/**< 区分是上拉加载还是下拉刷新 */

@end


#import "FJSPicMixCollectionViewLayout.h"

@interface FJSPicMixCollectionViewLayout ()

@property (nonatomic,assign)CGFloat contentHeight;/**< 总体高度 */@property (nonatomic,strong)NSMutableArray * attributesArray;/**< 存放所有布局的array *///

@property (nonatomic,assign)NSInteger lastArrayCount;/**< 记录上一次的数组整体个数,为了上拉刷新的时候不需要重新计算之前数组的位置,进行性能优化 */

@end

@implementation FJSPicMixCollectionViewLayout

/* 所以我们的思路是在- (void)prepareLayout;方法中算出所有item的frame,并赋值给当前item的  UICollectionViewLayoutAttributes。用图片的形式比较直观: */

-(void)prepareLayout{ 

   [super prepareLayout];   

 if (self.isHeaderRefresh) {       

 //初始化保存所有item attributes的数组      

  self.attributesArray = [NSMutableArray array];        

self.contentHeight = 0.f;//新添加  

  }   

 [self getwholeRowFrame];

}

- (void)getwholeRowFrame{   

 //设置一个宽度来记录和判断图片是否换行.   

 CGFloat width = 0.f;   

 //保存同一行图片的所有尺寸比例和,用来计算这一行图片的高度   

 CGFloat scaleSum = 0.f;   

 //如果之前的布局数组中有数据,上拉加载就从下一行,新来的model开始计算,不需要考虑之前最后一行是否已经超出屏幕,正常是要从倒数第一行重新计算,但是图片可能会有所闪动,体验不好.    

NSInteger beginIndex = self.attributesArray.count?self.attributesArray.count:0;  

  NSInteger currentIndex = beginIndex;    

//不需要担心最后如果只有一张图的话,没有匹配如何显示,因为遍历的次数和图片的数量相同.    NSLog(@"beginIndex == %ld",beginIndex);   

 for (NSInteger i = beginIndex; i < self.modelArray.count; i++) {  

      BQImageModel * model = [self.modelArray objectAtIndex:i];     

   width = width + model.width;     

   scaleSum = scaleSum + model.whScale;  

      //换行之后需要重新清空累计的宽度 同时保存下一个currentIndex从第几行开始.     

   //累计图片宽度,如果宽度超过了屏宽减去间距,则换行(这种方式存在一定的问题,依赖于图片的原始高度来进行排布,不过服务器没法根据客户端来进行图片匹配,所以继续研究了格瓦拉之后,找到了它有一个图片最大高度,即屏幕的一半,所以采用比例和的方式来进行约束.)      

  if (scaleSum >= 2.0) {         

   [self setAttributesFromCurrentIndex:currentIndex DestionIndex:i scaleSum:scaleSum];            //换行之后需要重新清空累计的宽度 同时保存下一个currentIndex从第几行开始.        

    width = 0.f;          

  scaleSum = 0.f;            

currentIndex = i + 1;        

}else        {         

   //如果是最后一行并没有满足超过屏宽,则将当前几个视图进行计算,铺满屏幕   

         if (i == self.modelArray.count - 1) {       

         [self setAttributesFromCurrentIndex:currentIndex DestionIndex:i scaleSum:scaleSum];          }        }    }    }

- (void)setAttributesFromCurrentIndex:(NSInteger)currnetIndex DestionIndex:(NSInteger)destionIndex scaleSum:(CGFloat)scaleSum{   

 //根据公式计算出该行的高度   

 CGFloat height = (ScreenWidth - (destionIndex - currnetIndex) * self.minimumInteritemSpacing) / scaleSum;  

  //均分的宽度,注意:四舍五入成整数   

 height = roundf(height);  

  NSLog(@"从第%ld个到第%ld个,高度为%f",currnetIndex,destionIndex,height);   

 for (NSInteger i = currnetIndex; i <= destionIndex; i++) {       

 //给attributes.frame 赋值,并存入 self.itemsAttributes      

  BQImageModel * model = [self.modelArray objectAtIndex:i];        //根据计算出来的高度来根据图片比例计算出宽度      

  CGFloat width = height * model.whScale;       

 UICollectionViewLayoutAttributes * oldAttributes;     

   /*如果不是这一行的第一个图片,需要获取上一张图片的UICollectionViewLayoutAttributes,用来计算当前的图片的x值.为什么不使用        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];方法来获取上一个内容呢?因为内容为空,都保存到数组self.attributesArray中了,所以直接获取.        */      

  if (i > currnetIndex) {          

  NSInteger oldIndex = i - 1;           

 oldAttributes = [self.attributesArray objectAtIndex:oldIndex];     

   }       

 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];       

 /*获取当前的对应UICollectionViewLayoutAttributes,进行修改,然后保存到数组中        x: 根据同一行,前一个视图进行累计,同时加上self.minimumInteritemSpacing        y: 使用全局的属性记录.        width和height都有计算好了.        */     

   CGFloat orignX = oldAttributes?CGRectGetMaxX(oldAttributes.frame) + self.minimumInteritemSpacing:0;      

  if (destionIndex == currnetIndex && self.modelArray.count - 1 == currnetIndex && model.whScale < 2.0) {        

    attributes.frame = CGRectMake(orignX, self.contentHeight, ScreenWidth, ScreenWidth * 0.5);            height = ScreenWidth * 0.5;     

   }else        {           

 attributes.frame = CGRectMake(orignX, self.contentHeight, width, height);    

    }        

//        NSLog(@"oldAttributes == %f\nself.contentHeight == %f\nwidth == %f\nheight == %f",CGRectGetMaxX(oldAttributes.frame),self.contentHeight,width,height);     

   //        NSLog(@"第%ld个到第%ld个在一行",currnetIndex,destionIndex);        [self.attributesArray addObject:attributes]; 

   }    

//累加记录高度的  

  self.contentHeight = self.contentHeight + height + self.minimumLineSpacing;}-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect

{

NSLog(@"我触发了");

NSLog(@"%ld",self.attributesArray.count);

return self.attributesArray;

}

-(CGSize)collectionViewContentSize{  

  //使用数组中最后一个布局来进行滚动内容的高度,而不是self.contentHeight,原因是需要判断是否是最后一个图片的那一行,如果是不需要累加self.minimumLineSpacing.    UICollectionViewLayoutAttributes * lastAttributes = [self.attributesArray lastObject];        return CGSizeMake(ScreenWidth, CGRectGetMaxY(lastAttributes.frame));}

//#pragma mark -- 返回collectionView的内容的尺寸//-(CGSize)collectionViewContentSize//{////}

#pragma mark -- UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:

#pragma mark -- 返回对应于indexPath的位置的cell的布局属性

//-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

//{

//

//}

//

#pragma mark -- 返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载

//-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

//{

//

//}

//

#pragma mark -- 返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载

//-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

//{

//

//}

//

//-(UICollectionViewLayoutAttributes *)layoutAttributesForInteractivelyMovingItemAtIndexPath:(NSIndexPath *)indexPath withTargetPosition:(CGPoint)position

//{

//

//}

/*

- 当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。

另外需要了解的是,在初始化一个UICollectionViewLayout实例后,会有一系列准备方法被自动调用,以保证layout实例的正确。

首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。

之后,-(CGSize) collectionViewContentSize将被调用,以确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView的本质是一个scrollView,因此需要这个尺寸来配置滚动行为。

接下来-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被调用,这个没什么值得多说的。初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。

另外,在需要更新layout时,需要给当前layout发送 -invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。

*/

//-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

//{

//    return YES;

//}

@end

更多的布局方式,在慢慢的添加,有需要你可以在这里获取到整个项目.

https://github.com/BestJoker/FJSPicMixCollectionViewLayout.git

如果对你的思路有一定的帮助,请别吝啬你的星星.预祝大家编程愉快.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容