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
如果对你的思路有一定的帮助,请别吝啬你的星星.预祝大家编程愉快.