UICollectionView详解二:UICollectionViewFlowLayout

UICollectionViewFlowLayout是一个具体的layout对象,用来把item布局在网格中,并且可选页眉和页脚。在collection view中的items,可以从一行或者一列flow至下一行或者下一列(行或者列取决于滚动的方向)。每行都会根据情况,包含尽可能多的Cells。Cells可以是相同的尺寸,也可以是不同的尺寸。

页眉页脚的属性如下图

当垂直的时候,需要设置Height,如下图

当水平的时候,需要设置Width,如下图

Section Inset : 我们先通过两个图来看看Sections Insets是怎么回事

从上面的两个图中,我们大概知道了,Section Inset就是某个section中cell的边界范围。

本节中得Demo,就是针对一行进行设计的。效果图如下:

一行显示很多的Icon,并且距中心点越近的Icon,尺寸越大;距离中心点越远的Icon,尺寸逐渐变小。而且在滚动过程中,也是这种效果。

1. 主界面的代码如下(如有不明白的,请参考我上一节讲解的内容,请点击这里):

@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>
@property (nonatomic,strong) NSMutableArray *images;
@property (nonatomic,weak) UICollectionView *collectionView;
@end

static NSString *const identifer = @"ImageCell";

@implementation ViewController

-(NSMutableArray *)images {
    if (!_images) {
        _images = [NSMutableArray array];
        for (int i=1;i<=20;i++) {
            [_images addObject:[NSString stringWithFormat:@"%d.jpg",i]];
        }
    }
    return _images;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    CGRect rect = CGRectMake(0, 250, self.view.frame.size.width,200);
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:rect collectionViewLayout:[[LFFlowLayout alloc] init]];
    collectionView.dataSource = self;
    collectionView.delegate = self;

    // 注册collectionView(因为是从xib中加载cell的,所以registerNib)
    [collectionView registerNib:[UINib nibWithNibName:@"ImageCell" bundle:nil] forCellWithReuseIdentifier:identifer];

    [self.view addSubview:collectionView];

    self.collectionView = collectionView;
}

#pragma mark - 点击屏幕空白处,切换布局模式
//- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//    if ([self.collectionView.collectionViewLayout isKindOfClass:[LFFlowLayout class]]) {
//        [self.collectionView setCollectionViewLayout:[[LFNormalLayout alloc] init] animated:YES];
//    }
//    else{
//        [self.collectionView setCollectionViewLayout:[[LFFlowLayout alloc] init] animated:YES];
//    }
//
//}

#pragma mark - delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return  self.images.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    ImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifer forIndexPath:indexPath];

    cell.iconName = self.images[indexPath.item];

    return cell;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    // 1. 删除模型数据
    [self.images removeObjectAtIndex:indexPath.item];

    // 2. 删除UI元素
    [collectionView deleteItemsAtIndexPaths:@[indexPath]];
}

2. 自定义一个类,继承自UICollectionViewFlowLayout

@interface LFFlowLayout : UICollectionViewFlowLayout

@end

3. 控制布局的主要代码如下,有以下几点,我进行说明:

1) prepareLayout 方法可以对布局中得内容进行初始化工作。如果init方法中执行的代码不起作用,可以放在prepareLayout中。

- (void)prepareLayout {
    [super prepareLayout];
    ...
}

2)  允许CollectionView Bounds发生变化时,重新进行布局,
要实现下面的方法

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

3) 流式处理中最关键的方法如下,用来获取CollectionView的所有Item项的layout,进行相印的处理。

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
       ...
}

4) 对于Item的细节计算,可以调用下面的方法,完成定位处理。

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
     ...
}

完整的实现代码如下:

#define kItemWidth 100

#import "LFFlowLayout.h"

@implementation LFFlowLayout

-(instancetype)init
{
    if(self=[super init]){
    }
    return self;
}

- (void)prepareLayout {
    [super prepareLayout];
    // 设置为水平滚动
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    // 设置每个Item之间的距离
    self.minimumLineSpacing = 100;
    // 重新设置Item的尺寸,不然的话,有等比例缩小的可能
    self.itemSize = CGSizeMake(kItemWidth, kItemWidth);
}

#pragma mark - 重写父类的方法
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

// 获取CollectionView的所有Item项,进行相印的处理(移动过程中,控制各个Item的缩放比例)
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *array = [super layoutAttributesForElementsInRect:rect];

    CGFloat inset = (self.collectionView.frame.size.width - kItemWidth) * 0.5;
    // 设置第一个和最后一个默认居中显示
    self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);

    CGRect visibleRect;
    visibleRect.origin =self.collectionView.contentOffset;
    visibleRect.size = self.collectionView.frame.size;

    CGFloat collectionViewCenterX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;

    for (UICollectionViewLayoutAttributes *attrs in array) {
        // 只处理正在界面上面显示的Item
        if(!CGRectIntersectsRect(visibleRect, attrs.frame)) continue;

        // 计算各个Item的缩放比例(距离中线越近,缩放比例就越大)
        CGFloat scale;
        // 防止突变的情况(当Item的中心与collectionView中心的距离大于等于collectionView宽度的一半时,Item不缩放,平稳过度)
        if(ABS(attrs.center.x - collectionViewCenterX) >= self.collectionView.frame.size.width * 0.5){
            scale = 1;
        }
        else{
            scale = 1 + 0.8 * (1 - ABS(attrs.center.x - collectionViewCenterX) / (self.collectionView.frame.size.width * 0.5));
        }
        attrs.transform3D = CATransform3DMakeScale(scale, scale, 1);
    }

    return array;
}

// 当UICollectionView停止的那一刻ContentOffset的值(控制UICollectionView停止时,有一个Item一定居中显示)
// 1.proposedContentOffset默认的ContentOffset
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {

    //1. 获取UICollectionView停止的时候的可视范围
    CGRect contentFrame;
    contentFrame.size = self.collectionView.frame.size;
    contentFrame.origin = proposedContentOffset;

    NSArray *array = [self layoutAttributesForElementsInRect:contentFrame];

    //2. 计算在可视范围的距离中心线最近的Item
    CGFloat minCenterX = CGFLOAT_MAX;
    CGFloat collectionViewCenterX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
    for (UICollectionViewLayoutAttributes *attrs in array) {
        if(ABS(attrs.center.x - collectionViewCenterX) < ABS(minCenterX)){
            minCenterX = attrs.center.x - collectionViewCenterX;
        }
    }

    //3. 补回ContentOffset,则正好将Item居中显示
    return CGPointMake(proposedContentOffset.x + minCenterX, proposedContentOffset.y);
}

@end
时间: 2024-11-09 11:11:44

UICollectionView详解二:UICollectionViewFlowLayout的相关文章

iOS开发——UI篇OC篇&amp;UICollectionView详解+实例

UICollectionView详解+实例 实现步骤: 一.新建两个类 1.继承自UIScrollView的子类,比如HMWaterflowView * 瀑布流显示控件,用来显示所有的瀑布流数据 2.继承自UIView的子类,比如HMWaterflowViewCell * 代表着瀑布流数据中的一个单元(一个格子) 3.总结 HMWaterflowView和HMWaterflowViewCell的关系实际上类似于 UITableView和UITableViewCell的关系 二.设计HMWater

UICollectionView详解

今天,将和大家一起学习UICollectionView,UIcollectionView自出来后,一直受追捧,确实好用.今天有朋友问我如何添加heardView,我简单的回答:tableview如何添加,那么CollectionView就怎么添加,后来经过自己实验发现确实不是那回事,所以列出一些自己犯的错误,供大家参考. 1.首先实例化一个 UICollectionViewFlowLayout因为要设置item,itemSize,等一些属性. UICollectionViewFlowLayout

iOS开发- UICollectionView详解+实例

iOS开发- UICollectionView详解+实例 本章通过先总体介绍UICollectionView及其常用方法,再结合一个实例,了解如何使用UICollectionView. UICollectionView 和 UICollectionViewController 类是iOS6 新引进的API,用于展示集合视图,布局更加灵活,可实现多列布局,用法类似于UITableView 和 UITableViewController 类. 使用UICollectionView 必须实现UICol

UINavigationController详解二(转)页面切换和SegmentedController

原文出自:http://blog.csdn.net/totogo2010/article/details/7682433,非常感谢. 1.RootView 跳到SecondView 首先我们需要新一个View.新建SecondView,按住Command键然后按N,弹出新建页面,我们新建SecondView 2.为Button 添加点击事件,实现跳转 在RootViewController.xib中和RootViewController.h文件建立连接 在RootViewController.m

Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

[Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.Android 布局学习之——LinearLayout的layout_weight属性   4.Android 布局学习之——LinearLayout属性baselineAligned的作用及baseline    Layout Parameters(布局参数): 在XML文件中,我们经常看到类似与lay

CSS3中的弹性流体盒模型技术详解(二)

在上一篇文章<CSS3中的弹性流体盒模型技术详解(一)>里,我给大家列出了,从css1到css3各版本中盒子模型的基本元素.本篇我会把余下的属性进行详细讲解. box-pack 作用:用来规定子元素在盒子内的水平空间分配方式 box-pack 语法:box-pack: start | end | center | justify; start 对于正常方向的框,首个子元素的左边缘吸附在盒子的左边框显示 对于相反方向的框,最后子元素的右边缘吸附在盒子的右边框显示 end 对于正常方向的框,最后子

php学习之道:WSDL详解(二)

3.定义服务使用的逻辑消息 当服务的操作被调用时,服务被定义为消息交换.在wsdl文档中,这些消息被定义message元素.这些消息由称之为part元素的部分组成. 一个服务的操作,通过指定逻辑消息的方式来定义.当操作被调用时,逻辑消息被交换.(也就是说,逻辑消息代表了服务的操作)这些逻辑消息,将在网络上传输的数据定义为xml文档.他包含了所有的参数,这些参数是方法调用的一部分.(也就是说,逻辑消息里的参数,是操作对应方法的参数集合) 消息和参数列表:每一个被服务暴露的操作能且仅能有一个输入消息

LinearLayout详解二:从其父类View说起

这个View类说来就话长了,但我们又不得不说,要说呢,就得说的彻底,要让大家看得一清二楚,明明白白.所以我们就从源代码角度来看一个view是如何被加载的吧. 如果大家不知道怎么下载android的源代码,或者说懒得去下载(因为源代码确实比较大,大概有10G)的话,教大家几个取巧的办法: 1.直接在google中输入"android view.java"即可.这种方法成功率非常高,一般android的比较重要的类都能搜到. 2.给大家提供一个人家用于放源码的的git:[email pro

jquery validate 详解二

原文:http://blog.sina.com.cn/s/blog_608475eb0100h3h2.html 这里只是第二篇,前面的内容请参阅上一篇 五.常用方法及注意问题 1.用其他方式替代默认的SUBMIT 1 $().ready(function() { 2 $("#signupForm").validate({ 3 submitHandler:function(form){ 4 alert("submitted"); 5 form.submit(); 6