iMDb和格瓦拉的图片集排布,自己抽时间进行了一次尝试,使用自定义UICollectionViewFlowLayout来进行布局,目前点击事件效果还没有完善。

先看一下iMDb和格瓦拉的图片集排布的效果:

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

模仿IMDb和格瓦拉的图片集混合排布展示效果 - 敏捷大拇指 - 模仿IMDb和格瓦拉的图片集混合排布展示效果 1


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

模仿IMDb和格瓦拉的图片集混合排布展示效果 - 敏捷大拇指 - 模仿IMDb和格瓦拉的图片集混合排布展示效果 2


下面是一张相关计算图,字比较丑,别喷我。

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

模仿IMDb和格瓦拉的图片集混合排布展示效果 - 敏捷大拇指 - 模仿IMDb和格瓦拉的图片集混合排布展示效果 3


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

FJSPicMixCollectionViewLayout.h

[Objective-C] 纯文本查看 复制代码
#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

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




相关内容

iOS视觉差Parallax效果简析

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





作者:BestJoker