自定义流水布局(实现相册功能)

来源:互联网 发布:专升本网络教育要多久 编辑:程序博客网 时间:2024/06/05 05:25

自定义流水布局collectionViewFlowLayout

  • 本文主要讲解如何进行自定义流水布局来实现一个相册功能,如下图所示:

这里写图片描述

一、

  • 想要实现类似这样的相册功能有三种办法

    1> 利用scrollView来写,添加三个imageView,然后实时监控scrollView的滚动,一旦有imageView离开就立刻放入缓存池中以便用来复用(这种方法属于比较麻烦的)

    2> 利用tableView,但是tableView只支持竖直滚动,不支持水平,但是可以改变tableView的transfrom属性来实现(这种方法看起来有点不合常理)

    3> 利用Apple自带的collectionView(iOS6之后就用得比较广泛了),collectionView是一种比较牛逼的控件,利用好它我们可以做出很多漂亮的界面,collectionView默认是垂直滚动,但是它也支持水平滚动,而且也有重用机制,我们只需要负责填充数据,控制cell的缩放罢了(因此collectionView是首选)

  • TableView和CollectionView的排布区别

    1> tableView的排布是一行一行往下排布,而collectionView的排布完全取决于Laytout,怎样的Laytout布局决定了显示怎样的cell

    2> 因此我们可以看出,想要让界面显示得更加好看(如瀑布流等),就得自定义布局了

    接下来就开始实现功能啦!

二、实现如下图样式

这里写图片描述

  • 首先实现能展现数据并且水平滚动

    1> viewDidLoad方法中创建collectionView,并且注册cell

    - (void)viewDidLoad{    [super viewDidLoad];    CGFloat w = self.view.bounds.size.width;    CGRect collectionViewFrame = CGRectMake(0, 100, w, 200);    // 创建collectionView    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:collectionViewFrame collectionViewLayout:[[UICollectionViewFlowLayout  alloc] init]];    // 设置代理和数据源    collectionView.delegate = self;    collectionView.dataSource = self;    // 注册cell    [collectionView registerNib:[UINib nibWithNibName:@"DSImageCell" bundle:nil] forCellWithReuseIdentifier:ID];    // 添加到控制器View中    [self.view addSubview:collectionView];}

2> 创建images数组模型,实现collectionView的数据源方法

@property (nonatomic, strong) NSMutableArray *images;
    - (NSArray *)images{        if (_images== nil) {            self.images = [NSMutableArray array];            for (int i = 1; i < 20; i++) {                [self.images addObject:[NSString stringWithFormat:@"%d", i]];            }        }        return _images;}
    /** *  每一组有多少个cell */- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{        return self.images.count;}    /** *  第indexPath位置上显示什么样的cell */- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{        DSImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];        cell.image = self.images[indexPath.item];        return cell;}
注意:这里我用的是自定义cell(DSImageCell),并且用了xib来展示item,以下是DSImageCell的.h和.m文件
    @interface DSImageCell : UICollectionViewCell    @property (nonatomic, copy) NSString *image;    @end
    @interface DSImageCell ()    @property (weak, nonatomic) IBOutlet UIImageView *iconView;    @end    @implementation DSImageCell    - (void)awakeFromNib    {         // 边框颜色        self.iconView.layer.borderColor = [UIColor whiteColor].CGColor;        // 边框宽度        self.iconView.layer.borderWidth = 3;        // 变宽圆角        self.iconView.layer.cornerRadius = 5;        // 剪切边框超出范围        self.iconView.layer.masksToBounds = YES;    }    - (void)setImage:(NSString *)image    {        _image = [image copy];        self.iconView.image = [UIImage imageNamed:image];    }    @end

因为在创建collectionView的时候在init方法中传入的collectionViewLayout是系统自带的UICollectionViewFlowLayout,所以默认是垂直滚动的,所以此时就得自定义流水布局了!

三.

  • 自定义流水布局,创建一个DSLineLayout继承自UICollectionViewFlowLayout,在.m文件中实现如下需求:

    1.cell的缩放2.停止滚动后cell居中

    1> 设置滚动方向,cell的大小,cell之间的间隔,还有第一个cell和最后一个cell居中(注意:初始化一般在prepareLayout方法中进行,不能在init方法中,因为init方法中collectionView是没有尺寸的),而且每一个cell都有自己的UICollectionViewLayoutAttributes属性,该属性可以控制cell的位置,大小等

    /**     *  一些初始化的工作最好在这里实现     */    - (void)prepareLayout    {            [super prepareLayout];            // 水平方向            self.scrollDirection = UICollectionViewScrollDirectionHorizontal;            // 设置item的宽高            self.itemSize = CGSizeMake(DSItemWH, DSItemWH);            // 设置cell之间的间距            self.minimumLineSpacing = DSItemWH - 40;            CGFloat inset = (self.collectionView.frame.size.width - DSItemWH) * 0.5;            // 设置组头和组尾的inset(让第一个和最后一个cell显示在最中间)            self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);    }

至于DSItemWH就是cell的默认宽高

static const CGFloat DSItemWH = 100;

这个时候就可以实现上面图片所展示的样式了,所以接下来就需要实现cell在滚动中的缩放(也就是需要时刻监听屏幕范围内cell,此时得重写两个方法)

第一个方法:判断是否要在collectionView的边界发生改变的时候重新布局cell,该方法默认返回NO,如果返回YES,内部会重新调用prepareLayout和layoutAttributesForElementsInRect方法获得所有cell的布局属性

/** *  只要边界改变的时候重新布局性 */ -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{    return YES;}

第二个方法:也就是刚才上面所说的layoutAttributesForElementsInRect方法,该方法可以在滚动中重新布局cell,然后返回rect范围的所有cell的UICollectionViewLayoutAttributes属性,所以想要实现滚动中控制cell的缩放就得在这个方法里面实现

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{    return nil;}

下面是实现的思路:首先取得屏幕可见的rect范围,然后从遍历所有cell的UICollectionViewLayoutAttributes,判断cell是否在屏幕内,然后根据cell的centerX和屏幕的centerX之间的距离来算出需要缩放的比例,然后赋值给cell的transfrom属性

/** *  选中该rect内的所有子控件 * *  @param rect 所有item加起来的rect * *  @return 所有item */- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{    // 0.屏幕的可见范围    CGRect visiableRect;    visiableRect.size = self.collectionView.frame.size;    visiableRect.origin = self.collectionView.contentOffset;    // 1.取出所有item的UICollectionViewLayoutAttributes    NSArray *attributeArray = [super layoutAttributesForElementsInRect:rect];    // 获取屏幕中点的X    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;    // 2.遍历所有的布局属性    for (UICollectionViewLayoutAttributes *attrs in attributeArray)    {        // 如果不在屏幕上,直接跳过(两个rect是否相交)        if (!CGRectIntersectsRect(visiableRect, attrs.frame)) continue;        // 获取item的中点X        CGFloat itemCenterX = attrs.center.x;        CGFloat scale = 1+ 0.8 * (1 - ABS(centerX - itemCenterX) / (self.collectionView.frame.size.width * 0.5));        attrs.transform = CGAffineTransformMakeScale(scale, scale);    }    return attributeArray;}

接下来,相册功能就差cell滚动停止后自动回到中点位置
在实现之前还得知道一个方法

/** *  用来设置collectionView停止滚动那一刻的位置 * *  @param proposedContentOffset 原本collectionView停止滚动那一刻的位置 *  @param velocity              滚动速度 * *  @return 想要停止滚动的位置 */- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ }

该方法设置collectionView停止滚动那一刻的位置, proposedContentOffset表示**原本**collectionView停止滚动那一刻的位置, velocity滚动速度,而返回值就是你想要设置的位置.接下来说下实现思路:

首先获取屏幕的centerX,然后获取滚动结束后还在屏幕里面所有cell的centerX,并且找出离屏幕中点X最近的cell,求出两个centerX之间的差值(不需要求绝对值,因为差值又可能为正/负),所以cell停下来的位置就是proposedContentOffset.x加上这段差值

    // 1.获取屏幕中点的X    CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;    // 2.计算屏幕滚动最后一刻的位置(lastRect大小和collectionView的frame一样)    CGRect lastRect;    lastRect.origin = proposedContentOffset;    lastRect.size = self.collectionView.frame.size;    // 3.取出lastRect范围内的item属性    NSArray *attributeArray = [super layoutAttributesForElementsInRect:lastRect];    // 4.遍历lastRect范围内的item属性    CGFloat adjustContentOffet = MAXFLOAT;    for (UICollectionViewLayoutAttributes *attrs in attributeArray) {        // 选出其中item的中点X和collectionView中点X的绝对值最小的item出来        if (ABS(attrs.center.x - centerX) < ABS(adjustContentOffet)) {            adjustContentOffet = attrs.center.x - centerX; // 该差值可能为正/负        }    }    return CGPointMake(proposedContentOffset.x + adjustContentOffet, proposedContentOffset.y);

到此为止,整个相册功能就实现完毕了,以上代码基本上都在了,由于代码里面注释写得十分清楚,所以就不多以言语代替了,而且因为是系统自带的循环利用,所以用起来一点都不会卡!

0 0
原创粉丝点击