关于collectionView的布局

#前言:

项目中需要实现个类似浏览器中标签页管理的界面。由于大boss觉得界面卡所以需要改动。(其实不是卡,是滑动时动画不如多个的连贯导致的,但大boss有需求,就算没条件,创造条件也要上)
这次写的比较粗糙,望见谅。

  1. 想要实现的效果
    learn
  2. 实现之前的效果
    before
  3. 实现以后的效果
    after

    因为以前就很想使用collectionView,这次x老大让我来做这个改动,心里那个激动啊。
    废话不说,直接加入主题。
    #布局对象

collectionView和tableView都是由data-sourcedelegate驱动的。但为什么collectionView会比tableView在对cell的布局更灵活呢?
因为collectionView将cell的位置、大小、外观委托给一个单独的布局对象(继承自collectionViewLayout这个基类)来管理。所以通过自定义一个布局对象,你能实现任何你能想象的到的布局(比如:瀑布流、水平缩放、杂志封面等)。
在iOS中有个可以直接拿过来使用的布局对象UICollectionViewFlowLayout(流式布局)。它通过一个接着一个的放置cell来建立自己的布局。通过设置滚动的方向、大小及cell之间的间距,flowLayout可以在单行或单列中布局cell。 在上图给出的效果图,我们可以直接定制flowLayout而不需要继承自collectionViewLayout这个基类,因为flowLayout以及能够满足我们的要求了。

#Cells和其他Views
为了能让我们做出任意的布局,collectionView拥有着和tableView类似的视图层级但比tableView更灵活的。像使用tableViw一样,你的主要内容显示在cell中,cell可以被任意分组到section中。 collectionView也有和tableView section中headerView和footView,但是用supplementaryView来表示header/foot,用法和tableview不一样,supplementaryView的数量和放置的位置完全由布局来控制。 除此之外,collectionView还有个decorationView。这个view完全就是一个装饰用的view。它并不从数据源获取内容。 supplementary和decoration必须是UICollectionResuableView的子类。 在布局中使用的视图类都需要在collectionView中注册(register),这样当data source从重用池中取出视图时,它才能重建新的实例。注册一般放到viewdidload中做。
如果你使用代码写的cell/supplementaryView使用下面的注册方法:

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;

如果使用的是xib弄的cell/supplementaryView使用下面的政策方法:

- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

#自定义布局
一般来说有2种类型的collectionView布局:

  1. 独立于内容的布局计算.像tableview货flowLayout这些情况就是这种布局。每个cell的位置和外观不是基于它显示的内容来计算的,所有cell的显示顺序却是基于内容的顺序。以flowlayout(tableView可以看出是flowlayout在竖直情况下的布局)作为例子,每个cell都是基于前一个cell的放置。布局对象不必访问实际数据来计算布局。
  2. 基于内容的布局计算.为了计算cell的显示位置和外观,布局对象需要直接访问collectionView的数据源。例如日历事件的视图就是这种类型的。在很多情况下,布局对象不仅需要取出当前可见cell的数据,还需要从所有记录中取出一些决定当前那些cell的可见数据。

如果有个依赖内容的布局,那你就不能使用自定义的uicollectionviewflowlayou,而需要写自定义的(继承自uicollectionViewLayout基类的)布局类。

#需要重写的方法

- (CGSize)collectionViewContentSize;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;

很多blog说上面的6个方法都是必须被重写的但在我的代码中有几个没重写,也能正常使用。apple文档中这样子写道:every layout object should implement the following methods.

下面上布局的代码:其他的代码就不能上了(因为collectionView的最核心的部分就是layout这里的代码了)。待我有空了,就专门写个比较酷炫的demo上传到github上来和大家一起讨论研究。共同进步。

//  WSPXBrowserTabsLayout.m
//  UOne
//
//  Created by MrChens on 15/12/1.
//  Copyright © 2015年 chinanetcenter. All rights reserved.
//

#import "WSPXBrowserTabsLayout.h"
#import "WSPXBrowserTabsLayoutAttributes.h"
#define ACTIVE_DISTANCE         100 //cell中心点到屏幕中心点之间的间距
#define TRANSLATE_DISTANCE      100
#define ZOOM_FACTOR             0.25f//恢复成1时剩下的比率
#define SCALE_FACTOR            0.75f//相对于中间的cell的比率
#define FLOW_OFFSET             5
#define INACTIVE_GREY_VALUE     0.6f
#define kMrChenLog              (1)
@interface WSPXBrowserTabsLayout ()
@property (nonatomic, strong) NSMutableArray *deleteIndexPaths;
@property (nonatomic, strong) NSMutableArray *insertIndexPaths;
@property (nonatomic, assign) CGFloat itemHeight;
@property (nonatomic, assign) CGFloat itemWidth;
@end

@implementation WSPXBrowserTabsLayout
/**< 从xib中加载 */
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  if (self) {
    _itemWidth = kScreenWidth * 0.6;
    _itemHeight = kScreenHeight * 0.6;
    self.itemSize = CGSizeMake(_itemWidth, _itemHeight);
    self.minimumInteritemSpacing = 20;
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    self.collectionView.decelerationRate = 1;
  }
  return self;
}
/**< 从代码加载 */
-(id)init
{
  self = [super init];
  if (self) {
    _itemWidth = kScreenWidth * 0.6;
    _itemHeight = kScreenHeight * 0.6;
    self.itemSize = CGSizeMake(_itemWidth, _itemHeight);
    self.minimumInteritemSpacing = 20;
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    self.collectionView.decelerationRate = 1;
  }
  return self;
}

+ (Class)layoutAttributesClass {
    return [WSPXBrowserTabsLayoutAttributes class];
}

-(void)prepareLayout {
  [super prepareLayout];

  self.collectionView.contentInset = UIEdgeInsetsMake(0, (CGRectGetWidth(self.collectionView.frame) - self.itemSize.width) / 2, 0, (CGRectGetWidth(self.collectionView.frame) - self.itemSize.width) / 2);

}

#pragma mark -
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes forVisibleRect:(CGRect)visibleRect{

    if (attributes.representedElementKind) {
        return;
    }

    //计算当前的cell中心距离屏幕的中心的位置
    CGFloat distanceFromVisibleRectToItem = CGRectGetMidX(visibleRect) - attributes.center.x;
    //    NSLog(@"距离的位置:%lf", distanceFromVisibleRectToItem);

      //计算距离与***的比例
    CGFloat normalizedDistance = distanceFromVisibleRectToItem / ACTIVE_DISTANCE;
    CGAffineTransform transforms =CGAffineTransformIdentity;

    CGFloat maskAlpha = 0.0f;
      //如果当前cell中心位置的到屏幕的中心位置小于某个值的话,设置移动的动画
    if (fabs(distanceFromVisibleRectToItem) < ACTIVE_DISTANCE) {

    //        NSLog(@"移动的比例:%lf", normalizedDistance);
        CGFloat scale = SCALE_FACTOR + ZOOM_FACTOR * (1 - ABS(normalizedDistance));
    //        NSLog(@"缩放的比例:%lf", scale);
        transforms = CGAffineTransformScale(transforms, scale, scale);

        attributes.zIndex = 0;
        maskAlpha = INACTIVE_GREY_VALUE + (1- ABS(normalizedDistance))* (-INACTIVE_GREY_VALUE);
        //如果大于某个值的话,设置普通的动画
    } else {
        transforms = CGAffineTransformScale(transforms, SCALE_FACTOR, SCALE_FACTOR);
        attributes.zIndex = 1;//值越小则越在顶部
        maskAlpha = INACTIVE_GREY_VALUE;
    }
      attributes.transform = transforms;
    [(WSPXBrowserTabsLayoutAttributes *)attributes setMaskingValue:maskAlpha];
}

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

    NSArray *layoutAttributesArray = [super layoutAttributesForElementsInRect:rect];

    CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, CGRectGetWidth(self.collectionView.bounds), CGRectGetHeight(self.collectionView.bounds));

    for (UICollectionViewLayoutAttributes *attributes in layoutAttributesArray) {
        if (CGRectIntersectsRect(attributes.frame, rect)) {
            [self applyLayoutAttributes:attributes forVisibleRect:visibleRect];
        }
    }
    return layoutAttributesArray;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {

    UICollectionViewLayoutAttributes *layoutAttributesArray = [super     layoutAttributesForItemAtIndexPath:indexPath];

    CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, CGRectGetWidth(self.collectionView.bounds), CGRectGetHeight(self.collectionView.bounds));
    [self applyLayoutAttributes:layoutAttributesArray forVisibleRect:visibleRect];
    return layoutAttributesArray;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
      return YES;
}
    #pragma mark - 设置吸附效果

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

    CGFloat offsetAdjustment = MAXFLOAT;
    CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
    CGRect proposedRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);

    NSArray* array = [self layoutAttributesForElementsInRect:proposedRect];

    for (UICollectionViewLayoutAttributes* layoutAttributes in array)
    {
        if (layoutAttributes.representedElementCategory != UICollectionElementCategoryCell)
        continue;

        CGFloat itemHorizontalCenter = layoutAttributes.center.x;
        if (fabs(itemHorizontalCenter - horizontalCenter) < fabs(offsetAdjustment))
        {
            offsetAdjustment = itemHorizontalCenter - horizontalCenter;
        }
    }
    return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
#pragma mark -
-(void)prepareForCollectionViewUpdates:(NSArray *)updateItems {
      [super prepareForCollectionViewUpdates:updateItems];

      self.deleteIndexPaths = [NSMutableArray array];
      self.insertIndexPaths = [NSMutableArray array];

      for (UICollectionViewUpdateItem *update in updateItems) {
        if (update.updateAction == UICollectionUpdateActionDelete) {
          [self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
        } else if (update.updateAction == UICollectionUpdateActionInsert) {
          [self.insertIndexPaths addObject:update.indexPathAfterUpdate];
        }
      }
    }

-(void)finalizeCollectionViewUpdates {
      [super finalizeCollectionViewUpdates];
      self.deleteIndexPaths = nil;
      self.insertIndexPaths = nil;
}

#pragma mark - collectionView增加删除动画

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {

      UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
      if ([self.insertIndexPaths containsObject:itemIndexPath]) {
        if (!attributes)
          attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
          attributes.center = CGPointMake(CGRectGetMidX(self.collectionView.frame), CGRectGetMidY(self.collectionView.frame));
          attributes.transform = CGAffineTransformMakeScale(0.5, 0.5);

          attributes.alpha = 0.0;
      }
          return attributes;
    }

- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {

      UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];

      if ([self.deleteIndexPaths containsObject:itemIndexPath]) {
        if (!attributes)
          attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];


          attributes.transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(0.5, 0.5), 0.05, 0.05);

        NSUInteger count = [self.collectionView numberOfItemsInSection:0];

          BOOL isLastIndex = itemIndexPath.row== count?YES:NO;

        if (count * _itemWidth < kScreenWidth || !isLastIndex) {
          attributes.center = CGPointMake( attributes.center.x, -CGRectGetHeight(self.collectionView.frame));
          NSLog(@"~~~~count:%lu", (unsigned long)count);
        } else if(isLastIndex) {
          count++;
          NSUInteger rightEdge = self.collectionView.contentInset.right;
          NSUInteger leftEdge = self.collectionView.contentInset.left;
          int x = count * _itemWidth + (count -1)*10 - rightEdge - leftEdge - kScreenWidth/2;
          NSLog(@"!!!!count:%lu x:%d", (unsigned long)count, x);
          attributes.center = CGPointMake(x, -CGRectGetHeight(self.collectionView.frame));
        }
          attributes.alpha = 0.0;
      }

      return attributes;
    }





    @end
Zerlz wechat
扫码关注一个很懒的程序员!
Winter is coming, give me a penny!