UICollectionView
注意:类似tableView,可以设置控制器的view就是collectionView,也可以添加一个比较小的collectionView作为控制器的view的子控件
流水布局:
- 系统会自动布局,一排放不下,自动放到第二排
- 界面要通过流水布局对象来设置各种约束
// 创建一个流水布局对象
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
// 设置cell的尺寸
layout.itemSize = CGSizeMake(100, 100);
// 行间距
layout.minimumLineSpacing = 50;
// 设置滚动的方向
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// 设置cell之间的间距
layout.minimumInteritemSpacing = 50;
// 组间距
layout.sectionInset = UIEdgeInsetsMake(100, 20, 0, 30);
collectionView的一些设置
关掉弹簧效果 self.collectionView.bounces = NO;
去掉导航条 self.collectionView.showsHorizontalScrollIndicator = NO;
分页效果 self.collectionView.pagingEnabled = YES;
创建
- 创建的时候必须设置布局对象
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collV = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
- 如果有多种布局方式,可以设置布局方式来切换
[self.collectionView setCollectionViewLayout:[[XMGCircleLayout alloc] init] animated:YES];
- 如果是UICollectionViewController,一般把流水布局的创建和控制器的创建封装起来:重写init方法
- (instancetype)init{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
return [super initWithCollectionViewLayout:layout];
}
- 注意,在UICollectionViewController中,self.view不等于self.collectionView
- 拿到self.collectionView才能设置背景颜色,要加子控件也是加在这里
数据源方法
- 类似于UITableViewController,先设置数据源,一般为控制器,控制器遵守数据源协议
必须用注册cell的方式
// 注册cell,先定义ID
static NSString *ID = @"cell";
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:ID];
- 设置cell的样式
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// 从缓存池里取
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
//indexPath不同于tableView,这里是用indexPath.item来区分每个cell
cell.backgroundColor = [UIColor greenColor];
return cell;
}
- 有多少个cell
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 12;
}
自定义cell
- 如果要给cell里面加点东西,那就必须自定义,因为它不同于tableView的cell有各种样式。
- 在initwithframe方法里添加子控件(
- 懒加载不然每次get都会创建一个imageView)(
- 类似于tableView的cell自定义,
子控件也是加到contontView上
),
- layoutsubViews里布局子控件,注意frame要设置为cell的bounds,详见几何问题总结
- 要给imageView传图片数据,一般也是给cell添加一个imageName属性,然后重写imageName的set方法,在这里传递数据。
代理方法
- 协议
- 实现图像错位移动:监听停止滑动的时候切换图像,通过当前偏移量和上次偏移量的差值来计算图像的移动
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
CGFloat currentOffSetX = scrollView.contentOffset.x;
CGFloat delata = currentOffSetX - self.lastOffSetX;
int index = currentOffSetX / scrollView.width + 1;
self.lastOffSetX = currentOffSetX;
//先让图像往滑动方向移动2个屏幕
self.guide.x += 2 * delata;
//再让图像往回移动1个屏幕,并且设置动画效果
[UIView animateWithDuration:0.25 animations:^{
self.guide.x -= delata;
}];
//通过currentOffSetX来计算当前显示哪一张图片
NSString *guideImageName = [NSString stringWithFormat:@"guide%d",index];
self.guide.image = [UIImage imageNamed:guideImageName];
}
自定义布局对象
流水布局UICollectionViewFlowLayout继承于UICollectionViewLayout,通过自定义布局可以实现一些不同的排布方式
自定义继承于流水布局的布局对象
- 流水布局对象有下面这个方法:通过调用super可以拿到所有cell的布局属性UICollectionViewLayoutAttributes,它们组成了一个数组,一个cell的布局属性对应一个数组的元素。可以通过更改布局属性的属性来调整cell的布局方式。
- 其中的rect是指当前显示的区域
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
// 调用super来拿到所有cell的布局方式
NSArray *attrsArray = [super layoutAttributesForElementsInRect:rect];
// 遍历数组拿出每一个UICollectionViewLayoutAttributes类型的元素
for (UICollectionViewLayoutAttributes *attrs in attrsArray) {
// 可以更改cell的各种属性
attrs.center.x = 100;
// 形变也可以改
attrs.transform = CGAffineTransformMakeScale(0.5, 0.5);
}
return attrsArray;
}
- UICollectionViewLayoutAttributes具体有如下属性
@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, retain) NSIndexPath *indexPath;
- 这个方法默认什么时候调用呢?刚加载collectionView的时候会调用,然后用力拖拽collectionView的时候会调用,而轻轻滑动的时候不会调用
- 如何才能做到轻滑也能调用呢?比如项目做的左右滑动缩放效果,必须时刻调用这个方法来改变图片的大小
- 需要实现下面这个方法,返回YES即可。这个方法的意思是当bounds改变的时候,是否要重新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
- 如何停止滚动的时候,cell会自动往中间靠,起到类似分页pagingEnabled的效果?
- 有两个方法,看起来类似,但是这个没有效果
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset{}
- 我们要用的是这个方法:这个方法在collectionView将要停止滚动时给出了期望停止位置的点和加速度
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
- 这个方法什么时候调用?当collectionView将要停止滚动的时候调用
- proposedContentOffset这个点就是滚动慢下来准备停了,那个最终会停的位置。(此处设定了滚动方向为水平,所以点的y值没用)
- 要达到分页的效果,我们还是要拿到cell的布局属性来进行设置,因此需要手动调用
layoutAttributesForElementsInRect方法。上面的方法里rect由系统传过来,但是这里的rect需要手动设定。(当然不设置rect也行,那就拿到所有cell的布局属性,这么做在cell多的时候会影响性能) - rect的x应该是最终collectionView停下来的时候的offset.x,根据上一条proposedContentOffset的描述,可以判断rect的x就是proposedContentOffset.x
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
CGFloat minMargin = MAXFLOAT;
// 手动调用layoutAttributesForElementsInRect方法来获得cell的布局属性
NSArray *attrsArray = [self layoutAttributesForElementsInRect:CGRectMake(proposedContentOffset.x, 0, self.collectionView.frame.size.width, self.collectionView.frame.size.height)];
// 遍历属性数组,找出和cell中心点间距最小的间距,
for (UICollectionViewLayoutAttributes *attrs in attrsArray) {
CGFloat cellCenterX = attrs.center.x;
CGFloat collectionViewCenterX = self.collectionView.frame.size.width / 2;
CGFloat margin = cellCenterX - collectionViewCenterX - proposedContentOffset.x;
// 遍历,遇到更小的margin就把最小间距设置为这个margin
if (ABS(margin) <= ABS(minMargin)) {
minMargin = margin;
}
}
// 最后把期望的停止位置再偏移一个margin间距,就达到目的了
proposedContentOffset.x += minMargin;
return proposedContentOffset;
}
自定义继承于UICollectionViewLayout的布局对象
- 这个布局方式就不是流水布局了,一切都按自定义来。
- 在layoutAttributesForElementsInRect中,一切都是空的,UICollectionViewLayoutAttributes没有设置属性。需要我们自己实现这个方法,在这里设置好所有的布局然后返回
- 为什么上面能拿到设置好的布局属性?因为上面的自定义布局继承于流水布局,它的父类流水布局已经把布局方式设置好了,只需要调用super就能拿到。
- 下面是实现layoutAttributesForElementsInRect并且完成了让图片围成一个圈的步骤
- 其中有
获得collectionView中指定section中cell的个数的方法,indexPath和UICollectionViewLayoutAttributes的创建方法
- 其中有
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attrsArray = [NSMutableArray array];
// 这个方法获得collectionView中Section:0的cell数
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i ++) {
// 根据i创建一个Section:0的indexPath,这样indexPath.row = i,indexPath.section = 0
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
// 根据cell的indexPath为每个cell创建UICollectionViewLayoutAttributes
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 设置计算cell的位置和size
attrs.size = CGSizeMake(60,60);
// 2.3.1 圆心
CGFloat centerX = self.collectionView.frame.size.width * 0.5;
CGFloat centerY = self.collectionView.frame.size.height * 0.5;
// 平分角度
NSInteger count = [self.collectionView numberOfItemsInSection:0];
CGFloat averageAngle = M_PI * 2 / count;
// 2.3.2 获取每个图片角度
CGFloat angle = averageAngle * i;
// 半径
CGFloat radius = centerY - 30;
// 2.3.3 计算a b 值
CGFloat a = sin(angle) * radius;
CGFloat b = cos(angle) * radius;
attrs.center = CGPointMake(a + centerX,b + centerY);
// 别忘了把设置好的UICollectionViewLayoutAttributes添加到数组中
[attrsArray addObject:attrs];
}
return attrsArray;
}
- 但是如果我们想切换布局,比如点击某个按钮让它变成流水布局,再点又变成圆形,光这么设置的话不够
- 切换布局的时候会调用下面这个方法:需要告诉系统每个cell的位置,如果不实现,那就崩了
- 把计算cell属性的代码全搬到这里来,然后返回就可以了。注意这里不同cell参照的是indexPath
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.各种属性 = xxxx;
return attrs;
}
竖直布局
如何做到如下的布局?
- 自定义布局,需要实现layoutAttributesForElementsInRect方法
- 注意这里的rect不用设置,不设置就意味着把所有cell的布局都设置好
- 布局完上面6个的frame,后面的循环体只需要获取上面对应位置的布局属性
}else if(i == 5){
cellH = cellW;
cellX = cellW;
cellY = cellH;
}else{
UICollectionViewLayoutAttributes *lastLayoutAtt = self.layoutAtts[i - 6];
CGRect lastFrm = lastLayoutAtt.frame;
cellH = lastFrm.size.height;
cellX = lastFrm.origin.x;
cellY = lastFrm.origin.y + 2 * cellW;
- 注意:
这个collectionView可以拖动,而继承viewLayout的布局需要手动设置contentSize
- 在这个方法里设置contentSize即可
- (CGSize)collectionViewContentSize{
return CGSizeMake(self.collectionView.frame.size.width, row * cellH);
}